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