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 }