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