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 java.util.Collections.unmodifiableMap;
020import static org.apache.xbean.recipe.RecipeHelper.getTypeParameters;
021import static org.apache.xbean.recipe.RecipeHelper.toClass;
022
023import java.beans.PropertyEditor;
024import java.beans.PropertyEditorManager;
025import java.io.Closeable;
026import java.lang.reflect.Type;
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.LinkedHashMap;
030import java.util.LinkedHashSet;
031import java.util.Map;
032import java.util.Set;
033import java.util.SortedMap;
034import java.util.SortedSet;
035import java.util.TreeMap;
036import java.util.TreeSet;
037import java.util.concurrent.ConcurrentHashMap;
038import java.util.concurrent.ConcurrentMap;
039
040import org.apache.xbean.recipe.RecipeHelper;
041
042public class PropertyEditorRegistry implements Closeable {
043    private final ConcurrentMap<Type, Converter> registry = new ConcurrentHashMap<Type, Converter>();
044
045    public PropertyEditorRegistry registerDefaults() {
046        register(new ArrayListEditor());
047        register(new BigDecimalEditor());
048        register(new BigIntegerEditor());
049        register(new BooleanEditor());
050        register(new ByteEditor());
051        register(new CharacterEditor());
052        register(new ClassEditor());
053        register(new DateEditor());
054        register(new DoubleEditor());
055        register(new FileEditor());
056        register(new FloatEditor());
057        register(new HashMapEditor());
058        register(new HashtableEditor());
059        register(new IdentityHashMapEditor());
060        register(new Inet4AddressEditor());
061        register(new Inet6AddressEditor());
062        register(new InetAddressEditor());
063        register(new IntegerEditor());
064        register(new LinkedHashMapEditor());
065        register(new LinkedHashSetEditor());
066        register(new LinkedListEditor());
067        register(new ListEditor());
068        register(new LongEditor());
069        register(new MapEditor());
070        register(new ObjectNameEditor());
071        register(new PropertiesEditor());
072        register(new SetEditor());
073        register(new ShortEditor());
074        register(new SortedMapEditor());
075        register(new SortedSetEditor());
076        register(new StringEditor());
077        register(new TreeMapEditor());
078        register(new TreeSetEditor());
079        register(new URIEditor());
080        register(new URLEditor());
081        register(new LoggerConverter());
082        register(new PatternConverter());
083        register(new JndiConverter());
084        register(new VectorEditor());
085        register(new WeakHashMapEditor());
086
087        return this;
088    }
089
090    /**
091     * @return a read-only view of the converters.
092     */
093    public Map<Type, Converter> getRegistry() {
094        return unmodifiableMap(registry);
095    }
096
097    /**
098     * Register a converter in the registry.
099     *
100     * @param converter the converter to register.
101     * @return the previously existing converter for the corresponding type or null.
102     */
103    public Converter register(final Converter converter) {
104        if (converter == null) {
105            throw new NullPointerException("converter is null");
106        }
107        final Class<?> type = converter.getType();
108        final Converter existing = registry.put(type, converter);
109
110        final Class<?> sibling = Primitives.findSibling(type);
111        if (sibling != null) {
112            registry.put(sibling, converter);
113        }
114        return existing;
115    }
116
117    /**
118     * Unregister a converter.
119     *
120     * @param converter the converter to remove from the registry.
121     * @return the converter if found, or null.
122     */
123    public Converter unregister(final Converter converter) {
124        if (converter == null) {
125            throw new NullPointerException("converter is null");
126        }
127        return registry.remove(converter.getType());
128    }
129
130    public Converter findConverter(final Type type){
131        {
132            final Converter converter = findInternalConverter(type);
133            if (converter != null) {
134                if (!registry.containsKey(converter.getType())) {
135                    register(converter);
136                }
137                return converter;
138            }
139        }
140
141        {
142            final Converter converter = createConverterFromEditor(type);
143            if (converter != null) {
144                register(converter);
145                return converter;
146            }
147        }
148
149        {
150            final Converter converter = findStructuralConverter(type);
151            if (converter != null) {
152                register(converter);
153                return converter;
154            }
155        }
156
157        return null;
158    }
159
160    public String toString(final Object value) throws PropertyEditorException {
161        if (value == null) {
162            throw new NullPointerException("value is null");
163        }
164        final Class type = unwrapClass(value);
165        final Converter converter = findConverter(type);
166        if (converter == null) {
167            throw new PropertyEditorException("Unable to find PropertyEditor for " + type.getSimpleName());
168        }
169        return converter.toString(value);
170    }
171
172    public Object getValue(final String type, final String value, final ClassLoader classLoader) throws PropertyEditorException {
173        if (type == null) {
174            throw new NullPointerException("type is null");
175        }
176        if (value == null) {
177            throw new NullPointerException("value is null");
178        }
179        if (classLoader == null) {
180            throw new NullPointerException("classLoader is null");
181        }
182
183        try {
184            return getValue(Class.forName(type, true, classLoader), value);
185        } catch (final ClassNotFoundException e) {
186            throw new PropertyEditorException("Type class could not be found: " + type);
187        }
188    }
189
190    public Object getValue(final Type type, final String value) throws PropertyEditorException {
191        if (type == null) {
192            throw new NullPointerException("type is null");
193        }
194        if (value == null) {
195            throw new NullPointerException("value is null");
196        }
197
198        final Converter converter = findConverter(type);
199        if (converter != null) {
200            return converter.toObject(value);
201        }
202
203        final Class clazz = toClass(type);
204
205        final Converter structuralConverter = findStructuralConverter(clazz);
206        if (structuralConverter != null) {
207            register(structuralConverter);
208            return structuralConverter.toObject(value);
209        }
210
211        throw new PropertyEditorException("Unable to find PropertyEditor for " + clazz.getSimpleName());
212    }
213
214    protected Class<?> unwrapClass(final Object value) {
215        Class<?> aClass = value.getClass();
216        while (aClass.getName().contains("$$")) {
217            aClass = aClass.getSuperclass();
218            if (aClass == null || aClass == Object.class) {
219                return value.getClass();
220            }
221        }
222        return aClass;
223    }
224
225    protected Converter findStructuralConverter(final Type type) {
226        if (type == null) throw new NullPointerException("type is null");
227
228        final Class clazz = toClass(type);
229
230        if (Enum.class.isAssignableFrom(clazz)){
231            return new EnumConverter(clazz);
232        }
233
234        {
235            final ConstructorConverter editor = ConstructorConverter.editor(clazz);
236            if (editor != null) {
237                return editor;
238            }
239        }
240
241        {
242            final StaticFactoryConverter editor = StaticFactoryConverter.editor(clazz);
243            if (editor != null) {
244                return editor;
245            }
246        }
247
248        return null;
249    }
250
251    protected Converter createConverterFromEditor(final Type type) {
252        if (type == null) {
253            throw new NullPointerException("type is null");
254        }
255
256        final Class<?> clazz = toClass(type);
257
258        // try to locate this directly from the editor manager first.
259        final PropertyEditor editor = PropertyEditorManager.findEditor(clazz);
260
261        // we're outta here if we got one.
262        if (editor != null) {
263            return new PropertyEditorConverter(clazz);
264        }
265
266
267        // it's possible this was a request for an array class.  We might not
268        // recognize the array type directly, but the component type might be
269        // resolvable
270        if (clazz.isArray() && !clazz.getComponentType().isArray()) {
271            // do a recursive lookup on the base type
272            final PropertyEditor arrayEditor = findEditor(clazz.getComponentType());
273            // if we found a suitable editor for the base component type,
274            // wrapper this in an array adaptor for real use
275            if (findEditor(clazz.getComponentType()) != null) {
276                return new ArrayConverter(clazz, arrayEditor);
277            }
278        }
279
280        return null;
281    }
282
283    protected Converter findInternalConverter(final Type type) {
284        if (type == null) {
285            throw new NullPointerException("type is null");
286        }
287
288        final Class clazz = toClass(type);
289
290        // it's possible this was a request for an array class.  We might not
291        // recognize the array type directly, but the component type might be
292        // resolvable
293        if (clazz.isArray() && !clazz.getComponentType().isArray()) {
294            // do a recursive lookup on the base type
295            PropertyEditor editor = findConverter(clazz.getComponentType());
296            // if we found a suitable editor for the base component type,
297            // wrapper this in an array adaptor for real use
298            if (editor != null) {
299                return new ArrayConverter(clazz, editor);
300            }
301            return null;
302        }
303
304        if (Collection.class.isAssignableFrom(clazz)){
305            Type[] types = getTypeParameters(Collection.class, type);
306
307            Type componentType = String.class;
308            if (types != null && types.length == 1 && types[0] instanceof Class) {
309                componentType = types[0];
310            }
311
312            PropertyEditor editor = findConverter(componentType);
313
314            if (editor != null){
315                if (RecipeHelper.hasDefaultConstructor(clazz)) {
316                    return new GenericCollectionConverter(clazz, editor);
317                } else if (SortedSet.class.isAssignableFrom(clazz)) {
318                    return new GenericCollectionConverter(TreeSet.class, editor);
319                } else if (Set.class.isAssignableFrom(clazz)) {
320                    return new GenericCollectionConverter(LinkedHashSet.class, editor);
321                }
322                return new GenericCollectionConverter(ArrayList.class, editor);
323            }
324
325            return null;
326        }
327
328        if (Map.class.isAssignableFrom(clazz)){
329            Type[] types = getTypeParameters(Map.class, type);
330
331            Type keyType = String.class;
332            Type valueType = String.class;
333            if (types != null && types.length == 2 && types[0] instanceof Class && types[1] instanceof Class) {
334                keyType = types[0];
335                valueType = types[1];
336            }
337
338            final Converter keyConverter = findConverter(keyType);
339            final Converter valueConverter = findConverter(valueType);
340
341            if (keyConverter != null && valueConverter != null){
342                if (RecipeHelper.hasDefaultConstructor(clazz)) {
343                    return new GenericMapConverter(clazz, keyConverter, valueConverter);
344                } else if (SortedMap.class.isAssignableFrom(clazz)) {
345                    return new GenericMapConverter(TreeMap.class, keyConverter, valueConverter);
346                } else if (ConcurrentMap.class.isAssignableFrom(clazz)) {
347                    return new GenericMapConverter(ConcurrentHashMap.class, keyConverter, valueConverter);
348                }
349                return new GenericMapConverter(LinkedHashMap.class, keyConverter, valueConverter);
350            }
351
352            return null;
353        }
354
355        Converter converter = registry.get(clazz);
356
357        // we're outta here if we got one.
358        if (converter != null) {
359            return converter;
360        }
361
362        final Class[] declaredClasses = clazz.getDeclaredClasses();
363        for (final Class<?> declaredClass : declaredClasses) {
364            if (Converter.class.isAssignableFrom(declaredClass)) {
365                try {
366                    converter = (Converter) declaredClass.newInstance();
367                    register(converter);
368
369                    // try to get the converter from the registry... the converter
370                    // created above may have been for another class
371                    converter = registry.get(clazz);
372                    if (converter != null) {
373                        return converter;
374                    }
375                } catch (Exception e) {
376                    // no-op
377                }
378
379            }
380        }
381
382        // nothing found
383        return null;
384    }
385
386    /**
387     * Locate a property editor for qiven class of object.
388     *
389     * @param type The target object class of the property.
390     * @return The resolved editor, if any.  Returns null if a suitable editor
391     *         could not be located.
392     */
393    protected PropertyEditor findEditor(final Type type) {
394        if (type == null) throw new NullPointerException("type is null");
395
396        Class clazz = toClass(type);
397
398        // try to locate this directly from the editor manager first.
399        PropertyEditor editor = PropertyEditorManager.findEditor(clazz);
400
401        // we're outta here if we got one.
402        if (editor != null) {
403            return editor;
404        }
405
406
407        // it's possible this was a request for an array class.  We might not
408        // recognize the array type directly, but the component type might be
409        // resolvable
410        if (clazz.isArray() && !clazz.getComponentType().isArray()) {
411            // do a recursive lookup on the base type
412            editor = findEditor(clazz.getComponentType());
413            // if we found a suitable editor for the base component type,
414            // wrapper this in an array adaptor for real use
415            if (editor != null) {
416                return new ArrayConverter(clazz, editor);
417            }
418        }
419
420        // nothing found
421        return null;
422    }
423
424    /**
425     * Release closeable converters.
426     */
427    public void close() {
428        for (final Converter converter : registry.values()) {
429            if (Closeable.class.isInstance(converter)) {
430                Closeable.class.cast(converter);
431            }
432        }
433        registry.clear();
434    }
435}