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 -&gt; ClassLoaderConvertersEntry. 
314             */
315            private static Map<ClassLoader, ClassLoaderDecoratorsEntry> classLoaderEntries = new HashMap<ClassLoader, ClassLoaderDecoratorsEntry>(); 
316    
317    
318    }