001package com.hammurapi.convert; 002 003import java.lang.reflect.InvocationTargetException; 004import java.lang.reflect.Method; 005import java.lang.reflect.Proxy; 006import java.util.ArrayList; 007import java.util.Collection; 008import java.util.HashMap; 009import java.util.Iterator; 010import java.util.Map; 011import java.util.ServiceLoader; 012 013import com.hammurapi.common.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 */ 021public 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}