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.concurrent.ConcurrentMap;
038    import java.util.concurrent.ConcurrentHashMap;
039    import java.lang.reflect.Type;
040    
041    /**
042     * The property editor manager.  This orchestrates Geronimo usage of
043     * property editors, allowing additional search paths to be added and
044     * specific editors to be registered.
045     *
046     * @version $Rev: 6687 $
047     */
048    public class PropertyEditors {
049        private static final Map registry = Collections.synchronizedMap(new ReferenceIdentityMap());
050        private static final Map PRIMITIVE_TO_WRAPPER;
051        private static final Map WRAPPER_TO_PRIMITIVE;
052    
053        /**
054         * Register all of the built in converters
055         */
056        static {
057            Map map = new HashMap();
058            map.put(boolean.class, Boolean.class);
059            map.put(char.class, Character.class);
060            map.put(byte.class, Byte.class);
061            map.put(short.class, Short.class);
062            map.put(int.class, Integer.class);
063            map.put(long.class, Long.class);
064            map.put(float.class, Float.class);
065            map.put(double.class, Double.class);
066            PRIMITIVE_TO_WRAPPER = Collections.unmodifiableMap(map);
067    
068    
069            map = new HashMap();
070            map.put(Boolean.class, boolean.class);
071            map.put(Character.class, char.class);
072            map.put(Byte.class, byte.class);
073            map.put(Short.class, short.class);
074            map.put(Integer.class, int.class);
075            map.put(Long.class, long.class);
076            map.put(Float.class, float.class);
077            map.put(Double.class, double.class);
078            WRAPPER_TO_PRIMITIVE = Collections.unmodifiableMap(map);
079    
080            // Explicitly register the types
081            registerConverter(new ArrayListEditor());
082            registerConverter(new BigDecimalEditor());
083            registerConverter(new BigIntegerEditor());
084            registerConverter(new BooleanEditor());
085            registerConverter(new ByteEditor());
086            registerConverter(new CharacterEditor());
087            registerConverter(new ClassEditor());
088            registerConverter(new DateEditor());
089            registerConverter(new DoubleEditor());
090            registerConverter(new FileEditor());
091            registerConverter(new FloatEditor());
092            registerConverter(new HashMapEditor());
093            registerConverter(new HashtableEditor());
094            registerConverter(new IdentityHashMapEditor());
095            registerConverter(new Inet4AddressEditor());
096            registerConverter(new Inet6AddressEditor());
097            registerConverter(new InetAddressEditor());
098            registerConverter(new IntegerEditor());
099            registerConverter(new LinkedHashMapEditor());
100            registerConverter(new LinkedHashSetEditor());
101            registerConverter(new LinkedListEditor());
102            registerConverter(new ListEditor());
103            registerConverter(new LongEditor());
104            registerConverter(new MapEditor());
105            registerConverter(new ObjectNameEditor());
106            registerConverter(new PropertiesEditor());
107            registerConverter(new SetEditor());
108            registerConverter(new ShortEditor());
109            registerConverter(new SortedMapEditor());
110            registerConverter(new SortedSetEditor());
111            registerConverter(new StringEditor());
112            registerConverter(new TreeMapEditor());
113            registerConverter(new TreeSetEditor());
114            registerConverter(new URIEditor());
115            registerConverter(new URLEditor());
116            registerConverter(new LoggerConverter());
117            registerConverter(new PatternConverter());
118            registerConverter(new JndiConverter());
119            registerConverter(new VectorEditor());
120            registerConverter(new WeakHashMapEditor());
121    
122            try {
123                registerConverter(new Log4jConverter());
124            } catch (Throwable e) {
125            }
126    
127            try {
128                registerConverter(new CommonsLoggingConverter());
129            } catch (Throwable e) {
130            }
131        }
132    
133        public static void registerConverter(Converter converter) {
134            if (converter == null) throw new NullPointerException("editor is null");
135            Class type = converter.getType();
136            registry.put(type, converter);
137            PropertyEditorManager.registerEditor(type, converter.getClass());
138    
139            if (PRIMITIVE_TO_WRAPPER.containsKey(type)) {
140                Class wrapperType = (Class) PRIMITIVE_TO_WRAPPER.get(type);
141                registry.put(wrapperType, converter);
142                PropertyEditorManager.registerEditor(wrapperType, converter.getClass());
143            } else if (WRAPPER_TO_PRIMITIVE.containsKey(type)) {
144                Class primitiveType = (Class) WRAPPER_TO_PRIMITIVE.get(type);
145                registry.put(primitiveType, converter);
146                PropertyEditorManager.registerEditor(primitiveType, converter.getClass());
147            }
148        }
149    
150        public static boolean canConvert(String type, ClassLoader classLoader) {
151            if (type == null) throw new NullPointerException("type is null");
152            if (classLoader == null) throw new NullPointerException("classLoader is null");
153    
154            // load using the ClassLoading utility, which also manages arrays and primitive classes.
155            Class typeClass = null;
156            try {
157                typeClass = Class.forName(type, true, classLoader);
158            } catch (ClassNotFoundException e) {
159                throw new PropertyEditorException("Type class could not be found: " + type);
160            }
161    
162            return canConvert(typeClass);
163    
164        }
165    
166        public static boolean canConvert(Class type) {
167            PropertyEditor editor = findConverterOrEditor(type);
168    
169            return editor != null;
170        }
171    
172        private static PropertyEditor findConverterOrEditor(Type type){
173            Converter converter = findConverter(type);
174            if (converter != null) {
175                return converter;
176            }
177    
178            // fall back to a property editor
179            PropertyEditor editor = findEditor(type);
180            if (editor != null) {
181                return editor;
182            }
183    
184            converter = findBuiltinConverter(type);
185            if (converter != null) {
186                return converter;
187            }
188    
189            return null;
190        }
191    
192        public static String toString(Object value) throws PropertyEditorException {
193            if (value == null) throw new NullPointerException("value is null");
194    
195            // get an editor for this type
196            Class type = value.getClass();
197    
198            PropertyEditor editor = findConverterOrEditor(type);
199    
200            if (editor instanceof Converter) {
201                Converter converter = (Converter) editor;
202                return converter.toString(value);
203            }
204    
205            if (editor == null) {
206                throw new PropertyEditorException("Unable to find PropertyEditor for " + type.getSimpleName());
207            }
208    
209            // create the string value
210            editor.setValue(value);
211            String textValue = null;
212            try {
213                textValue = editor.getAsText();
214            } catch (Exception e) {
215                throw new PropertyEditorException("Error while converting a \"" + type.getSimpleName() + "\" to text " +
216                        " using the property editor " + editor.getClass().getSimpleName(), e);
217            }
218            return textValue;
219        }
220    
221        public static Object getValue(String type, String value, ClassLoader classLoader) throws PropertyEditorException {
222            if (type == null) throw new NullPointerException("type is null");
223            if (value == null) throw new NullPointerException("value is null");
224            if (classLoader == null) throw new NullPointerException("classLoader is null");
225    
226            // load using the ClassLoading utility, which also manages arrays and primitive classes.
227            Class typeClass = null;
228            try {
229                typeClass = Class.forName(type, true, classLoader);
230            } catch (ClassNotFoundException e) {
231                throw new PropertyEditorException("Type class could not be found: " + type);
232            }
233    
234            return getValue(typeClass, value);
235    
236        }
237    
238        public static Object getValue(Type type, String value) throws PropertyEditorException {
239            if (type == null) throw new NullPointerException("type is null");
240            if (value == null) throw new NullPointerException("value is null");
241    
242            PropertyEditor editor = findConverterOrEditor(type);
243    
244            if (editor instanceof Converter) {
245                Converter converter = (Converter) editor;
246                return converter.toObject(value);
247            }
248    
249            Class clazz = toClass(type);
250    
251            if (editor == null) {
252                throw new PropertyEditorException("Unable to find PropertyEditor for " + clazz.getSimpleName());
253            }
254    
255            editor.setAsText(value);
256            Object objectValue = null;
257            try {
258                objectValue = editor.getValue();
259            } catch (Exception e) {
260                throw new PropertyEditorException("Error while converting \"" + value + "\" to a " + clazz.getSimpleName() +
261                        " using the property editor " + editor.getClass().getSimpleName(), e);
262            }
263            return objectValue;
264        }
265    
266        private static Converter findBuiltinConverter(Type type) {
267            if (type == null) throw new NullPointerException("type is null");
268    
269            Class clazz = toClass(type);
270    
271            if (Enum.class.isAssignableFrom(clazz)){
272                return new EnumConverter(clazz);
273            }
274    
275            return null;       
276        }
277    
278        private static Converter findConverter(Type type) {
279            if (type == null) throw new NullPointerException("type is null");
280    
281            Class clazz = toClass(type);
282    
283    
284    
285            // it's possible this was a request for an array class.  We might not
286            // recognize the array type directly, but the component type might be
287            // resolvable
288            if (clazz.isArray() && !clazz.getComponentType().isArray()) {
289                // do a recursive lookup on the base type
290                PropertyEditor editor = findConverterOrEditor(clazz.getComponentType());
291                // if we found a suitable editor for the base component type,
292                // wrapper this in an array adaptor for real use
293                if (editor != null) {
294                    return new ArrayConverter(clazz, editor);
295                } else {
296                    return null;
297                }
298            }
299    
300            if (Collection.class.isAssignableFrom(clazz)){
301                Type[] types = getTypeParameters(Collection.class, type);
302    
303                Type componentType = String.class;
304                if (types != null && types.length == 1 && types[0] instanceof Class) {
305                    componentType = types[0];
306                }
307    
308                PropertyEditor editor = findConverterOrEditor(componentType);
309    
310                if (editor != null){
311                    if (RecipeHelper.hasDefaultConstructor(clazz)) {
312                        return new GenericCollectionConverter(clazz, editor);
313                    } else if (SortedSet.class.isAssignableFrom(clazz)) {
314                        return new GenericCollectionConverter(TreeSet.class, editor);
315                    } else if (Set.class.isAssignableFrom(clazz)) {
316                        return new GenericCollectionConverter(LinkedHashSet.class, editor);
317                    } else {
318                        return new GenericCollectionConverter(ArrayList.class, editor);
319                    }
320                }
321    
322                return null;
323            }
324    
325            if (Map.class.isAssignableFrom(clazz)){
326                Type[] types = getTypeParameters(Map.class, type);
327    
328                Type keyType = String.class;
329                Type valueType = String.class;
330                if (types != null && types.length == 2 && types[0] instanceof Class && types[1] instanceof Class) {
331                    keyType = types[0];
332                    valueType = types[1];
333                }
334    
335                PropertyEditor keyConverter = findConverterOrEditor(keyType);
336                PropertyEditor valueConverter = findConverterOrEditor(valueType);
337    
338                if (keyConverter != null && valueConverter != null){
339                    if (RecipeHelper.hasDefaultConstructor(clazz)) {
340                        return new GenericMapConverter(clazz, keyConverter, valueConverter);
341                    } else if (SortedMap.class.isAssignableFrom(clazz)) {
342                        return new GenericMapConverter(TreeMap.class, keyConverter, valueConverter);
343                    } else if (ConcurrentMap.class.isAssignableFrom(clazz)) {
344                        return new GenericMapConverter(ConcurrentHashMap.class, keyConverter, valueConverter);
345                    } else {
346                        return new GenericMapConverter(LinkedHashMap.class, keyConverter, valueConverter);
347                    }
348                }
349    
350                return null;
351            }
352    
353            Converter converter = (Converter) registry.get(type);
354    
355            // we're outta here if we got one.
356            if (converter != null) {
357                return converter;
358            }
359    
360            Class[] declaredClasses = clazz.getDeclaredClasses();
361            for (int i = 0; i < declaredClasses.length; i++) {
362                Class declaredClass = declaredClasses[i];
363                if (Converter.class.isAssignableFrom(declaredClass)) {
364                    try {
365                        converter = (Converter) declaredClass.newInstance();
366                        registerConverter(converter);
367    
368                        // try to get the converter from the registry... the converter
369                        // created above may have been for another class
370                        converter = (Converter) registry.get(clazz);
371                        if (converter != null) {
372                            return converter;
373                        }
374                    } catch (Exception e) {
375                    }
376    
377                }
378            }
379    
380            // nothing found
381            return null;
382        }
383    
384        /**
385         * Locate a property editor for qiven class of object.
386         *
387         * @param type The target object class of the property.
388         * @return The resolved editor, if any.  Returns null if a suitable editor
389         *         could not be located.
390         */
391        private static PropertyEditor findEditor(Type type) {
392            if (type == null) throw new NullPointerException("type is null");
393    
394            Class clazz = toClass(type);
395    
396            // try to locate this directly from the editor manager first.
397            PropertyEditor editor = PropertyEditorManager.findEditor(clazz);
398    
399            // we're outta here if we got one.
400            if (editor != null) {
401                return editor;
402            }
403    
404    
405            // it's possible this was a request for an array class.  We might not
406            // recognize the array type directly, but the component type might be
407            // resolvable
408            if (clazz.isArray() && !clazz.getComponentType().isArray()) {
409                // do a recursive lookup on the base type
410                editor = findEditor(clazz.getComponentType());
411                // if we found a suitable editor for the base component type,
412                // wrapper this in an array adaptor for real use
413                if (editor != null) {
414                    return new ArrayConverter(clazz, editor);
415                }
416            }
417    
418            // nothing found
419            return null;
420        }
421    }