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}