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.config.MapContext; 013 import biz.hammurapi.config.MutableContext; 014 import biz.hammurapi.config.RuntimeConfigurationException; 015 import biz.hammurapi.metrics.Metric.Measurement; 016 017 018 /** 019 * Creates converters from Context and MutableContext to interfaces which have only setters and getters (beans) 020 * @author Pavel 021 * 022 */ 023 public class ContextConverterFactory { 024 025 /** 026 * Returns source object unchanged 027 */ 028 private static Converter ZERO_CONVERTER = new Converter() { 029 030 public Object convert(Object source) { 031 return source; 032 } 033 034 }; 035 036 /** 037 * Contains [sourceClass, targetClass] -> ProxyConverter(targetMethod -> sourceMethod) mapping. 038 */ 039 private static Map converterMap = new HashMap(); 040 041 private static class ProxyConverter implements Converter { 042 043 /** 044 * Maps target methods to source methods. Unmapped methods 045 * are invoked directly (e.g. equals() or if method in both source and target belongs 046 * to the same interface (partial overlap)). 047 */ 048 private Map methodMap; 049 050 private Class[] targetInterfaces; 051 052 private ClassLoader classLoader; 053 054 public ProxyConverter(Class targetInterface, Map methodMap, ClassLoader classLoader) { 055 this.methodMap = methodMap; 056 this.targetInterfaces = new Class[] {targetInterface}; 057 this.classLoader = classLoader; 058 } 059 060 public Object convert(final Object source) { 061 062 InvocationHandler ih = new InvocationHandler() { 063 064 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 065 Method sourceMethod = (Method) (methodMap==null ? null : methodMap.get(method)); 066 if (sourceMethod==null) { 067 return method.invoke(source, args); 068 } 069 070 if (method.getName().startsWith("get")) { 071 Object ret = sourceMethod.invoke(source, new Object[] {method.getName().substring("get".length())}); 072 return CompositeConverter.getDefaultConverter().convert(ret, method.getReturnType(), false); 073 } 074 075 return sourceMethod.invoke(source, new Object[] {method.getName().substring("set".length()), args[0]}); 076 } 077 078 }; 079 080 return Proxy.newProxyInstance(classLoader, targetInterfaces, ih); 081 } 082 083 } 084 085 /** 086 * @param sourceClass 087 * @param targetInterface 088 * @return Converter which can "duck-type" instance of source class to target interface or null if conversion is not possible. 089 * Methods are mapped as follows: return types shall be compatible, arguments shall be compatible, exception declarations are ignored. 090 */ 091 public static Converter getConverter(Class sourceClass, Class targetInterface) { 092 if (targetInterface.isAssignableFrom(sourceClass)) { 093 return ZERO_CONVERTER; 094 } 095 096 Collection key=new ArrayList(); 097 key.add(sourceClass); 098 key.add(targetInterface); 099 synchronized (converterMap) { 100 Object value = converterMap.get(key); 101 if (Boolean.FALSE.equals(value)) { 102 return null; 103 } 104 105 if (!Context.class.isAssignableFrom(sourceClass)) { 106 converterMap.put(key, Boolean.FALSE); // To indicate that we tried and failed 107 return null; 108 } 109 110 Method getter; 111 try { 112 getter = Context.class.getMethod("get", new Class[] {String.class}); 113 } catch (SecurityException e) { 114 throw new RuntimeConfigurationException(e); 115 } catch (NoSuchMethodException e) { 116 throw new RuntimeConfigurationException(e); 117 } 118 119 Method setter; 120 try { 121 setter = MutableContext.class.isAssignableFrom(sourceClass) ? MutableContext.class.getMethod("set", new Class[] {String.class, Object.class}) : null; 122 } catch (SecurityException e) { 123 throw new RuntimeConfigurationException(e); 124 } catch (NoSuchMethodException e) { 125 throw new RuntimeConfigurationException(e); 126 } 127 128 if (value==null) { 129 Method[] targetMethods = targetInterface.getMethods(); 130 131 Map methodMap = new HashMap(); 132 133 for (int i=0, l=targetMethods.length; i<l; ++i) { 134 if (Object.class.equals(targetMethods[i].getDeclaringClass())) { 135 continue; 136 } 137 138 Method targetMethod = targetMethods[i]; 139 if (targetMethod.getName().startsWith("get") 140 && targetMethod.getParameterTypes().length==0) { 141 methodMap.put(targetMethod, getter); 142 } else if (targetMethod.getName().startsWith("set") 143 && targetMethod.getParameterTypes().length==1 144 && setter!=null) { 145 methodMap.put(targetMethod, setter); 146 } else { 147 converterMap.put(key, Boolean.FALSE); // To indicate that we tried and failed 148 return null; 149 } 150 } 151 152 ClassLoader cl = getChildClassLoader(sourceClass.getClassLoader(), targetInterface.getClassLoader()); 153 if (cl==null) { 154 converterMap.put(key, Boolean.FALSE); // To indicate that we tried and failed 155 return null; 156 } 157 158 value = new ProxyConverter(targetInterface, methodMap.isEmpty() ? null : methodMap, cl); 159 converterMap.put(key, value); 160 } 161 return (Converter) value; 162 } 163 } 164 165 /** 166 * @param cl1 167 * @param cl2 168 * @return Child classloader or null if classloaders are not related 169 */ 170 private static ClassLoader getChildClassLoader(ClassLoader cl1, ClassLoader cl2) { 171 if (cl1==null) { 172 return cl2; 173 } 174 if (cl2==null) { 175 return cl1; 176 } 177 for (ClassLoader cl = cl1; cl!=null && cl!=cl.getParent(); cl=cl.getParent()) { 178 if (cl2.equals(cl)) { 179 return cl1; 180 } 181 } 182 for (ClassLoader cl = cl2; cl!=null && cl!=cl.getParent(); cl=cl.getParent()) { 183 if (cl1.equals(cl)) { 184 return cl2; 185 } 186 } 187 return null; 188 } 189 190 public static void main(String[] args) { 191 192 Map testMap = new HashMap(); 193 testMap.put("Value", "33"); 194 MapContext mc = new MapContext(testMap); 195 196 Converter converter = getConverter(mc.getClass(), Measurement.class); 197 System.out.println(converter); 198 199 System.out.println(((Measurement) converter.convert(mc)).getValue()); 200 } 201 202 }