    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}