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 }