001 package com.hammurapi.convert;
002
003 import java.lang.reflect.InvocationTargetException;
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.Iterator;
010 import java.util.Map;
011 import java.util.ServiceLoader;
012
013 import com.hammurapi.util.Context;
014
015
016 /**
017 * Decorates objects based on their type and decoration providers
018 * available. Decoration is done with dynamic proxies.
019 * @author Pavel
020 */
021 public class DecoratingService {
022
023 /**
024 * Converter which delegates to convert() method.
025 */
026 public static final Decorator DECORATOR = new Decorator() {
027
028 public Object decorate(Object source) {
029 if (source==null) {
030 return null;
031 }
032 return DecoratingService.decorate(source);
033 }
034
035 };
036
037
038 /**
039 * Decorates object using static decoration providers from the object's class
040 * classloader. If there are no decorations available or object doesn't
041 * impement any interfaces, then the object is returned as-is.
042 * @param obj Object to be decorated
043 * @return Decorated object (dynamic proxy instance) or original object if
044 * no decorations are available or object doesn't implement any interfaces.
045 */
046 public static Object decorate(Object obj) {
047 return decorate(obj, null, null);
048 }
049
050 /**
051 * Decorates object using static decoration providers from the object's class
052 * classloader. If there are no decorations available or object doesn't
053 * impement any interfaces, then the object is returned as-is.
054 * @param obj Object to be decorated
055 * @param context Context for dynamic decorators.
056 * @return Decorated object (dynamic proxy instance) or original object if
057 * no decorations are available or object doesn't implement any interfaces.
058 */
059 public static Object decorate(Object obj, Context context) {
060 return decorate(obj, context, null);
061 }
062
063 /**
064 * Decorates object using providers from the given classloader.
065 * @param obj Object to decorate.
066 * @param classLoader Class loader.
067 * @return Decorated object (dynamic proxy instance) or original object if
068 * no decorations are available or object doesn't implement any interfaces.
069 */
070 public static Object decorate(Object obj, ClassLoader classLoader) {
071 return decorate(obj, null, classLoader);
072 }
073
074 /**
075 * Decorates object using given context for dynamic decorators and given class
076 * loader to load decorating providers.
077 * @param obj Object to decorate.
078 * @param context Context for dynamic decorators.
079 * @param classLoader Class loader.
080 * @return Decorated object (dynamic proxy instance) or original object if
081 * no decorations are available or object doesn't implement any interfaces.
082 */
083 public static Object decorate(final Object obj, Context context, ClassLoader classLoader) {
084 if (obj==null) {
085 return null;
086 }
087
088 final Map<Class, Object> interfaceMap = new HashMap<Class, Object>();
089 Class[] interfaces = DuckConverterFactory.getClassInterfaces(obj.getClass());
090 for (int i=0; i<interfaces.length; ++i) {
091 interfaceMap.put(interfaces[i], obj);
092 }
093
094 if (interfaceMap.isEmpty()) {
095 return obj;
096 }
097
098 if (classLoader==null) {
099 classLoader = obj.getClass().getClassLoader();
100 }
101
102 if (classLoader==null) {
103 classLoader = DecoratingService.class.getClassLoader();
104 }
105
106 ClassLoaderDecoratorsEntry clde;
107 synchronized (classLoaderEntries) {
108 clde = classLoaderEntries.get(classLoader);
109 if (clde == null) {
110 clde = new ClassLoaderDecoratorsEntry();
111 for (Object provider: ServiceLoader.load(Decorator.class, classLoader)) {
112 clde.addDecorators(provider);
113 }
114 classLoaderEntries.put(classLoader, clde);
115 }
116 }
117
118 int iSize = interfaceMap.size();
119 clde.collectDecorators(obj, interfaceMap, context, DECORATOR);
120 if (iSize==interfaceMap.size()) {
121 return obj;
122 }
123
124 Class[] allInterfaces = new Class[interfaceMap.size()];
125 FilterInvocationHandler fih = new FilterInvocationHandler() {
126
127 public Object getMaster() {
128 return obj;
129 }
130
131 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
132 Object target = interfaceMap.get(method.getDeclaringClass());
133 if (target==null) {
134 target = obj;
135 }
136 return method.invoke(target, args);
137 }
138
139 };
140
141 return Proxy.newProxyInstance(classLoader, allInterfaces, fih);
142 }
143
144 /**
145 * Creates decorating closure.
146 * @return Decorating closure.
147 */
148 public static Decorator getDecorator() {
149 return getDecorator(null, null);
150 }
151
152 /**
153 * Creates decorating closure.
154 * @param classLoader Class loader.
155 * @return Decorating closure.
156 */
157 public static Decorator getDecorator(ClassLoader classLoader) {
158 return getDecorator(null, classLoader);
159 }
160
161 /**
162 * Creates decorating closure.
163 * @param context Context for dynamic decorators.
164 * @return Decorating closure.
165 */
166 public static Decorator getDecorator(Context context) {
167 return getDecorator(context, null);
168 }
169
170 /**
171 * Creates decorating closure.
172 * @param context Context for dynamic decorators.
173 * @param classLoader Class loader.
174 * @return Decorating closure.
175 */
176 public static Decorator getDecorator(final Context context, final ClassLoader classLoader) {
177 return new Decorator() {
178
179 public Object decorate(Object source) {
180 return DecoratingService.decorate(source, context, classLoader);
181 }
182
183 };
184 }
185
186 private static class ClassLoaderDecoratorsEntry {
187 private static final String DECORATE = "decorate";
188
189 Collection<Object> atomicDecorators = new ArrayList<Object>();
190
191 void addDecorators(final Object provider) {
192 if (provider instanceof AtomicDecorator) {
193 atomicDecorators.add(provider);
194 } else if (provider instanceof AtomicDecoratorsBundle) {
195 atomicDecorators.addAll(((AtomicDecoratorsBundle) provider).getDecorators());
196 } else if (provider instanceof Collection) {
197 Iterator it = ((Collection) provider).iterator();
198 while (it.hasNext()) {
199 addDecorators(it.next());
200 }
201 } else { // Introspection
202 Class<? extends Object> pClass = provider.getClass();
203 Method[] methods = pClass.getMethods();
204 for (int i=0; i<methods.length; ++i) {
205 final Method method = methods[i];
206 if (DECORATE.equals(method.getName()) && !void.class.equals(method.getReturnType())) {
207 final Class[] pTypes = method.getParameterTypes();
208 if (pTypes.length == 1) {
209 atomicDecorators.add(new AtomicDecorator() {
210
211 public Object decorate(Object source, Context context, Decorator master) {
212 try {
213 return method.invoke(provider, new Object[] {source});
214 } catch (IllegalAccessException e) {
215 throw new DecorationException(e);
216 } catch (InvocationTargetException e) {
217 throw new DecorationException(e);
218 }
219 }
220
221 public Class getSourceType() {
222 return pTypes[0];
223 }
224
225 });
226 } else if (pTypes.length == 2 && pTypes[1].equals(Decorator.class)) {
227 atomicDecorators.add(new AtomicDecorator() {
228
229 public Object decorate(Object source, Context context, Decorator master) {
230 try {
231 return method.invoke(provider, new Object[] {source, master});
232 } catch (IllegalAccessException e) {
233 throw new DecorationException(e);
234 } catch (InvocationTargetException e) {
235 throw new DecorationException(e);
236 }
237 }
238
239 public Class getSourceType() {
240 return pTypes[0];
241 }
242
243 });
244 } else if (pTypes.length == 2 && pTypes[1].equals(Context.class)) {
245 atomicDecorators.add(new AtomicDecorator() {
246
247 public Object decorate(Object source, Context context, Decorator master) {
248 try {
249 return method.invoke(provider, new Object[] {source, context});
250 } catch (IllegalAccessException e) {
251 throw new DecorationException(e);
252 } catch (InvocationTargetException e) {
253 throw new DecorationException(e);
254 }
255 }
256
257 public Class getSourceType() {
258 return pTypes[0];
259 }
260
261 });
262 } else if (pTypes.length == 3 && pTypes[1].equals(Context.class) && pTypes[1].equals(Decorator.class)) {
263 atomicDecorators.add(new AtomicDecorator() {
264
265 public Object decorate(Object source, Context context, Decorator master) {
266 try {
267 return method.invoke(provider, new Object[] {source, context, master});
268 } catch (IllegalAccessException e) {
269 throw new DecorationException(e);
270 } catch (InvocationTargetException e) {
271 throw new DecorationException(e);
272 }
273 }
274
275 public Class getSourceType() {
276 return pTypes[0];
277 }
278
279 });
280 }
281
282 }
283 }
284 }
285 }
286
287 void collectDecorators(Object obj, Map<Class, Object> interfaceMap, Context context, Decorator master) {
288 Iterator<Object> it = atomicDecorators.iterator();
289 while (it.hasNext()) {
290 AtomicDecorator<Object> ad = (AtomicDecorator<Object>) it.next();
291 if (ad.getSourceType().isInstance(obj)) {
292 Object decoration = ad.decorate(obj, context, master);
293 if (decoration!=null) {
294 boolean cascade = false;
295 Class[] dia = DuckConverterFactory.getClassInterfaces(decoration.getClass());
296 for (int i=0; i<dia.length; ++i) {
297 if (!interfaceMap.containsKey(dia[i])) {
298 cascade=true;
299 interfaceMap.put(dia[i], decoration);
300 }
301 }
302 if (cascade) {
303 collectDecorators(decoration, interfaceMap, context, master);
304 }
305 }
306 }
307 }
308 }
309
310 }
311
312 /**
313 * Map of resolved converters: ClassLoader -> ClassLoaderConvertersEntry.
314 */
315 private static Map<ClassLoader, ClassLoaderDecoratorsEntry> classLoaderEntries = new HashMap<ClassLoader, ClassLoaderDecoratorsEntry>();
316
317
318 }