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