1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 package nl.openedge.baritus;
32
33 import java.lang.reflect.Constructor;
34 import java.lang.reflect.InvocationTargetException;
35 import java.sql.Timestamp;
36 import java.util.ArrayList;
37 import java.util.Date;
38 import java.util.HashMap;
39 import java.util.Iterator;
40 import java.util.List;
41 import java.util.Locale;
42 import java.util.Map;
43
44 import nl.openedge.baritus.converters.BaseLocaleConverter;
45 import nl.openedge.baritus.converters.Converter;
46 import nl.openedge.baritus.converters.DateLocaleConverter;
47 import nl.openedge.baritus.converters.Formatter;
48 import nl.openedge.baritus.converters.LocaleConverter;
49 import nl.openedge.baritus.converters.LocaleFormatter;
50 import nl.openedge.baritus.converters.NoopConverter;
51 import nl.openedge.baritus.converters.BooleanConverter;
52 import nl.openedge.baritus.converters.ByteConverter;
53 import nl.openedge.baritus.converters.ByteLocaleConverter;
54 import nl.openedge.baritus.converters.CharacterConverter;
55 import nl.openedge.baritus.converters.DoubleConverter;
56 import nl.openedge.baritus.converters.DoubleLocaleConverter;
57 import nl.openedge.baritus.converters.FloatConverter;
58 import nl.openedge.baritus.converters.FloatLocaleConverter;
59 import nl.openedge.baritus.converters.IntegerConverter;
60 import nl.openedge.baritus.converters.LongConverter;
61 import nl.openedge.baritus.converters.LongLocaleConverter;
62 import nl.openedge.baritus.converters.ShortConverter;
63 import nl.openedge.baritus.converters.ShortLocaleConverter;
64
65 import org.apache.commons.logging.Log;
66 import org.apache.commons.logging.LogFactory;
67
68 /***
69 * Global registry for converters.
70 *
71 * This serves as the alternative for ConvertUtils. We use this instead of ConvertUtils
72 * for the following reasons:
73 * <ul>
74 * <li>to avoid conflicts with other uses of BeanUtils</li>
75 * <li>to have a simple, flat registry with mixed usage of locales</li>
76 * <li>to keep control of the registry within this framework</li>
77 * <li>to have an extra option for the registration of 'just' formatters</li>
78 * </ul>
79 * @author Eelco Hillenius
80 */
81 public final class ConverterRegistry
82 {
83
84 /***
85 * The set of {@link Converter}s that can be used to convert Strings
86 * into objects of a specified Class, keyed by the destination Class.
87 */
88 private Map converters = new HashMap();
89
90 /***
91 * The set of {@link Converter}s that can be used to convert Strings
92 * into objects of a specified Class, keyed by the destination Class and locale.
93 */
94 private Map localizedConverters = new HashMap();
95
96 /***
97 * singleton instance
98 */
99 private static ConverterRegistry _instance = null;
100
101 /***
102 * converter for final fallthrough
103 */
104 private static NoopConverter noopConverter = new NoopConverter();
105
106 /***
107 * when true, a noopConverter is returned as a fallback. When
108 * false, null is returned
109 */
110 private static boolean returnNoopConverterWhenNotFound = true;
111
112 private static Log log = LogFactory.getLog(ConverterRegistry.class);
113
114
115
116
117 private ConverterRegistry()
118 {
119
120 }
121
122 /***
123 * access to singleton
124 * @return ConverterRegistry singleton instance
125 */
126 public static ConverterRegistry getInstance()
127 {
128 if (_instance == null)
129 {
130 _instance = new ConverterRegistry();
131 _instance.registerDefaults();
132 }
133 return _instance;
134 }
135
136
137
138
139 private void registerDefaults()
140 {
141 register(new BooleanConverter(), Boolean.TYPE);
142 register(new BooleanConverter(), Boolean.class);
143
144 register(new ByteConverter(), Byte.TYPE);
145 register(new ByteConverter(), Byte.class);
146 register(new ByteLocaleConverter(), Byte.TYPE);
147 register(new ByteLocaleConverter(), Byte.class);
148
149 register(new CharacterConverter(), Character.TYPE);
150 register(new CharacterConverter(), Character.class);
151
152 register(new DoubleConverter(), Double.TYPE);
153 register(new DoubleConverter(), Double.class);
154 register(new DoubleLocaleConverter(), Double.TYPE);
155 register(new DoubleLocaleConverter(), Double.class);
156
157 register(new FloatConverter(), Float.TYPE);
158 register(new FloatConverter(), Float.class);
159 register(new FloatLocaleConverter(), Float.TYPE);
160 register(new FloatLocaleConverter(), Float.class);
161
162 register(new IntegerConverter(), Integer.TYPE);
163 register(new IntegerConverter(), Integer.class);
164
165 register(new LongConverter(), Long.TYPE);
166 register(new LongConverter(), Long.class);
167 register(new LongLocaleConverter(), Long.TYPE);
168 register(new LongLocaleConverter(), Long.class);
169
170 register(new ShortConverter(), Short.TYPE);
171 register(new ShortConverter(), Short.class);
172 register(new ShortLocaleConverter(), Short.TYPE);
173 register(new ShortLocaleConverter(), Short.class);
174 register(new DateLocaleConverter(), Date.class);
175 register(new DateLocaleConverter(), java.sql.Date.class);
176 register(new DateLocaleConverter(), Timestamp.class);
177 }
178
179 /***
180 * Register a custom {@link Converter} for the specified destination
181 * <code>Class</code>, replacing any previously registered Converter.
182 *
183 * @param converter Converter to be registered
184 * @param clazz Destination class for conversions performed by this Converter
185 */
186 public void register(Converter converter, Class clazz)
187 {
188 converters.put(clazz, converter);
189 }
190
191 /***
192 * Register a custom {@link LocaleConverter} for the specified destination
193 * <code>Class</code>, replacing any previously registered Converter.
194 *
195 * @param converter LocaleConverter to be registered
196 * @param clazz Destination class for conversions performed by this Converter
197 */
198 public void register(LocaleConverter converter, Class clazz)
199 {
200 localizedConverters.put(clazz, converter);
201 }
202
203 /***
204 * Register a custom {@link LocaleConverter} for the specified destination
205 * <code>Class</code>, replacing any previously registered Converter.
206 *
207 * @param converter LocaleConverter to be registered
208 * @param clazz Destination class for conversions performed by this Converter
209 * @param locale Locale class
210 */
211 public void register(LocaleConverter converter, Class clazz, Locale locale)
212 {
213 String lockey = getLocKey(clazz, locale);
214 localizedConverters.put(lockey, converter);
215 }
216
217 /***
218 * register a global formatter with the given key
219 * @param formatter the formatter
220 * @param key the key to register the instance of Formatter with
221 */
222 public void register(Formatter formatter, String key)
223 {
224 key = getLocKey(key);
225 converters.put(key, formatter);
226 }
227
228 /***
229 * register a global locale aware formatter with the given key and locale
230 * @param formatter the formatter
231 * @param key the key to register the instance of Formatter with
232 * @param locale the locale
233 */
234 public void register(LocaleFormatter formatter, String key, Locale locale)
235 {
236 key = getLocKey(key, locale);
237 localizedConverters.put(key, formatter);
238 }
239
240 /***
241 * register a global locale aware formatter with the given key
242 * @param formatter the formatter
243 * @param key the key to register the instance of Formatter with
244 */
245 public void register(LocaleFormatter formatter, String key)
246 {
247 key = getLocKey(key);
248 localizedConverters.put(key, formatter);
249 }
250
251
252 /***
253 * Remove any registered {@link Converter} for the specified destination
254 * <code>Class</code> and <code>Locale</code>.
255 *
256 * @param clazz Class for which to remove a registered Converter
257 */
258 public void deregister(Class clazz, Locale locale)
259 {
260 String lockey = getLocKey(clazz, locale);
261 localizedConverters.remove(lockey);
262 }
263
264 /***
265 * Remove any registered {@link Converter} for the specified destination
266 * <code>Class</code>.
267 *
268 * @param clazz Class for which to remove a registered Converter
269 */
270 public void deregister(Class clazz)
271 {
272 if(LocaleConverter.class.isAssignableFrom(clazz))
273 {
274 converters.remove(clazz);
275 }
276 else
277 {
278 localizedConverters.remove(clazz);
279 }
280 }
281
282 /***
283 * Remove all instances registered {@link Converter} by class of converter
284 *
285 * @param clazz Class of converter to remove. Removes all subclasses as well.
286 */
287 public void deregisterByConverterClass(Class clazz)
288 {
289 List keys = new ArrayList();
290 for(Iterator i = converters.keySet().iterator(); i.hasNext(); )
291 {
292 Object key = i.next();
293 Converter converter = (Converter)converters.get(key);
294 if(converter.getClass().isAssignableFrom(clazz))
295 {
296 keys.add(key);
297 }
298 }
299 for(Iterator i = keys.iterator(); i.hasNext(); )
300 {
301 Object key = i.next();
302 converters.remove(key);
303 log.info("removed registration for " + key);
304 }
305
306 keys.clear();
307 for(Iterator i = localizedConverters.keySet().iterator(); i.hasNext(); )
308 {
309 Object key = i.next();
310 LocaleConverter converter = (LocaleConverter)localizedConverters.get(key);
311 if(converter.getClass().isAssignableFrom(clazz))
312 {
313 keys.add(key);
314 }
315 }
316 for(Iterator i = keys.iterator(); i.hasNext(); )
317 {
318 Object key = i.next();
319 localizedConverters.remove(key);
320 log.info("removed registration for " + key);
321 }
322 }
323
324 /***
325 * Remove the instances of registered {@link LocaleConverter}
326 *
327 * @param converter instance of converter to remove
328 */
329 public void deregister(LocaleConverter converter)
330 {
331 List keys = new ArrayList();
332 for(Iterator i = localizedConverters.keySet().iterator(); i.hasNext(); )
333 {
334 Object key = i.next();
335 LocaleConverter _converter = (LocaleConverter)localizedConverters.get(key);
336 if(converter == _converter)
337 {
338 keys.add(key);
339 }
340 }
341 for(Iterator i = keys.iterator(); i.hasNext(); )
342 {
343 Object key = i.next();
344 localizedConverters.remove(key);
345 log.info("removed registration for " + key);
346 }
347 }
348
349 /***
350 * Remove any registered {@link Formatter} for the specified key
351 *
352 * @param key key for which to remove a registered Formatter
353 */
354 public void deregister(String key)
355 {
356 key = getLocKey(key);
357 converters.remove(key);
358 }
359
360 /***
361 * deregister a global formatter with the given key and locale
362 * @param key the key of the formatter
363 * @param locale the locale
364 */
365 public void deregister(String key, Locale locale)
366 {
367 key = getLocKey(key, locale);
368 localizedConverters.remove(key);
369 }
370
371 /***
372 * clear all registrations
373 */
374 public void clear()
375 {
376 converters.clear();
377 localizedConverters.clear();
378 }
379
380 /***
381 * lookup a globally registered formatter
382 * @param key key of formatter
383 * @return Formatter instance of Formatter that was registered with the
384 * specified key or null if not found
385 */
386 public Formatter lookup(String key)
387 {
388 key = getLocKey(key);
389 return (Formatter)converters.get(key);
390 }
391
392 /***
393 * Look up and return any registered {@link Converter} for the specified
394 * destination class. If there is no registered Converter, return an
395 * instance of NoopConverter if returnNoopConverterWhenNotFound == true or
396 * else <code>null</code>.
397 *
398 * @param clazz Class for which to return a registered Converter
399 * @return Converter converter
400 */
401 public Converter lookup(Class clazz)
402 throws NoSuchMethodException,
403 IllegalArgumentException,
404 InstantiationException,
405 IllegalAccessException,
406 InvocationTargetException
407 {
408 return lookup(clazz, null);
409 }
410
411 /***
412 * Look up and return any registered {@link Converter} for the specified
413 * destination class and locale. If there is no registered Converter, return an
414 * instance of NoopConverter if returnNoopConverterWhenNotFound == true or
415 * else <code>null</code>.
416 *
417 * Precedence: if a locale is given the first search is for a converter that was
418 * registered for the given type and locale. If it is not found, the second
419 * search is for any converter of the type LocaleConverter that was registered
420 * for the given type. If it is found, a new instance will be created for the
421 * given locale, the pattern will be copied if possible and the newly
422 * instantiated converter will be registered for the given type and locale
423 * (and thus will be found at the first search next time). If it is not found,
424 * the search is the same as when no locale was given (locale == null):
425 * the 'normal', not localized registry will be searched for an entry with
426 * the given type. If still no Converter is found after this, and
427 * returnNoopConverterWhenNotFound is true an instance of NoopConverter is returned,
428 * so that clients allways get a valid converter. If returnNoopConverterWhenNotFound
429 * is false, null will be returned.
430 *
431 * @param clazz Class for which to return a registered Converter
432 * @param locale The Locale
433 * @return Converter converter
434 */
435 public Converter lookup(Class clazz, Locale locale)
436 throws NoSuchMethodException,
437 IllegalArgumentException,
438 InstantiationException,
439 IllegalAccessException,
440 InvocationTargetException
441 {
442 Converter converter = null;
443 if(locale != null)
444 {
445 String lockey = getLocKey(clazz, locale);
446
447
448 converter = (LocaleConverter)localizedConverters.get(lockey);
449 if(converter == null)
450 {
451 LocaleConverter _converter = (LocaleConverter)localizedConverters.get(clazz);
452
453 if(_converter != null)
454 {
455 Class cls = _converter.getClass();
456 Class[] paramTypes = new Class[]{ Locale.class };
457 Constructor constructor = cls.getConstructor(paramTypes);
458 Object[] initArgs = new Object[]{ locale };
459
460 LocaleConverter _newConverter =
461 (LocaleConverter)constructor.newInstance(initArgs);
462
463
464 if((_converter instanceof BaseLocaleConverter) &&
465 (_newConverter instanceof BaseLocaleConverter) )
466 {
467 String pattern = ((BaseLocaleConverter)_converter).getPattern();
468 ((BaseLocaleConverter)_newConverter).setPattern(pattern);
469 }
470
471
472
473 localizedConverters.put(lockey, _newConverter);
474 converter = _newConverter;
475 }
476 }
477 }
478
479 if(converter == null)
480 {
481 converter = (Converter)converters.get(clazz);
482 }
483
484 if(converter == null && returnNoopConverterWhenNotFound)
485 {
486 converter = noopConverter;
487 }
488
489 return converter;
490 }
491
492 /***
493 * Look up and return any registered {@link Formatter} for the specified
494 * destination key and locale; if there is no registered Formatter, return
495 * <code>null</code>.
496 *
497 * Precedence: if a locale is given the first search is for a formatter that was
498 * registered for the given type and locale. If it is not found, the second
499 * search is for any formatter of the type LocaleFormatter that was registered
500 * for the given key. If it is found, a new instance will be created for the
501 * given locale and the newly instantiated formatter will be registered for
502 * the given key and locale
503 * (and thus will be found at the first search next time). If it is not found,
504 * the search is the same as when no locale was given (locale == null):
505 * the 'normal', not localized registry will be searched for an entry with
506 * the given key. If this is not found either, null will be returned.
507 *
508 * @param key key that the formatter was registered with
509 * @param locale the Locale
510 */
511 public Formatter lookup(String key, Locale locale)
512 throws NoSuchMethodException,
513 IllegalArgumentException,
514 InstantiationException,
515 IllegalAccessException,
516 InvocationTargetException
517 {
518 Formatter formatter = null;
519 if(locale != null)
520 {
521 String lockey = getLocKey(key, locale);
522
523
524 formatter = (LocaleFormatter)localizedConverters.get(lockey);
525 if(formatter == null)
526 {
527 String globLocKey = getLocKey(key);
528 LocaleFormatter _formatter = (LocaleFormatter)localizedConverters.get(globLocKey);
529
530 if(_formatter != null)
531 {
532 Class cls = _formatter.getClass();
533 LocaleFormatter _newFormatter = (LocaleFormatter)cls.newInstance();
534 _newFormatter.setLocale(locale);
535
536
537 localizedConverters.put(key, _newFormatter);
538 formatter = _newFormatter;
539 }
540 }
541 }
542
543 if(formatter == null)
544 {
545 formatter = (Formatter)converters.get(getLocKey(key));
546 }
547
548 return formatter;
549 }
550
551
552
553
554
555
556
557 private String getLocKey(Class clazz, Locale locale)
558 {
559 return clazz.getName() + "|" +
560 ((locale.getCountry() != null) ? locale.getCountry() : "_") + "|" +
561 ((locale.getLanguage() != null) ? locale.getLanguage() : "_") + "|" +
562 ((locale.getVariant() != null) ? locale.getVariant() : "_");
563 }
564
565
566
567
568
569
570
571 private String getLocKey(String key, Locale locale)
572 {
573 return "_fmt" + key + "|" +
574 ((locale.getCountry() != null) ? locale.getCountry() : "_") + "|" +
575 ((locale.getLanguage() != null) ? locale.getLanguage() : "_") + "|" +
576 ((locale.getVariant() != null) ? locale.getVariant() : "_");
577 }
578
579
580
581
582
583
584 private String getLocKey(String key)
585 {
586 return "_fmt" + key;
587 }
588
589 /***
590 * Whether to return a noopConverter as a fallback.
591 * @return boolean when true, a noopConverter is returned as a fallback. When
592 * false, null is returned
593 */
594 public static boolean isReturnNoopConverterWhenNotFound()
595 {
596 return returnNoopConverterWhenNotFound;
597 }
598
599 /***
600 * Whether to return a noopConverter as a fallback.
601 * false, null is returned
602 * @param b when true, a noopConverter is returned as a fallback. When
603 * false, null is returned
604 */
605 public static void setReturnNoopConverterWhenNotFound(boolean b)
606 {
607 returnNoopConverterWhenNotFound = b;
608 }
609
610 }