001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.xbean.propertyeditor;
018    
019    import static org.apache.xbean.recipe.RecipeHelper.getTypeParameters;
020    import static org.apache.xbean.recipe.RecipeHelper.*;
021    import org.apache.xbean.recipe.RecipeHelper;
022    
023    import java.beans.PropertyEditor;
024    import java.beans.PropertyEditorManager;
025    import java.util.Collections;
026    import java.util.HashMap;
027    import java.util.Map;
028    import java.util.Collection;
029    import java.util.SortedSet;
030    import java.util.Set;
031    import java.util.TreeSet;
032    import java.util.LinkedHashSet;
033    import java.util.ArrayList;
034    import java.util.SortedMap;
035    import java.util.TreeMap;
036    import java.util.LinkedHashMap;
037    import java.util.Map.Entry;
038    import java.util.concurrent.ConcurrentMap;
039    import java.util.concurrent.ConcurrentHashMap;
040    import java.lang.reflect.Type;
041    
042    /**
043     * The property editor manager.  This orchestrates Geronimo usage of
044     * property editors, allowing additional search paths to be added and
045     * specific editors to be registered.
046     *
047     * @version $Rev: 6687 $
048     */
049    public class PropertyEditors {
050        private static final Map<Class, Converter> registry = Collections.synchronizedMap(new ReferenceIdentityMap());
051        private static final Map<Class, Class> PRIMITIVE_TO_WRAPPER;
052        private static final Map<Class, Class> WRAPPER_TO_PRIMITIVE;
053        private static boolean registerWithVM;
054    
055        /**
056         * Register all of the built in converters
057         */
058        static {
059            Map<Class, Class> map = new HashMap<Class, Class>();
060            map.put(boolean.class, Boolean.class);
061            map.put(char.class, Character.class);
062            map.put(byte.class, Byte.class);
063            map.put(short.class, Short.class);
064            map.put(int.class, Integer.class);
065            map.put(long.class, Long.class);
066            map.put(float.class, Float.class);
067            map.put(double.class, Double.class);
068            PRIMITIVE_TO_WRAPPER = Collections.unmodifiableMap(map);
069    
070    
071            map = new HashMap<Class, Class>();
072            map.put(Boolean.class, boolean.class);
073            map.put(Character.class, char.class);
074            map.put(Byte.class, byte.class);
075            map.put(Short.class, short.class);
076            map.put(Integer.class, int.class);
077            map.put(Long.class, long.class);
078            map.put(Float.class, float.class);
079            map.put(Double.class, double.class);
080            WRAPPER_TO_PRIMITIVE = Collections.unmodifiableMap(map);
081    
082            // Explicitly register the types
083            registerConverter(new ArrayListEditor());
084            registerConverter(new BigDecimalEditor());
085            registerConverter(new BigIntegerEditor());
086            registerConverter(new BooleanEditor());
087            registerConverter(new ByteEditor());
088            registerConverter(new CharacterEditor());
089            registerConverter(new ClassEditor());
090            registerConverter(new DateEditor());
091            registerConverter(new DoubleEditor());
092            registerConverter(new FileEditor());
093            registerConverter(new FloatEditor());
094            registerConverter(new HashMapEditor());
095            registerConverter(new HashtableEditor());
096            registerConverter(new IdentityHashMapEditor());
097            registerConverter(new Inet4AddressEditor());
098            registerConverter(new Inet6AddressEditor());
099            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    }