View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.xbean.propertyeditor;
18  
19  import static org.apache.xbean.recipe.RecipeHelper.getTypeParameters;
20  import static org.apache.xbean.recipe.RecipeHelper.*;
21  import org.apache.xbean.recipe.RecipeHelper;
22  
23  import java.beans.PropertyEditor;
24  import java.beans.PropertyEditorManager;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.Map;
28  import java.util.Collection;
29  import java.util.SortedSet;
30  import java.util.Set;
31  import java.util.TreeSet;
32  import java.util.LinkedHashSet;
33  import java.util.ArrayList;
34  import java.util.SortedMap;
35  import java.util.TreeMap;
36  import java.util.LinkedHashMap;
37  import java.util.Map.Entry;
38  import java.util.concurrent.ConcurrentMap;
39  import java.util.concurrent.ConcurrentHashMap;
40  import java.lang.reflect.Type;
41  
42  /**
43   * The property editor manager.  This orchestrates Geronimo usage of
44   * property editors, allowing additional search paths to be added and
45   * specific editors to be registered.
46   *
47   * @version $Rev: 6687 $
48   */
49  public class PropertyEditors {
50      private static final Map<Class, Converter> registry = Collections.synchronizedMap(new ReferenceIdentityMap());
51      private static final Map<Class, Class> PRIMITIVE_TO_WRAPPER;
52      private static final Map<Class, Class> WRAPPER_TO_PRIMITIVE;
53      private static boolean registerWithVM;
54  
55      /**
56       * Register all of the built in converters
57       */
58      static {
59          Map<Class, Class> map = new HashMap<Class, Class>();
60          map.put(boolean.class, Boolean.class);
61          map.put(char.class, Character.class);
62          map.put(byte.class, Byte.class);
63          map.put(short.class, Short.class);
64          map.put(int.class, Integer.class);
65          map.put(long.class, Long.class);
66          map.put(float.class, Float.class);
67          map.put(double.class, Double.class);
68          PRIMITIVE_TO_WRAPPER = Collections.unmodifiableMap(map);
69  
70  
71          map = new HashMap<Class, Class>();
72          map.put(Boolean.class, boolean.class);
73          map.put(Character.class, char.class);
74          map.put(Byte.class, byte.class);
75          map.put(Short.class, short.class);
76          map.put(Integer.class, int.class);
77          map.put(Long.class, long.class);
78          map.put(Float.class, float.class);
79          map.put(Double.class, double.class);
80          WRAPPER_TO_PRIMITIVE = Collections.unmodifiableMap(map);
81  
82          // Explicitly register the types
83          registerConverter(new ArrayListEditor());
84          registerConverter(new BigDecimalEditor());
85          registerConverter(new BigIntegerEditor());
86          registerConverter(new BooleanEditor());
87          registerConverter(new ByteEditor());
88          registerConverter(new CharacterEditor());
89          registerConverter(new ClassEditor());
90          registerConverter(new DateEditor());
91          registerConverter(new DoubleEditor());
92          registerConverter(new FileEditor());
93          registerConverter(new FloatEditor());
94          registerConverter(new HashMapEditor());
95          registerConverter(new HashtableEditor());
96          registerConverter(new IdentityHashMapEditor());
97          registerConverter(new Inet4AddressEditor());
98          registerConverter(new Inet6AddressEditor());
99          registerConverter(new InetAddressEditor());
100         registerConverter(new IntegerEditor());
101         registerConverter(new LinkedHashMapEditor());
102         registerConverter(new LinkedHashSetEditor());
103         registerConverter(new LinkedListEditor());
104         registerConverter(new ListEditor());
105         registerConverter(new LongEditor());
106         registerConverter(new MapEditor());
107         registerConverter(new ObjectNameEditor());
108         registerConverter(new PropertiesEditor());
109         registerConverter(new SetEditor());
110         registerConverter(new ShortEditor());
111         registerConverter(new SortedMapEditor());
112         registerConverter(new SortedSetEditor());
113         registerConverter(new StringEditor());
114         registerConverter(new TreeMapEditor());
115         registerConverter(new TreeSetEditor());
116         registerConverter(new URIEditor());
117         registerConverter(new URLEditor());
118         registerConverter(new LoggerConverter());
119         registerConverter(new PatternConverter());
120         registerConverter(new JndiConverter());
121         registerConverter(new VectorEditor());
122         registerConverter(new WeakHashMapEditor());
123 
124         try {
125             registerConverter(new Log4jConverter());
126         } catch (Throwable e) {
127         }
128 
129         try {
130             registerConverter(new CommonsLoggingConverter());
131         } catch (Throwable e) {
132         }
133     }
134 
135     /**
136      * Are converters registered with the VM PropertyEditorManager.  By default
137      * converters are not registered with the VM as this creates problems for
138      * IDE and Spring because they rely in their specific converters being
139      * registered to function properly. 
140      */
141     public static boolean isRegisterWithVM() {
142         return registerWithVM;
143     }
144 
145     /**
146      * Sets if converters registered with the VM PropertyEditorManager.
147      * If the new value is true, all currently registered converters are
148      * immediately registered with the VM.
149      */
150     public static void setRegisterWithVM(boolean registerWithVM) {
151         if (PropertyEditors.registerWithVM != registerWithVM) {
152             PropertyEditors.registerWithVM = registerWithVM;
153 
154             // register all converters with the VM
155             if (registerWithVM) {
156                 for (Entry<Class, Converter> entry : registry.entrySet()) {
157                     Class type = entry.getKey();
158                     Converter converter = entry.getValue();
159                     PropertyEditorManager.registerEditor(type, converter.getClass());
160                 }
161             }
162         }
163     }
164 
165     public static void registerConverter(Converter converter) {
166         if (converter == null) throw new NullPointerException("editor is null");
167         Class type = converter.getType();
168         registry.put(type, converter);
169         if (registerWithVM) {
170             PropertyEditorManager.registerEditor(type, converter.getClass());
171         }
172 
173         if (PRIMITIVE_TO_WRAPPER.containsKey(type)) {
174             Class wrapperType = PRIMITIVE_TO_WRAPPER.get(type);
175             registry.put(wrapperType, converter);
176             if (registerWithVM) {
177                 PropertyEditorManager.registerEditor(wrapperType, converter.getClass());
178             }
179         } else if (WRAPPER_TO_PRIMITIVE.containsKey(type)) {
180             Class primitiveType = WRAPPER_TO_PRIMITIVE.get(type);
181             registry.put(primitiveType, converter);
182             if (registerWithVM) {
183                 PropertyEditorManager.registerEditor(primitiveType, converter.getClass());
184             }
185         }
186     }
187 
188     public static boolean canConvert(String type, ClassLoader classLoader) {
189         if (type == null) throw new NullPointerException("type is null");
190         if (classLoader == null) throw new NullPointerException("classLoader is null");
191 
192         // load using the ClassLoading utility, which also manages arrays and primitive classes.
193         Class typeClass;
194         try {
195             typeClass = Class.forName(type, true, classLoader);
196         } catch (ClassNotFoundException e) {
197             throw new PropertyEditorException("Type class could not be found: " + type);
198         }
199 
200         return canConvert(typeClass);
201 
202     }
203 
204     public static boolean canConvert(Class type) {
205         PropertyEditor editor = findConverterOrEditor(type);
206 
207         return editor != null;
208     }
209 
210     private static PropertyEditor findConverterOrEditor(Type type){
211         Converter converter = findConverter(type);
212         if (converter != null) {
213             return converter;
214         }
215 
216         // fall back to a property editor
217         PropertyEditor editor = findEditor(type);
218         if (editor != null) {
219             return editor;
220         }
221 
222         converter = findBuiltinConverter(type);
223         if (converter != null) {
224             return converter;
225         }
226 
227         return null;
228     }
229 
230     public static String toString(Object value) throws PropertyEditorException {
231         if (value == null) throw new NullPointerException("value is null");
232 
233         // get an editor for this type
234         Class type = value.getClass();
235 
236         PropertyEditor editor = findConverterOrEditor(type);
237 
238         if (editor instanceof Converter) {
239             Converter converter = (Converter) editor;
240             return converter.toString(value);
241         }
242 
243         if (editor == null) {
244             throw new PropertyEditorException("Unable to find PropertyEditor for " + type.getSimpleName());
245         }
246 
247         // create the string value
248         editor.setValue(value);
249         String textValue;
250         try {
251             textValue = editor.getAsText();
252         } catch (Exception e) {
253             throw new PropertyEditorException("Error while converting a \"" + type.getSimpleName() + "\" to text " +
254                     " using the property editor " + editor.getClass().getSimpleName(), e);
255         }
256         return textValue;
257     }
258 
259     public static Object getValue(String type, String value, ClassLoader classLoader) throws PropertyEditorException {
260         if (type == null) throw new NullPointerException("type is null");
261         if (value == null) throw new NullPointerException("value is null");
262         if (classLoader == null) throw new NullPointerException("classLoader is null");
263 
264         // load using the ClassLoading utility, which also manages arrays and primitive classes.
265         Class typeClass;
266         try {
267             typeClass = Class.forName(type, true, classLoader);
268         } catch (ClassNotFoundException e) {
269             throw new PropertyEditorException("Type class could not be found: " + type);
270         }
271 
272         return getValue(typeClass, value);
273 
274     }
275 
276     public static Object getValue(Type type, String value) throws PropertyEditorException {
277         if (type == null) throw new NullPointerException("type is null");
278         if (value == null) throw new NullPointerException("value is null");
279 
280         PropertyEditor editor = findConverterOrEditor(type);
281 
282         if (editor instanceof Converter) {
283             Converter converter = (Converter) editor;
284             return converter.toObject(value);
285         }
286 
287         Class clazz = toClass(type);
288 
289         if (editor == null) {
290             throw new PropertyEditorException("Unable to find PropertyEditor for " + clazz.getSimpleName());
291         }
292 
293         editor.setAsText(value);
294         Object objectValue;
295         try {
296             objectValue = editor.getValue();
297         } catch (Exception e) {
298             throw new PropertyEditorException("Error while converting \"" + value + "\" to a " + clazz.getSimpleName() +
299                     " using the property editor " + editor.getClass().getSimpleName(), e);
300         }
301         return objectValue;
302     }
303 
304     private static Converter findBuiltinConverter(Type type) {
305         if (type == null) throw new NullPointerException("type is null");
306 
307         Class clazz = toClass(type);
308 
309         if (Enum.class.isAssignableFrom(clazz)){
310             return new EnumConverter(clazz);
311         }
312 
313         return null;       
314     }
315 
316     private static Converter findConverter(Type type) {
317         if (type == null) throw new NullPointerException("type is null");
318 
319         Class clazz = toClass(type);
320 
321 
322 
323         // it's possible this was a request for an array class.  We might not
324         // recognize the array type directly, but the component type might be
325         // resolvable
326         if (clazz.isArray() && !clazz.getComponentType().isArray()) {
327             // do a recursive lookup on the base type
328             PropertyEditor editor = findConverterOrEditor(clazz.getComponentType());
329             // if we found a suitable editor for the base component type,
330             // wrapper this in an array adaptor for real use
331             if (editor != null) {
332                 return new ArrayConverter(clazz, editor);
333             } else {
334                 return null;
335             }
336         }
337 
338         if (Collection.class.isAssignableFrom(clazz)){
339             Type[] types = getTypeParameters(Collection.class, type);
340 
341             Type componentType = String.class;
342             if (types != null && types.length == 1 && types[0] instanceof Class) {
343                 componentType = types[0];
344             }
345 
346             PropertyEditor editor = findConverterOrEditor(componentType);
347 
348             if (editor != null){
349                 if (RecipeHelper.hasDefaultConstructor(clazz)) {
350                     return new GenericCollectionConverter(clazz, editor);
351                 } else if (SortedSet.class.isAssignableFrom(clazz)) {
352                     return new GenericCollectionConverter(TreeSet.class, editor);
353                 } else if (Set.class.isAssignableFrom(clazz)) {
354                     return new GenericCollectionConverter(LinkedHashSet.class, editor);
355                 } else {
356                     return new GenericCollectionConverter(ArrayList.class, editor);
357                 }
358             }
359 
360             return null;
361         }
362 
363         if (Map.class.isAssignableFrom(clazz)){
364             Type[] types = getTypeParameters(Map.class, type);
365 
366             Type keyType = String.class;
367             Type valueType = String.class;
368             if (types != null && types.length == 2 && types[0] instanceof Class && types[1] instanceof Class) {
369                 keyType = types[0];
370                 valueType = types[1];
371             }
372 
373             PropertyEditor keyConverter = findConverterOrEditor(keyType);
374             PropertyEditor valueConverter = findConverterOrEditor(valueType);
375 
376             if (keyConverter != null && valueConverter != null){
377                 if (RecipeHelper.hasDefaultConstructor(clazz)) {
378                     return new GenericMapConverter(clazz, keyConverter, valueConverter);
379                 } else if (SortedMap.class.isAssignableFrom(clazz)) {
380                     return new GenericMapConverter(TreeMap.class, keyConverter, valueConverter);
381                 } else if (ConcurrentMap.class.isAssignableFrom(clazz)) {
382                     return new GenericMapConverter(ConcurrentHashMap.class, keyConverter, valueConverter);
383                 } else {
384                     return new GenericMapConverter(LinkedHashMap.class, keyConverter, valueConverter);
385                 }
386             }
387 
388             return null;
389         }
390 
391         Converter converter = registry.get(clazz);
392 
393         // we're outta here if we got one.
394         if (converter != null) {
395             return converter;
396         }
397 
398         Class[] declaredClasses = clazz.getDeclaredClasses();
399         for (Class declaredClass : declaredClasses) {
400             if (Converter.class.isAssignableFrom(declaredClass)) {
401                 try {
402                     converter = (Converter) declaredClass.newInstance();
403                     registerConverter(converter);
404 
405                     // try to get the converter from the registry... the converter
406                     // created above may have been for another class
407                     converter = registry.get(clazz);
408                     if (converter != null) {
409                         return converter;
410                     }
411                 } catch (Exception e) {
412                 }
413 
414             }
415         }
416 
417         // nothing found
418         return null;
419     }
420 
421     /**
422      * Locate a property editor for qiven class of object.
423      *
424      * @param type The target object class of the property.
425      * @return The resolved editor, if any.  Returns null if a suitable editor
426      *         could not be located.
427      */
428     private static PropertyEditor findEditor(Type type) {
429         if (type == null) throw new NullPointerException("type is null");
430 
431         Class clazz = toClass(type);
432 
433         // try to locate this directly from the editor manager first.
434         PropertyEditor editor = PropertyEditorManager.findEditor(clazz);
435 
436         // we're outta here if we got one.
437         if (editor != null) {
438             return editor;
439         }
440 
441 
442         // it's possible this was a request for an array class.  We might not
443         // recognize the array type directly, but the component type might be
444         // resolvable
445         if (clazz.isArray() && !clazz.getComponentType().isArray()) {
446             // do a recursive lookup on the base type
447             editor = findEditor(clazz.getComponentType());
448             // if we found a suitable editor for the base component type,
449             // wrapper this in an array adaptor for real use
450             if (editor != null) {
451                 return new ArrayConverter(clazz, editor);
452             }
453         }
454 
455         // nothing found
456         return null;
457     }
458 }