001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010:
011: package org.mmbase.util;
012:
013: import java.util.*;
014: import java.lang.reflect.*;
015: import java.text.Collator;
016: import org.mmbase.cache.Cache;
017: import org.mmbase.util.logging.*;
018: import org.mmbase.datatypes.StringDataType;
019:
020: /**
021: * A bit like {@link java.util.ResourceBundle} (on which it is based), but it creates
022: * SortedMap's. The order of the entries of the Map can be influenced in tree ways. You can
023: * associate the keys with JAVA constants (and their natural ordering can be used), you can wrap the
024: * keys in a 'wrapper' (which can be of any type, the sole restriction being that there is a
025: * constructor with String argument or of the type of the assiocated JAVA constant if that happened
026: * too, and the natural order of the wrapper can be used (a wrapper of some Number type would be
027: * logical). Finally you can also explicitely specify a {@link java.util.Comparator} if no natural
028: * order is good.
029: *
030: * @author Michiel Meeuwissen
031: * @since MMBase-1.8
032: * @version $Id: SortedBundle.java,v 1.31 2008/02/03 17:33:57 nklasens Exp $
033: */
034: public class SortedBundle {
035:
036: private static final Logger log = Logging
037: .getLoggerInstance(SortedBundle.class);
038:
039: /**
040: * Constant which can be used as an argument for {@link #getResource}
041: */
042: public static final Class<?> NO_WRAPPER = null;
043: /**
044: * Constant which can be used as an argument for {@link #getResource}
045: */
046: public static final Comparator<? super Object> NO_COMPARATOR = null;
047: /**
048: * Constant which can be used as an argument for {@link #getResource}
049: */
050: public static final HashMap<String, Object> NO_CONSTANTSPROVIDER = null;
051:
052: // cache of maps.
053: private static Cache<String, SortedMap<Object, Object>> knownResources = new Cache<String, SortedMap<Object, Object>>(
054: 100) {
055: public String getName() {
056: return "ConstantBundles";
057: }
058:
059: public String getDescription() {
060: return "A cache for constant bundles, to avoid a lot of reflection.";
061: }
062: };
063:
064: static {
065: knownResources.putCache();
066: }
067:
068: /**
069: * You can specify ValueWrapper.class as a value for the wrapper argument. The keys will be objects with natural order of the values.
070: */
071:
072: public static class ValueWrapper implements
073: Comparable<ValueWrapper> {
074: private final Object key;
075: private final Object value;
076: private final Comparator<Object> com;
077:
078: public ValueWrapper(Object k, Comparable<Object> v) {
079: key = k;
080: value = v;
081: com = null;
082: }
083:
084: public ValueWrapper(Object k, Object v, Comparator<Object> c) {
085: key = k;
086: value = v;
087: com = c;
088: }
089:
090: public int compareTo(ValueWrapper other) {
091: int result = com != null ? com.compare(value, other.value)
092: : ((Comparable) value).compareTo(other.value);
093: if (result != 0)
094: return result;
095: if (key instanceof Comparable) {
096: return ((Comparable<Object>) key).compareTo(other.key);
097: } else {
098: return 0;
099: }
100: }
101:
102: public boolean equals(Object o) {
103: if (o == this )
104: return true;
105: if (o == null)
106: return false;
107: if (getClass() == o.getClass()) {
108: ValueWrapper other = (ValueWrapper) o;
109: return key.equals(other.key)
110: && (value == null ? other.value == null : value
111: .equals(other.value));
112: }
113: return false;
114: }
115:
116: public String toString() {
117: return Casting.toString(key);
118: }
119:
120: public Object getKey() {
121: return key;
122: }
123:
124: /**
125: * @see java.lang.Object#hashCode()
126: */
127: public int hashCode() {
128: int result = 0;
129: result = HashCodeUtil.hashCode(result, key);
130: result = HashCodeUtil.hashCode(result, value);
131: result = HashCodeUtil.hashCode(result, com);
132: return result;
133: }
134: }
135:
136: /**
137: * @param baseName A string identifying the resource. See {@link java.util.ResourceBundle#getBundle(java.lang.String, java.util.Locale, java.lang.ClassLoader)} for an explanation of this string.
138: *
139: * @param locale the locale for which a resource bundle is desired
140: * @param loader the class loader from which to load the resource bundle
141: * @param constantsProvider A map representing constants for the value. Can be based on a class using {@link #getConstantsProvider(Class)}, then the class's constants ar used to associate with the elements of this resource.
142: * @param wrapper the keys will be wrapped in objects of this type (which must have a
143: * constructor with the right type (String, or otherwise the type of the variable given by the constantsProvider), and must be Comparable.
144: * You could specify e.g. Integer.class if the keys of the
145: * map are meant to be integers. This can be <code>null</code>, in which case the keys will remain unwrapped (and therefore String).
146: * @param comparator the elements will be sorted (by key) using this comparator or by natural key order if this is <code>null</code>.
147: *
148: * @throws NullPointerException if baseName or locale is <code>null</code> (not if loader is <code>null</code>)
149: * @throws MissingResourceException if no resource bundle for the specified base name can be found
150: * @throws IllegalArgumentExcpetion if wrapper is not Comparable.
151: */
152: public static SortedMap<Object, Object> getResource(
153: final String baseName, Locale locale,
154: final ClassLoader loader,
155: final Map<String, Object> constantsProvider,
156: final Class<?> wrapper,
157: Comparator<? super Object> comparator) {
158: String resourceKey = baseName
159: + '/'
160: + locale
161: + (constantsProvider == null ? "" : ""
162: + constantsProvider.hashCode())
163: + "/"
164: + (comparator == null ? "" : "" + comparator.hashCode())
165: + "/" + (wrapper == null ? "" : wrapper.getName());
166: SortedMap<Object, Object> m = knownResources.get(resourceKey);
167: if (locale == null)
168: locale = LocalizedString.getDefault();
169:
170: if (m == null) { // find and make the resource
171: ResourceBundle bundle;
172: if (loader == null) {
173: bundle = ResourceBundle.getBundle(baseName, locale);
174: } else {
175: bundle = ResourceBundle.getBundle(baseName, locale,
176: loader);
177: }
178: if (comparator == null && wrapper != null
179: && !Comparable.class.isAssignableFrom(wrapper)) {
180: throw new IllegalArgumentException("Key wrapper "
181: + wrapper + " is not Comparable");
182: }
183:
184: m = new TreeMap<Object, Object>(comparator);
185:
186: Enumeration<String> keys = bundle.getKeys();
187: while (keys.hasMoreElements()) {
188: String bundleKey = keys.nextElement();
189: Object value = bundle.getObject(bundleKey);
190: Object key = castKey(bundleKey, value,
191: constantsProvider, wrapper, locale);
192: if (key == null)
193: continue;
194: m.put(key, value);
195: }
196: m = Collections.unmodifiableSortedMap(m);
197: knownResources.put(resourceKey, m);
198: }
199: return m;
200: }
201:
202: public static Object castKey(final String bundleKey,
203: final Object value,
204: final Map<String, Object> constantsProvider,
205: final Class<?> wrapper) {
206: return castKey(bundleKey, value, constantsProvider, wrapper,
207: null);
208: }
209:
210: /**
211: * Casts a key of the bundle to the specified key-type. This type is defined by
212: * the combination of the arguments. See {@link #getResource}.
213: */
214: protected static Object castKey(final String bundleKey,
215: final Object value,
216: final Map<String, Object> constantsProvider,
217: final Class<?> wrapper, final Locale locale) {
218: if (bundleKey == null)
219: return null;
220: Object key;
221: // if the key is numeric then it will be sorted by number
222: //key Double
223:
224: Map<String, Object> provider = constantsProvider; // default class (may be null)
225: int lastDot = bundleKey.lastIndexOf('.');
226: if (lastDot > 0) {
227: Class<?> providerClass;
228: String className = bundleKey.substring(0, lastDot);
229: try {
230: providerClass = Class.forName(className);
231: provider = getConstantsProvider(providerClass);
232: } catch (ClassNotFoundException cnfe) {
233: if (log.isDebugEnabled()) {
234: log.debug("No class found with name " + className
235: + " found from " + bundleKey);
236: }
237: }
238: }
239:
240: if (provider != null) {
241: key = provider.get(bundleKey.toUpperCase());
242: if (key == null)
243: key = bundleKey;
244: } else {
245: key = bundleKey;
246: }
247:
248: if (wrapper != null
249: && !wrapper.isAssignableFrom(key.getClass())) {
250: try {
251: if (ValueWrapper.class.isAssignableFrom(wrapper)) {
252: log.debug("wrapper is a valueWrapper");
253: if (locale == null) {
254: Constructor<?> c = wrapper.getConstructor(
255: Object.class, Comparable.class);
256: key = c.newInstance(key, value);
257: } else {
258: Constructor<?> c = wrapper.getConstructor(
259: Object.class, Object.class,
260: Comparator.class);
261: Collator comp = Collator.getInstance(locale);
262: comp.setStrength(Collator.PRIMARY);
263: key = c.newInstance(key, value, comp);
264: }
265: } else if (Number.class.isAssignableFrom(wrapper)) {
266: if (key instanceof String) {
267: if (StringDataType.DOUBLE_PATTERN.matcher(
268: (String) key).matches()) {
269: key = Casting.toType(wrapper, key);
270: }
271: } else {
272: key = Casting.toType(wrapper, key);
273: log
274: .debug("wrapper is a Number, that can simply be cast "
275: + value
276: + " --> "
277: + key
278: + "("
279: + wrapper + ")");
280: }
281: } else if (Boolean.class.isAssignableFrom(wrapper)) {
282: if (key instanceof String) {
283: if (StringDataType.BOOLEAN_PATTERN.matcher(
284: (String) key).matches()) {
285: key = Casting.toType(wrapper, key);
286: }
287: } else {
288: key = Casting.toType(wrapper, key);
289: log
290: .debug("wrapper is a Boolean, that can simply be cast "
291: + value
292: + " --> "
293: + key
294: + "("
295: + wrapper + ")");
296: }
297:
298: } else {
299: log
300: .debug("wrapper is unrecognized, suppose constructor "
301: + key.getClass());
302: Constructor<?> c = wrapper.getConstructor(key
303: .getClass());
304: key = c.newInstance(key);
305: }
306: } catch (NoSuchMethodException nsme) {
307: log.warn(nsme.getClass().getName()
308: + ". Could not convert "
309: + key.getClass().getName() + " " + key + " to "
310: + wrapper.getName() + " : " + nsme.getMessage()
311: + " locale " + locale, nsme);
312: } catch (SecurityException se) {
313: log.error(se.getClass().getName()
314: + ". Could not convert "
315: + key.getClass().getName() + " " + key + " to "
316: + wrapper.getName() + " : " + se.getMessage());
317: } catch (InstantiationException ie) {
318: log.error(ie.getClass().getName()
319: + ". Could not convert "
320: + key.getClass().getName() + " " + key + " to "
321: + wrapper.getName() + " : " + ie.getMessage());
322: } catch (InvocationTargetException ite) {
323: log.debug(ite.getClass().getName()
324: + ". Could not convert "
325: + key.getClass().getName() + " " + key + " to "
326: + wrapper.getName() + " : " + ite.getMessage());
327: } catch (IllegalAccessException iae) {
328: log.error(iae.getClass().getName()
329: + ". Could not convert "
330: + key.getClass().getName() + " " + key + " to "
331: + wrapper.getName() + " : " + iae.getMessage());
332: }
333: }
334: return key;
335: }
336:
337: /**
338: * Returns a (serializable) Map representing all accessible static public members of given class (so, all constants).
339: * @since MMBase-1.8
340: */
341: public static HashMap<String, Object> getConstantsProvider(
342: Class<?> clazz) {
343: if (clazz == null)
344: return null;
345: HashMap<String, Object> map = new HashMap<String, Object>();
346: fillConstantsProvider(clazz, map);
347: return map;
348: }
349:
350: private static void fillConstantsProvider(Class<?> clazz,
351: Map<String, Object> map) {
352: while (clazz != null) {
353: Field[] fields = clazz.getDeclaredFields();
354: for (Field constant : fields) {
355: if (Modifier.isStatic(constant.getModifiers())) {
356: String key = constant.getName().toUpperCase();
357: if (!map.containsKey(key)) { // super should not override this.
358: try {
359: Object value = constant.get(null);
360: try {
361: // support for enums where ordinal is no good.
362: Method keyMethod = value.getClass()
363: .getMethod("getValue");
364: value = "" + keyMethod.invoke(value);
365: } catch (NoSuchMethodException nsme) {
366: log.debug("" + nsme);
367: try {
368: // support for enums
369: Method keyMethod = value.getClass()
370: .getMethod("ordinal");
371: value = ""
372: + keyMethod.invoke(value);
373: } catch (Exception e1) {
374: log.debug("" + e1);
375: }
376: } catch (Exception e2) {
377: log.debug("" + e2);
378: }
379: map.put(key, value);
380: } catch (IllegalAccessException ieae) {
381: log.debug("The java constant with name "
382: + key + " is not accessible");
383: }
384: }
385: }
386: }
387: Class<?>[] interfaces = clazz.getInterfaces();
388: for (Class<?> element : interfaces) {
389: fillConstantsProvider(element, map);
390: }
391: clazz = clazz.getSuperclass();
392: }
393: }
394: }
|