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 */
017package org.apache.xbean.propertyeditor;
018
019import static org.apache.xbean.recipe.RecipeHelper.getTypeParameters;
020import static org.apache.xbean.recipe.RecipeHelper.*;
021import org.apache.xbean.recipe.RecipeHelper;
022
023import java.beans.PropertyEditor;
024import java.beans.PropertyEditorManager;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.Map;
028import java.util.Collection;
029import java.util.SortedSet;
030import java.util.Set;
031import java.util.TreeSet;
032import java.util.LinkedHashSet;
033import java.util.ArrayList;
034import java.util.SortedMap;
035import java.util.TreeMap;
036import java.util.LinkedHashMap;
037import java.util.Map.Entry;
038import java.util.concurrent.ConcurrentMap;
039import java.util.concurrent.ConcurrentHashMap;
040import 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 */
049public 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}