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 }