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 }