001 package biz.hammurapi.convert; 002 003 import java.lang.reflect.InvocationTargetException; 004 import java.lang.reflect.Method; 005 import java.util.ArrayList; 006 import java.util.Collection; 007 import java.util.Collections; 008 import java.util.HashMap; 009 import java.util.HashSet; 010 import java.util.Iterator; 011 import java.util.List; 012 import java.util.Map; 013 import java.util.Set; 014 015 import biz.hammurapi.config.Context; 016 import biz.hammurapi.config.DomConfigFactory; 017 018 public class ConvertingService { 019 020 private interface ConvertersBucket { 021 022 Object convert(Object source, Context context); 023 } 024 025 /** 026 * Converter which delegates to convert() method. 027 */ 028 public static final Converter CONVERTER = new Converter() { 029 030 public Object convert(Object source, Class targetType, Context context) throws ConversionException { 031 return ConvertingService.convert(source, targetType, context); 032 } 033 034 }; 035 036 /** 037 * Converts source to target type using converters loaded from 038 * META-INF/config/biz.hammurapi.convert.Converter resources. 039 * If no converter is found, this method performs converter discovery through 040 * constructors if target type is a class and through duck conversion if target 041 * type is an interface. 042 * Invocation of this method is equivalent to invocation of 043 * <code>convert(source, targetType, false)</code> 044 * @param source Source object. 045 * @param targetType Target type. 046 * @return Source object converted to target type or null if no converter 047 * is available for source/targetType combination 048 * @throws ConversionException 049 */ 050 public static Object convert(Object source, Class targetType) throws ConversionException { 051 return convert(source, targetType, null, false, null); 052 } 053 054 /** 055 * Converts source to target type using converters loaded from 056 * META-INF/config/biz.hammurapi.convert.Converter resources. 057 * If no converter is found, this method performs converter discovery through 058 * constructors if target type is a class and through duck conversion if target 059 * type is an interface. 060 * Invocation of this method is equivalent to invocation of 061 * <code>convert(source, targetType, false)</code> 062 * @param source Source object. 063 * @param targetType Target type. 064 * @param context Conversion context. 065 * @return Source object converted to target type or null if no converter 066 * is available for source/targetType combination 067 * @throws ConversionException 068 */ 069 public static Object convert(Object source, Class targetType, Context context) throws ConversionException { 070 return convert(source, targetType, context, false, null); 071 } 072 073 /** 074 * Converts source to target type using converters loaded from 075 * META-INF/config/biz.hammurapi.convert.Converter resources. 076 * If no converter is found, this method performs converter discovery through 077 * constructors if target type is a class and through duck conversion if target 078 * type is an interface. 079 * Invocation of this method is equivalent to invocation of 080 * <code>convert(source, targetType, false)</code> 081 * @param source Source object. 082 * @param targetType Target type. 083 * @param classLoader Class loader, source of converters. 084 * @return Source object converted to target type or null if no converter 085 * is available for source/targetType combination 086 * @throws ConversionException 087 */ 088 public static Object convert(Object source, Class targetType, ClassLoader classLoader) throws ConversionException { 089 return convert(source, targetType, null, false, classLoader); 090 } 091 092 private static class ConverterKey { 093 Class sourceType; 094 Class targetType; 095 096 public ConverterKey(Class sourceType, Class targetType) { 097 super(); 098 this.sourceType = sourceType; 099 this.targetType = targetType; 100 } 101 102 public int hashCode() { 103 final int prime = 31; 104 int result = 1; 105 result = prime * result + ((sourceType == null) ? 0 : sourceType.hashCode()); 106 result = prime * result + ((targetType == null) ? 0 : targetType.hashCode()); 107 return result; 108 } 109 110 public boolean equals(Object obj) { 111 return 112 sourceType == ((ConverterKey) obj).sourceType 113 && targetType == ((ConverterKey) obj).targetType; 114 } 115 116 } 117 118 private static class ClassLoaderConvertersEntry { 119 private static final String CONVERT = "convert"; 120 121 private Converter master = new Converter() { 122 123 public Object convert(Object source, Class targetType, Context context) throws ConversionException { 124 if (source==null) { 125 return null; 126 } 127 128 if (targetType.isInstance(source)) { 129 return source; 130 } 131 132 final ConvertersBucket bucket = findConverter(source.getClass(), targetType); 133 return bucket==null ? null : bucket.convert(source, context); 134 } 135 136 }; 137 138 private Converter chainingMaster = new Converter() { 139 140 public Object convert(Object source, Class targetType, Context context) throws ConversionException { 141 if (source==null) { 142 return null; 143 } 144 145 if (targetType.isInstance(source)) { 146 return source; 147 } 148 149 ConvertersBucket bucket = findChainingConverter(source.getClass(), targetType); 150 return bucket==null ? null : bucket.convert(source, context); 151 } 152 153 }; 154 155 Collection atomicConverters = new ArrayList(); 156 157 /** 158 * Map of resolved converters: Collection[source, target] -> ConverterClosure. 159 */ 160 Map converters = new HashMap(); 161 162 Set introspectedTargetTypes = new HashSet(); 163 164 /** 165 * Map of resolved chaining converters: Collection[source, target] -> ConverterClosure. 166 */ 167 Map chainingConverters = new HashMap(); 168 169 synchronized ConvertersBucket findConverter(Class sourceType, Class targetType) { 170 ConverterKey key = new ConverterKey(sourceType, targetType); 171 Object ret = converters.get(key); 172 if (Boolean.FALSE.equals(ret)) { // Indicates that previous converter resolution didn't yield results. 173 return null; 174 } 175 176 if (ret == null) { 177 if (!targetType.isInterface() && introspectedTargetTypes.add(targetType)) { 178 atomicConverters.addAll(ReflectionConverter.discoverConstructorConverters(targetType)); 179 } 180 final List theConverters = new ArrayList(); 181 182 class ConverterEntry implements Comparable { 183 public ConverterEntry(AtomicConverter converter, int affinity, int position) { 184 super(); 185 this.converter = converter; 186 this.affinity = affinity; 187 this.position = position; 188 } 189 AtomicConverter converter; 190 int affinity; 191 int position; 192 public int compareTo(Object obj) { 193 int cret = affinity - ((ConverterEntry) obj).affinity; 194 if (cret==0) { 195 return position - ((ConverterEntry) obj).position; 196 } 197 return cret; 198 } 199 } 200 201 Iterator it = atomicConverters.iterator(); 202 while (it.hasNext()) { 203 AtomicConverter ac = (AtomicConverter) it.next(); 204 int position = 0; 205 if (ac.getSourceType().isAssignableFrom(sourceType) && targetType.isAssignableFrom(ac.getTargetType()) ) { 206 Integer newSourceAffinity = DuckConverterFactory.classAffinity(sourceType, ac.getSourceType()); 207 Integer newTargetAffinity = DuckConverterFactory.classAffinity(ac.getTargetType(), targetType); 208 if (newSourceAffinity==null || newTargetAffinity==null) { 209 throw new IllegalArgumentException("kosyak"); 210 } 211 theConverters.add(new ConverterEntry(ac, newSourceAffinity.intValue()+newTargetAffinity.intValue(), ++position)); 212 } 213 } 214 if (theConverters.isEmpty()) { 215 converters.put(key, Boolean.FALSE); 216 } else { 217 Collections.sort(theConverters); 218 ret = new ConvertersBucket() { 219 220 public Object convert(Object source, Context context) { 221 Iterator it = theConverters.iterator(); 222 while (it.hasNext()) { 223 ConverterEntry ce = (ConverterEntry) it.next(); 224 Object ret = ce.converter.convert(source, master, context); 225 if (ret!=null) { 226 return ret; 227 } 228 } 229 return null; 230 } 231 232 }; 233 converters.put(key, ret); 234 } 235 } 236 237 return (ConvertersBucket) ret; 238 } 239 240 synchronized ConvertersBucket findChainingConverter(Class sourceType, Class targetType) { 241 ConvertersBucket ret = findConverter(sourceType, targetType); 242 if (ret!=null) { 243 return ret; 244 } 245 246 // TODO 777 - Resolve chain. 247 return ret; 248 } 249 250 void addConverters(final Object provider) { 251 if (provider instanceof AtomicConverter) { 252 atomicConverters.add(provider); 253 } else if (provider instanceof AtomicConvertersBundle) { 254 atomicConverters.addAll(((AtomicConvertersBundle) provider).getConverters()); 255 } else if (provider instanceof Collection) { 256 Iterator it = ((Collection) provider).iterator(); 257 while (it.hasNext()) { 258 addConverters(it.next()); 259 } 260 } else { // Introspection 261 Class pClass = provider.getClass(); 262 Method[] methods = pClass.getMethods(); 263 for (int i=0; i<methods.length; ++i) { 264 final Method method = methods[i]; 265 if (CONVERT.equals(method.getName()) && !void.class.equals(method.getReturnType())) { 266 Class[] pTypes = method.getParameterTypes(); 267 if (pTypes.length == 1) { 268 atomicConverters.add(new AtomicConverterBase(pTypes[0], method.getReturnType()) { 269 270 public Object convert(Object source, Converter master, Context context) { 271 try { 272 return method.invoke(provider, new Object[] {source}); 273 } catch (IllegalAccessException e) { 274 throw new ConversionException(e); 275 } catch (InvocationTargetException e) { 276 throw new ConversionException(e); 277 } 278 } 279 280 }); 281 } else if (pTypes.length == 2 && Converter.class.equals(pTypes[1])) { 282 atomicConverters.add(new AtomicConverterBase(pTypes[0], method.getReturnType()) { 283 284 public Object convert(Object source, Converter master, Context context) { 285 try { 286 return method.invoke(provider, new Object[] {source, master}); 287 } catch (IllegalAccessException e) { 288 throw new ConversionException(e); 289 } catch (InvocationTargetException e) { 290 throw new ConversionException(e); 291 } 292 } 293 294 }); 295 296 } else if (pTypes.length == 2 && Context.class.equals(pTypes[1])) { 297 atomicConverters.add(new AtomicConverterBase(pTypes[0], method.getReturnType()) { 298 299 public Object convert(Object source, Converter master, Context context) { 300 try { 301 return method.invoke(provider, new Object[] {source, context}); 302 } catch (IllegalAccessException e) { 303 throw new ConversionException(e); 304 } catch (InvocationTargetException e) { 305 throw new ConversionException(e); 306 } 307 } 308 309 }); 310 311 } else if (pTypes.length == 3 && Converter.class.equals(pTypes[1]) && Context.class.equals(pTypes[2])) { 312 atomicConverters.add(new AtomicConverterBase(pTypes[0], method.getReturnType()) { 313 314 public Object convert(Object source, Converter master, Context context) { 315 try { 316 return method.invoke(provider, new Object[] {source, master, context}); 317 } catch (IllegalAccessException e) { 318 throw new ConversionException(e); 319 } catch (InvocationTargetException e) { 320 throw new ConversionException(e); 321 } 322 } 323 324 }); 325 326 } 327 328 } 329 } 330 } 331 } 332 333 } 334 335 /** 336 * Map of resolved converters: ClassLoader -> ClassLoaderConvertersEntry. 337 */ 338 private static Map classLoaderEntries = new HashMap(); 339 340 /** 341 * Converts source to target type using converters loaded from 342 * <code>META-INF/config/biz.hammurapi.convert.Converter</code> resources. 343 * If no converter is found, this method performs converter discovery through 344 * constructors if target type is a class and through duck conversion if target 345 * type is an interface. 346 * @param source Source object. 347 * @param targetType Target type. 348 * @param context Conversion context. 349 * @param classLoader Class loader. 350 * @return Source object converted to target type or null if no converter 351 * is available for source/targetType combination 352 * @throws ConversionException 353 */ 354 public static Object convert(Object source, Class targetType, Context context, ClassLoader classLoader) throws ConversionException { 355 return convert(source, targetType, context, false, classLoader); 356 } 357 358 /** 359 * Converts source to target type using converters loaded from 360 * <code>META-INF/config/biz.hammurapi.convert.Converter</code> resources. 361 * If no converter is found, this method performs converter discovery through 362 * constructors if target type is a class and through duck conversion if target 363 * type is an interface. 364 * @param source Source object. 365 * @param targetType Target type. 366 * @param chain If true, the converter will attempt to build a chain of 367 * conversions from source type to target type. 368 * For example, StringBuffer doesn't have a constructor from Integer. If chain 369 * is <code>false</code> then this method will return <code>null</code>. If 370 * <code>chain</code> is true, then the converter will convert Integer to String 371 * and then will create StringBuffer from it. If multiple conversion chains 372 * are available, the shortest is chosen. 373 * @return Source object converted to target type or null if no converter 374 * is available for source/targetType combination 375 * @throws ConversionException 376 */ 377 private static Object convert(Object source, Class targetType, Context context, boolean chain, ClassLoader classLoader) throws ConversionException { 378 if (source==null) { 379 return null; 380 } 381 382 if (targetType.isInstance(source)) { 383 return source; 384 } 385 386 ConverterClosure converter = getConverter(source.getClass(), targetType, context, chain, classLoader); 387 return converter==null ? null : converter.convert(source); 388 } 389 390 /** 391 * Converts source to target type using converters loaded from 392 * <code>META-INF/config/biz.hammurapi.convert.Converter</code> resources. 393 * If no converter is found, this method performs converter discovery through 394 * constructors if target type is a class and through duck conversion if target 395 * type is an interface. 396 * @param sourceType Source type. 397 * @param targetType Target type. 398 * @return Converter from source type to target type, or null if such converter 399 * is not available. 400 * @throws ConversionException 401 */ 402 public static ConverterClosure getConverter(Class sourceType, Class targetType) { 403 return getConverter(sourceType, targetType, null, false, null); 404 } 405 406 /** 407 * Converts source to target type using converters loaded from 408 * <code>META-INF/config/biz.hammurapi.convert.Converter</code> resources. 409 * If no converter is found, this method performs converter discovery through 410 * constructors if target type is a class and through duck conversion if target 411 * type is an interface. 412 * @param sourceType Source type. 413 * @param targetType Target type. 414 * @param classLoader Class loader to retrieve converters from. 415 * @return Converter from source type to target type, or null if such converter 416 * is not available. 417 * @throws ConversionException 418 */ 419 public static ConverterClosure getConverter(Class sourceType, Class targetType, ClassLoader classLoader) { 420 return getConverter(sourceType, targetType, null, false, classLoader); 421 } 422 423 /** 424 * Converts source to target type using converters loaded from 425 * <code>META-INF/config/biz.hammurapi.convert.Converter</code> resources. 426 * If no converter is found, this method performs converter discovery through 427 * constructors if target type is a class and through duck conversion if target 428 * type is an interface. 429 * @param sourceType Source type. 430 * @param targetType Target type. 431 * @param context Conversion context. 432 * @return Converter from source type to target type, or null if such converter 433 * is not available. 434 * @throws ConversionException 435 */ 436 public static ConverterClosure getConverter(Class sourceType, Class targetType, Context context) { 437 return getConverter(sourceType, targetType, context, false, null); 438 } 439 440 /** 441 * Converts source to target type using converters loaded from 442 * <code>META-INF/config/biz.hammurapi.convert.Converter</code> resources. 443 * If no converter is found, this method performs converter discovery through 444 * constructors if target type is a class and through duck conversion if target 445 * type is an interface. 446 * @param sourceType Source type. 447 * @param targetType Target type. 448 * @param context Conversion context. 449 * @param classLoader Class loader to retrieve converters from. 450 * @return Converter from source type to target type, or null if such converter 451 * is not available. 452 * @throws ConversionException 453 */ 454 public static ConverterClosure getConverter(Class sourceType, Class targetType, Context context, ClassLoader classLoader) { 455 return getConverter(sourceType, targetType, context, false, classLoader); 456 } 457 458 private static ClassLoader promote(ClassLoader present, ClassLoader candidate) { 459 ClassLoader ret = DuckConverterFactory.getChildClassLoader(present, candidate); 460 return ret==null ? present : ret; 461 } 462 463 /** 464 * 465 * @param sourceType 466 * @param targetType 467 * @param context 468 * @param chain If true, the converter will attempt to build a chain of 469 * conversions from source type to target type. 470 * For example, StringBuffer doesn't have a constructor from Integer. If chain 471 * is <code>false</code> then this method will return <code>null</code>. If 472 * <code>chain</code> is true, then the converter will convert Integer to String 473 * and then will create StringBuffer from it. If multiple conversion chains 474 * are available, the shortest is chosen. 475 * @param classLoader 476 * @return 477 */ 478 private static ConverterClosure getConverter(Class sourceType, Class targetType, final Context context, boolean chain, final ClassLoader pClassLoader) { 479 ClassLoader classLoader = promote(pClassLoader, promote(targetType.getClassLoader(), sourceType.getClassLoader())); 480 481 ClassLoaderConvertersEntry clce; 482 synchronized (classLoaderEntries) { 483 clce = (ClassLoaderConvertersEntry) classLoaderEntries.get(classLoader); 484 if (clce == null) { 485 clce = new ClassLoaderConvertersEntry(); 486 Iterator pit = DomConfigFactory.loadProviders(Converter.class, classLoader); 487 while (pit.hasNext()) { 488 clce.addConverters(pit.next()); 489 } 490 classLoaderEntries.put(classLoader, clce); 491 } 492 } 493 final ConvertersBucket bucket = chain ? clce.findChainingConverter(sourceType, targetType) : clce.findConverter(sourceType, targetType); 494 return bucket==null ? null : new ConverterClosure() { 495 496 public Object convert(Object source) { 497 return bucket.convert(source, context); 498 } 499 500 }; 501 } 502 }