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 }