001 /* 002 @license.text@ 003 */ 004 package biz.hammurapi.convert; 005 006 import java.io.File; 007 import java.io.FileReader; 008 import java.io.Reader; 009 import java.lang.reflect.Constructor; 010 import java.lang.reflect.Method; 011 import java.math.BigDecimal; 012 import java.util.ArrayList; 013 import java.util.Collections; 014 import java.util.Comparator; 015 import java.util.Iterator; 016 import java.util.List; 017 018 import biz.hammurapi.util.ClassHierarchyVisitable; 019 import biz.hammurapi.util.Visitor; 020 import biz.hammurapi.xml.dom.CompositeDomSerializer; 021 import biz.hammurapi.xml.dom.DomSerializable; 022 023 024 /** 025 * @author Pavel Vlasov 026 * @version $Revision: 1.7 $ 027 */ 028 public class CompositeConverter { 029 030 /** 031 * Creates new composite converter populated by default with some generic converters. 032 */ 033 public CompositeConverter() { 034 _addConverter(Number.class, byte.class, "byteValue", null); 035 _addConverter(Number.class, double.class, "doubleValue", null); 036 _addConverter(Number.class, float.class, "floatValue", null); 037 _addConverter(Number.class, int.class, "intValue", null); 038 _addConverter(Number.class, long.class, "longValue", null); 039 _addConverter(Number.class, short.class, "shortValue", null); 040 041 _addConverter(Number.class, Byte.class, "byteValue", null); 042 _addConverter(Number.class, Double.class, "doubleValue", null); 043 _addConverter(Number.class, Float.class, "floatValue", null); 044 _addConverter(Number.class, Integer.class, "intValue", null); 045 _addConverter(Number.class, Long.class, "longValue", null); 046 _addConverter(Number.class, Short.class, "shortValue", null); 047 048 discoverConverter(String.class, Integer.class); 049 discoverConverter(String.class, Long.class); 050 discoverConverter(String.class, Double.class); 051 discoverConverter(String.class, Float.class); 052 discoverConverter(String.class, Byte.class); 053 discoverConverter(String.class, Short.class); 054 discoverConverter(String.class, BigDecimal.class); 055 056 try { 057 _addConverter(File.class, Reader.class, null, FileReader.class.getConstructor(new Class[] {File.class})); 058 } catch (SecurityException e) { 059 throw new ConversionException(e); 060 } catch (NoSuchMethodException e) { 061 throw new ConversionException(e); 062 } 063 064 addConverter(String.class, boolean.class, new Converter() { 065 066 public Object convert(Object source) { 067 if (source==null) { 068 return Boolean.FALSE; 069 } 070 071 String str = ((String) source).trim(); 072 if (str.length()==0 || "no".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str) || "0".equals(source)) { 073 return Boolean.FALSE; 074 } else if ("yes".equalsIgnoreCase(str) || "true".equalsIgnoreCase(str) || "1".equals(source)) { 075 return Boolean.TRUE; 076 } 077 078 throw new ConversionException("Cannot convert string '"+source+"' to boolean"); 079 } 080 081 }); 082 083 addConverter(String.class, byte.class, new Converter() { 084 085 public Object convert(Object source) { 086 return new Byte((String) source); 087 } 088 089 }); 090 091 addConverter(String.class, double.class, new Converter() { 092 093 public Object convert(Object source) { 094 return new Double((String) source); 095 } 096 097 }); 098 099 addConverter(String.class, float.class, new Converter() { 100 101 public Object convert(Object source) { 102 return new Float((String) source); 103 } 104 105 }); 106 107 108 addConverter(String.class, int.class, new Converter() { 109 110 public Object convert(Object source) { 111 return new Integer((String) source); 112 } 113 114 }); 115 116 117 addConverter(String.class, long.class, new Converter() { 118 119 public Object convert(Object source) { 120 return new Long((String) source); 121 } 122 123 }); 124 125 126 addConverter(String.class, short.class, new Converter() { 127 128 public Object convert(Object source) { 129 return new Short((String) source); 130 } 131 132 }); 133 134 addConverter(Object.class, DomSerializable.class, new Converter() { 135 136 public Object convert(Object source) { 137 return CompositeDomSerializer.getThreadInstance().toDomSerializable(source); 138 } 139 140 }); 141 142 } 143 144 private boolean immutable; 145 146 /** 147 * @return true if no additional converters can be added to this converter. 148 */ 149 public boolean isImmutable() { 150 return immutable; 151 } 152 153 /** 154 * Makes converter immutable. 155 */ 156 public void setImmutable() { 157 this.immutable = true; 158 } 159 160 private static CompositeConverter defaultConverter; 161 162 static { 163 defaultConverter=new CompositeConverter(); 164 defaultConverter.setImmutable(); 165 } 166 167 public static CompositeConverter getDefaultConverter() { 168 return defaultConverter; 169 } 170 171 // TODO - chained conversions 172 173 private static class ReflectionConverter implements Converter { 174 Method accessor; 175 Constructor constructor; 176 177 /** 178 * @param accessor 179 * @param constructor 180 */ 181 ReflectionConverter(Method accessor, Constructor constructor) { 182 super(); 183 this.accessor = accessor; 184 this.constructor = constructor; 185 } 186 187 public Object convert(Object source) { 188 try { 189 Object param = accessor==null ? source : accessor.invoke(source, null); 190 return constructor==null ? param : constructor.newInstance(new Object[] {param}); 191 } catch (Exception e) { 192 throw new ConversionException("Cannot convert "+source.getClass().getName()+" to " + constructor.getDeclaringClass(), e); 193 } 194 } 195 } 196 197 private static class ConverterEntry { 198 Class source; 199 Class target; 200 private Converter converter; 201 202 /** 203 * @param accessor 204 * @param constructor 205 */ 206 ConverterEntry(Class source, Class target, Converter converter) { 207 super(); 208 this.source = source; 209 this.target = target; 210 this.converter = converter; 211 } 212 213 boolean isCompatible(Class source, Class target) { 214 return this.source.isAssignableFrom(source) && target.isAssignableFrom(this.target); 215 } 216 217 boolean isCompatible(ConverterEntry otherEntry) { 218 return isCompatible(otherEntry.source, otherEntry.target); 219 } 220 } 221 222 private List converters=new ArrayList(); 223 224 /** 225 * Adds a converter which uses method of source object and constructor of target object to 226 * perform conversion. 227 * @param source Source object. 228 * @param target Target class. 229 * @param accessor Method name to invoke on source to obtain intermediate object. Can be null. 230 * @param constructor Target constructor. Can be null. 231 */ 232 public Converter addConverter(Class source, Class target, String accessor, Constructor constructor) { 233 if (immutable) { 234 throw new ConversionException("Converter is immutable"); 235 } 236 return _addConverter(source, target, accessor, constructor); 237 } 238 239 private Converter _addConverter(Class source, Class target, String accessor, Constructor constructor) { 240 ReflectionConverter ret; 241 try { 242 ret = new ReflectionConverter(accessor==null ? null : source.getMethod(accessor, null), constructor); 243 } catch (SecurityException e) { 244 throw new ConversionException(e); 245 } catch (NoSuchMethodException e) { 246 throw new ConversionException(e); 247 } 248 _addConverter(source, target, ret); 249 return ret; 250 } 251 252 /** 253 * Adds a converter from source object to target class. 254 * @param source 255 * @param target 256 * @param converter 257 */ 258 public void addConverter(Class source, Class target, Converter converter) { 259 if (immutable) { 260 throw new ConversionException("Converter is immutable"); 261 } 262 _addConverter(source, target, converter); 263 } 264 265 private void _addConverter(final Class source, Class target, final Converter converter) { 266 synchronized (converters) { 267 new ClassHierarchyVisitable(target).accept( 268 new Visitor() { 269 270 public boolean visit(Object target) { 271 Iterator it = converters.iterator(); 272 while (it.hasNext()) { 273 ConverterEntry ce = (ConverterEntry) it.next(); 274 if (ce.source.equals(source) && ce.target.equals(target)) { 275 return true; // Converter already exists. 276 } 277 } 278 converters.add(new ConverterEntry(source, (Class) target, converter)); 279 return true; 280 } 281 282 }); 283 284 Collections.sort( 285 converters, 286 new Comparator() { 287 288 public int compare(Object o1, Object o2) { 289 if (o1==o2) { 290 return 0; 291 } 292 293 if (o1 instanceof ConverterEntry && o2 instanceof ConverterEntry) { 294 ConverterEntry c1=(ConverterEntry) o1; 295 ConverterEntry c2=(ConverterEntry) o2; 296 297 // More specific converters go first. 298 if (c1.isCompatible(c2)) { 299 return c2.isCompatible(c1) ? 0 : 1; 300 } 301 302 if (c2.isCompatible(c1)) { 303 return -1; 304 } 305 306 return 0; 307 } 308 309 return o1.hashCode()-o2.hashCode(); 310 } 311 312 }); 313 314 } 315 } 316 317 private Converter findConverter(Class source, Class target) { 318 synchronized (converters) { 319 Iterator it=converters.iterator(); 320 while (it.hasNext()) { 321 ConverterEntry ret=(ConverterEntry) it.next(); 322 if (ret.isCompatible(source, target)) { 323 return ret.converter; 324 } 325 } 326 return null; 327 } 328 } 329 330 /** 331 * Converts source object to target class instance 332 * @param source Source object 333 * @param target Target class. If target class is String then toString() is always used. 334 * @param lenient When true null is returned if conversion cannot be performed, 335 * otherwise ConversionException is thrown 336 * @return Instance of target class. 337 * @throws ConversionException If lenient=false and conversion cannot be performed. 338 */ 339 public Object convert(Object source, Class target, boolean lenient) { 340 if (source==null) { 341 return null; 342 } else if (target.isInstance(source)) { 343 return source; 344 } else if (String.class.equals(target)) { 345 return source.toString(); 346 } else { 347 try { 348 Converter converter=findConverter(source.getClass(), target); 349 350 if (converter==null) { 351 converter = discoverConverter(source.getClass(), target); 352 } 353 354 if (converter==null) { 355 throw new ConversionException("No appropriate converter from "+source.getClass()+" to "+target); 356 } 357 358 return converter.convert(source); 359 } catch (ConversionException e) { 360 if (lenient) { 361 return null; 362 } 363 364 throw e; 365 } 366 } 367 } 368 369 /** 370 * @param class1 371 * @param target 372 * @return Discovered converter or null. 373 */ 374 private Converter discoverConverter(Class source, Class target) { 375 Converter ret=null; 376 Constructor constructor=null; 377 Constructor stringConstructor=null; 378 for (int i=0, cc=target.getConstructors().length; i<cc; i++) { 379 Constructor candidate = target.getConstructors()[i]; 380 if (candidate.getParameterTypes().length==1) { 381 Converter c=_addConverter(candidate.getParameterTypes()[0], candidate.getDeclaringClass(), null, candidate); 382 383 if (candidate.getParameterTypes()[0].isInstance(source) && (constructor==null || constructor.getParameterTypes()[0].isAssignableFrom(candidate.getParameterTypes()[0]))) { 384 constructor=candidate; 385 ret=c; 386 } 387 388 if (stringConstructor==null && java.lang.String.class.equals(candidate.getParameterTypes()[0])) { 389 stringConstructor=candidate; 390 } 391 } 392 } 393 394 if (ret!=null) { 395 return ret; 396 } 397 398 if (stringConstructor!=null) { 399 return _addConverter(Object.class, target, "toString", stringConstructor); 400 } 401 402 if (target.isInterface()) { 403 ret = DuckConverterFactory.getConverter(source, target); 404 if (ret==null) { 405 ret=ContextConverterFactory.getConverter(source, target); 406 } 407 } 408 409 return ret; 410 } 411 412 413 }