001: package biz.hammurapi.convert;
002:
003: import java.lang.reflect.InvocationHandler;
004: import java.lang.reflect.Method;
005: import java.lang.reflect.Proxy;
006: import java.util.ArrayList;
007: import java.util.Collection;
008: import java.util.HashMap;
009: import java.util.Map;
010:
011: import biz.hammurapi.config.Context;
012: import biz.hammurapi.util.ClassHierarchyVisitable;
013: import biz.hammurapi.util.Visitor;
014:
015: /**
016: * Creates converters which use "duck" typing.
017: * @author Pavel
018: *
019: */
020: public class DuckConverterFactory {
021:
022: /**
023: * Returns source object unchanged
024: */
025: private static Converter ZERO_CONVERTER = new Converter() {
026:
027: public Object convert(Object source) {
028: return source;
029: }
030:
031: };
032:
033: /**
034: * Contains [sourceClass, targetClass] -> ProxyConverter(targetMethod -> sourceMethod) mapping.
035: */
036: private static Map converterMap = new HashMap();
037:
038: private static class ProxyConverter implements Converter {
039:
040: /**
041: * Maps target methods to source methods. Unmapped methods
042: * are invoked directly (e.g. equals() or if method in both source and target belongs
043: * to the same interface (partial overlap)).
044: */
045: private Map methodMap;
046:
047: private Class[] targetInterfaces;
048:
049: private ClassLoader classLoader;
050:
051: public ProxyConverter(Class targetInterface, Map methodMap,
052: ClassLoader classLoader) {
053: this .methodMap = methodMap;
054: this .targetInterfaces = new Class[] { targetInterface };
055: this .classLoader = classLoader;
056: }
057:
058: public Object convert(final Object source) {
059:
060: InvocationHandler ih = new InvocationHandler() {
061:
062: public Object invoke(Object proxy, Method method,
063: Object[] args) throws Throwable {
064: Method sourceMethod = (Method) (methodMap == null ? null
065: : methodMap.get(method));
066: return sourceMethod == null ? method.invoke(source,
067: args) : sourceMethod.invoke(source, args);
068: }
069:
070: };
071:
072: return Proxy.newProxyInstance(classLoader,
073: targetInterfaces, ih);
074: }
075:
076: }
077:
078: /**
079: * @param sourceClass
080: * @param targetInterface
081: * @return Converter which can "duck-type" instance of source class to target interface or null if conversion is not possible.
082: * Methods are mapped as follows: return types shall be compatible, arguments shall be compatible, exception declarations are ignored.
083: */
084: public static Converter getConverter(Class sourceClass,
085: Class targetInterface) {
086: if (targetInterface.isAssignableFrom(sourceClass)) {
087: return ZERO_CONVERTER;
088: }
089:
090: Collection key = new ArrayList();
091: key.add(sourceClass);
092: key.add(targetInterface);
093: synchronized (converterMap) {
094: Object value = converterMap.get(key);
095: if (Boolean.FALSE.equals(value)) {
096: return null;
097: }
098:
099: if (value == null) {
100: Method[] targetMethods = targetInterface.getMethods();
101: Method[] sourceMethods = sourceClass.getMethods();
102: Map methodMap = new HashMap();
103:
104: Z: for (int i = 0, l = targetMethods.length; i < l; ++i) {
105: if (Object.class.equals(targetMethods[i]
106: .getDeclaringClass())) {
107: continue;
108: }
109:
110: Method targetMethod = targetMethods[i];
111: int candidateIndex = -1;
112: Method candidateMethod = null;
113:
114: Y: for (int j = 0, m = sourceMethods.length; j < m; ++j) {
115: Method sourceMethod = sourceMethods[j];
116: if (sourceMethod != null) {
117: if (targetMethod.equals(sourceMethod)) { // No mapping neccesary
118: sourceMethods[j] = null; // Method is taken
119: continue Z;
120: }
121:
122: if (targetMethod.getName().equals(
123: sourceMethod.getName())
124: && targetMethod.getParameterTypes().length == sourceMethod
125: .getParameterTypes().length) {
126: // Check for compatibility
127:
128: // Return type shall "widen"
129: if (!targetMethod
130: .getReturnType()
131: .isAssignableFrom(
132: sourceMethod
133: .getReturnType())) {
134: continue;
135: }
136:
137: Class[] targetParameterTypes = targetMethod
138: .getParameterTypes();
139: Class[] sourceParameterTypes = sourceMethod
140: .getParameterTypes();
141: for (int k = 0, n = targetParameterTypes.length; k < n; ++k) {
142: if (!sourceParameterTypes[k]
143: .isAssignableFrom(targetParameterTypes[k])) {
144: continue Y;
145: }
146: }
147:
148: if (candidateMethod != null) {
149: int oldAffinity = classAffinity(
150: candidateMethod
151: .getReturnType(),
152: targetMethod
153: .getReturnType());
154: int newAffinity = classAffinity(
155: sourceMethod
156: .getReturnType(),
157: targetMethod
158: .getReturnType());
159: if (oldAffinity < newAffinity) {
160: continue;
161: }
162:
163: Class[] candidateParameterTypes = candidateMethod
164: .getParameterTypes();
165: for (int k = 0, n = sourceParameterTypes.length; k < n; ++k) {
166: oldAffinity = classAffinity(
167: targetParameterTypes[k],
168: candidateParameterTypes[k]);
169: newAffinity = classAffinity(
170: targetParameterTypes[k],
171: sourceParameterTypes[k]);
172: if (oldAffinity < newAffinity) {
173: continue Y;
174: }
175: }
176:
177: sourceMethods[candidateIndex] = candidateMethod; // return method back
178: }
179:
180: candidateMethod = sourceMethod;
181: candidateIndex = j;
182: sourceMethods[j] = null; // Method is taken
183: // Compare with existing candidate
184:
185: }
186: }
187: }
188:
189: if (candidateMethod == null) {
190: converterMap.put(key, Boolean.FALSE); // To indicate that we tried and failed
191: return null;
192: }
193:
194: methodMap.put(targetMethod, candidateMethod);
195: }
196:
197: ClassLoader cl = getChildClassLoader(sourceClass
198: .getClassLoader(), targetInterface
199: .getClassLoader());
200: if (cl == null) {
201: converterMap.put(key, Boolean.FALSE); // To indicate that we tried and failed
202: return null;
203: }
204:
205: value = new ProxyConverter(targetInterface, methodMap
206: .isEmpty() ? null : methodMap, cl);
207: converterMap.put(key, value);
208: }
209: return (Converter) value;
210: }
211: }
212:
213: /**
214: * Calculates how close is subclass to superclass in class hierarchy.
215: * @param subClass
216: * @param superClass
217: * @return affinity, or Integer.MAX_VALUE if classes don't belong to the same class hierarchy.
218: */
219: public static int classAffinity(Class subClass,
220: final Class super Class) {
221: if (super Class.isAssignableFrom(subClass)) {
222: final int[] caffinity = { 0 };
223: new ClassHierarchyVisitable(subClass).accept(new Visitor() {
224:
225: public boolean visit(Object target) {
226: if (target.equals(super Class)) {
227: return false;
228: }
229:
230: caffinity[0]++;
231: return true;
232: }
233:
234: });
235: return caffinity[0];
236: }
237:
238: return Integer.MAX_VALUE;
239: }
240:
241: /**
242: * @param cl1
243: * @param cl2
244: * @return Child classloader or null if classloaders are not related
245: */
246: private static ClassLoader getChildClassLoader(ClassLoader cl1,
247: ClassLoader cl2) {
248: if (cl1 == null) {
249: return cl2;
250: }
251: if (cl2 == null) {
252: return cl1;
253: }
254: for (ClassLoader cl = cl1; cl != null && cl != cl.getParent(); cl = cl
255: .getParent()) {
256: if (cl2.equals(cl)) {
257: return cl1;
258: }
259: }
260: for (ClassLoader cl = cl2; cl != null && cl != cl.getParent(); cl = cl
261: .getParent()) {
262: if (cl1.equals(cl)) {
263: return cl2;
264: }
265: }
266: return null;
267: }
268:
269: public static void main(String[] args) {
270: Converter converter = getConverter(Map.class, Context.class);
271: System.out.println(converter);
272:
273: Map testMap = new HashMap();
274: testMap.put("odin", "dva");
275:
276: System.out.println(((Context) converter.convert(testMap))
277: .get("odin"));
278:
279: Converter c2 = getConverter(Map.class, String.class);
280: System.out.println(c2);
281:
282: Context ctx = (Context) CompositeConverter
283: .getDefaultConverter().convert(testMap, Context.class,
284: false);
285: System.out.println(ctx.get("odin"));
286: }
287:
288: }
|