View Javadoc

1   /*
2    * $Id: ConverterRegistry.java,v 1.4 2004/04/04 18:23:19 eelco12 Exp $
3    * $Revision: 1.4 $
4    * $Date: 2004/04/04 18:23:19 $
5    *
6    * ====================================================================
7    * Copyright (c) 2003, Open Edge B.V.
8    * All rights reserved.
9    * Redistribution and use in source and binary forms, with or without 
10   * modification, are permitted provided that the following conditions are met:
11   * Redistributions of source code must retain the above copyright notice, 
12   * this list of conditions and the following disclaimer. Redistributions 
13   * in binary form must reproduce the above copyright notice, this list of 
14   * conditions and the following disclaimer in the documentation and/or other 
15   * materials provided with the distribution. Neither the name of OpenEdge B.V. 
16   * nor the names of its contributors may be used to endorse or promote products 
17   * derived from this software without specific prior written permission.
18   * 
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
20   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
21   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
22   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
23   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
24   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
25   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
26   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
27   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
28   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
29   * THE POSSIBILITY OF SUCH DAMAGE.
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 	 * hidden constructor
116 	 */
117 	private ConverterRegistry()
118 	{
119 		// no nada
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 	 * register the default converters
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 			// first try registration for specific locale
448 			converter = (LocaleConverter)localizedConverters.get(lockey);
449 			if(converter == null) // not found, try generic localized registration
450 			{
451 				LocaleConverter _converter = (LocaleConverter)localizedConverters.get(clazz);
452 				// if found, instantiate a localized one and store for next use
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 					// create new instance for this locale
460 					LocaleConverter _newConverter = 
461 						(LocaleConverter)constructor.newInstance(initArgs); 
462 					
463 					// try to copy the pattern
464 					if((_converter instanceof BaseLocaleConverter) &&
465 						(_newConverter instanceof BaseLocaleConverter) )
466 					{
467 						String pattern = ((BaseLocaleConverter)_converter).getPattern();
468 						((BaseLocaleConverter)_newConverter).setPattern(pattern);
469 					}
470 					// else: too bad, but it's probably not a problem
471 
472 					// register the new instance for this locale
473 					localizedConverters.put(lockey, _newConverter);
474 					converter = _newConverter;
475 				}
476 			}			
477 		}
478 		// else // get without locale right away
479 		if(converter == null) // (still) not found, try generic non-localized registration
480 		{
481 			converter = (Converter)converters.get(clazz);
482 		}
483 		
484 		if(converter == null && returnNoopConverterWhenNotFound) // STILL not found; return no-op 
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 			// first try registration for specific locale
524 			formatter = (LocaleFormatter)localizedConverters.get(lockey);
525 			if(formatter == null) // not found, try generic localized registration
526 			{
527 				String globLocKey = getLocKey(key);
528 				LocaleFormatter _formatter = (LocaleFormatter)localizedConverters.get(globLocKey);
529 				// if found, instantiate a localized one and store for next use
530 				if(_formatter != null)
531 				{
532 					Class cls = _formatter.getClass();
533 					LocaleFormatter _newFormatter = (LocaleFormatter)cls.newInstance();
534 					_newFormatter.setLocale(locale);
535 
536 					// register the new instance for this locale
537 					localizedConverters.put(key, _newFormatter);
538 					formatter = _newFormatter;
539 				}
540 			}			
541 		}
542 		// else // get without locale right away
543 		if(formatter == null) // (still) not found, try generic non-localized registration
544 		{
545 			formatter = (Formatter)converters.get(getLocKey(key));
546 		}
547 		
548 		return formatter;
549 	}
550 	
551 	/*
552 	 * get key for localized converters
553 	 * @param clazz class
554 	 * @param locale locale
555 	 * @return String key
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 	 * get key for localized formatters
567 	 * @param key key
568 	 * @param locale locale
569 	 * @return String key
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 	 * get key for localized formatters
581 	 * @param key key
582 	 * @return String key
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 }