    001package com.hammurapi.common.concurrent;
    002
    003import java.io.BufferedReader;
    004import java.io.IOException;
    005import java.io.InputStreamReader;
    006import java.lang.reflect.InvocationHandler;
    007import java.lang.reflect.Method;
    008import java.lang.reflect.Proxy;
    009import java.net.URL;
    010import java.util.ArrayList;
    011import java.util.Arrays;
    012import java.util.Enumeration;
    013import java.util.HashSet;
    014import java.util.List;
    015import java.util.Set;
    016import java.util.concurrent.ExecutorService;
    017import java.util.concurrent.locks.Lock;
    018import java.util.concurrent.locks.ReadWriteLock;
    019import java.util.concurrent.locks.ReentrantReadWriteLock;
    020
    021import com.hammurapi.common.Context;
    022import com.hammurapi.convert.Converter;
    023
    024public class LocalStringPropertySet extends     LocalAbstractPropertySet<String, String> {
    025
    026        private static final String SETTER_PREFIX = "set";
    027        private static final int SETTER_PREFIX_LENGTH = SETTER_PREFIX.length();
    028        private static final String GETTER_PREFIX = "get";
    029        private static final int GETTER_PREFIX_LENGTH = GETTER_PREFIX.length();
    030
    031        private String separator;
    032        private ClassLoader classLoader;
    033        private ReadWriteLock lock;
    034        private Set<String> immutableClasses = new HashSet<String>();
    035
    036        public LocalStringPropertySet(
    037                        ExecutorService executorService,
    038                        Converter converter,
    039                        Context context,
    040                        ClassLoader classLoader,
    041                        PropertySet<String>... chain) {
    042                this(executorService, converter, context, "/", classLoader, chain);
    043        }
    044
    045        public LocalStringPropertySet(
    046                        ExecutorService executorService,
    047                        Converter converter,
    048                        Context context,
    049                        String separator,
    050                        ClassLoader classLoader,
    051                        PropertySet<String>... chain) {
    052                this(executorService, converter, context, separator, classLoader, new ReentrantReadWriteLock(), chain);
    053                this.separator = separator;
    054                this.classLoader = classLoader==null ? getClass().getClassLoader() : classLoader;
    055        }
    056
    057        protected LocalStringPropertySet(
    058                        ExecutorService executorService,
    059                        Converter converter,
    060                        Context context,
    061                        String separator,
    062                        ClassLoader classLoader,
    063                        ReadWriteLock lock,
    064                        PropertySet<String>... chain) {
    065                super(executorService, converter, context, chain);
    066                this.separator = separator;
    067                this.classLoader = classLoader==null ? getClass().getClassLoader() : classLoader;
    068                try {
    069                        Enumeration<URL> immutableResources = this.classLoader.getResources("META-INF/services/"+Immutable.class.getName());
    070                        while (immutableResources.hasMoreElements()) {
    071                                BufferedReader br = new BufferedReader(new InputStreamReader(immutableResources.nextElement().openStream()));
    072                                String l;
    073                                while ((l=br.readLine())!=null) {
    074                                        immutableClasses.add(l.trim());
    075                                }
    076                        }
    077                } catch (IOException e) {
    078                        throw new PropertySetException("Cannot load list of immutable classes: "+e, e);
    079                }
    080                this.lock = lock;
    081        }
    082
    083        @Override
    084        protected String buildPath(int startIdx, Object... elements) {
    085                StringBuilder sb = new StringBuilder();
    086                for (int i=startIdx; i < elements.length; ++i) {
    087                        if (i>startIdx) {
    088                                sb.append(separator);
    089                        }
    090                        sb.append(elements[i]);
    091                }
    092                return sb.toString();
    093        }
    094
    095        @Override
    096        protected String buildPath(String prefix, Object... elements) {
    097                StringBuilder sb = new StringBuilder(prefix);
    098                for (int i=0; i < elements.length; ++i) {
    099                        sb.append(separator);
    100                        sb.append(elements[i]);
    101                }
    102                return sb.toString();
    103        }
    104
    105        @Override
    106        protected String buildPath(String prefix, String suffix) {
    107                return prefix+separator+suffix;
    108        }
    109
    110        @Override
    111        protected String[] tokenize(String path) {
    112                List<String> ret = new ArrayList<String>();
    113                for (int idx = path.indexOf(separator); idx!=-1; idx = path.indexOf(separator)) {
    114                        ret.add(path.substring(0, idx));
    115                        path = path.substring(idx+separator.length());
    116                }
    117                if (!path.isEmpty()) {
    118                        ret.add(path);
    119                }
    120                return ret.toArray(new String[ret.size()]);
    121        }
    122
    123        @Override
    124        public <T> T getAdapter(final Class<T> targetType, final Context context) {
    125                if (targetType.isInterface()) {
    126                        InvocationHandler invocationHandler = new InvocationHandler() {
    127
    128                                @Override
    129                                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    130                                        if (method.getDeclaringClass().equals(targetType)) {
    131                                                Class<?>[] parameterTypes = method.getParameterTypes();
    132                                                Class<?> returnType = method.getReturnType();
    133                                                String name = method.getName();
    134                                                if (name.startsWith(GETTER_PREFIX) && name.length()>GETTER_PREFIX.length() && parameterTypes.length==0 && !void.class.equals(returnType)) { // Getters
    135                                                        String firstChar = name.substring(GETTER_PREFIX_LENGTH, GETTER_PREFIX_LENGTH+1).toLowerCase();
    136                                                        String pName = name.length()>GETTER_PREFIX.length()+1 ? firstChar + name.substring(GETTER_PREFIX.length()+1) : firstChar;
    137                                                        Object ret = get(pName, returnType);
    138                                                        if (ret==null || returnType.isInstance(ret)) {
    139                                                                return ret;
    140                                                        }
    141                                                        Object cRet = converter.convert(ret, returnType, context);
    142                                                        if (cRet==null) {
    143                                                                throw new PropertySetException("Cannot convert from "+ret+" to "+returnType);
    144                                                        }
    145                                                        return cRet;
    146                                                }
    147
    148                                                if (name.startsWith(SETTER_PREFIX) && name.length()>SETTER_PREFIX.length() && parameterTypes.length==1 && void.class.equals(returnType)) { // Setters
    149                                                        String firstChar = name.substring(SETTER_PREFIX_LENGTH, SETTER_PREFIX_LENGTH+1).toLowerCase();
    150                                                        String pName = name.length()>SETTER_PREFIX.length()+1 ? firstChar + name.substring(SETTER_PREFIX.length()+1) : firstChar;
    151                                                        set(pName, args[0]);
    152                                                        return null;
    153                                                }
    154
    155                                                // Invoke invocable
    156                                                Object ret = LocalStringPropertySet.this.invoke(name, args);
    157                                                if (ret==null || returnType.isInstance(ret)) {
    158                                                        return ret;
    159                                                }
    160                                                Object cRet = converter.convert(ret, returnType, context);
    161                                                if (cRet==null) {
    162                                                        throw new PropertySetException("Cannot convert from "+ret+" to "+returnType);
    163                                                }
    164                                                return cRet;
    165                                        }
    166                                        // Object methods.
    167                                        return method.invoke(proxy, args);
    168                                }
    169
    170                        };
    171                        return (T) Proxy.newProxyInstance(classLoader, new Class[] {targetType}, invocationHandler);
    172                }
    173
    174                return null;
    175        }
    176
    177        @Override
    178        protected <T> T version(T source) {
    179                if (source==null) {
    180                        return source;
    181                }
    182                Class<? extends Object> sourceClass = source.getClass();
    183                if (immutableClasses.contains(sourceClass.getName())) {
    184                        return source;
    185                }
    186                if (sourceClass.getAnnotation(Immutable.class)!=null) {
    187                        return source;
    188                }
    189                Versionable<T> v = converter.convert(source, Versionable.class, context);
    190                if (v!=null) {
    191                        return v.createVersion();
    192                }
    193                if (source instanceof Cloneable) {
    194                        try {
    195                                return (T) sourceClass.getMethod("clone").invoke(source);
    196                        } catch (Exception e) {
    197                                throw new PropertySetException("Cannot version "+source+" by cloning", e);
    198                        }
    199                }
    200
    201                return source; // Not immutable, not versionable, not cloneable - return AS IS.
    202        }
    203
    204        @Override
    205        protected AbstractPropertySet<String, String> newInstance(String key) {
    206                PropertySet<String>[] subChain = Arrays.copyOf(shadows, shadows.length);
    207                for (int i=0; i<subChain.length; ++i) {
    208                        subChain[i] = new StringSubSet(shadows[i], key, separator);
    209                }
    210                return new LocalStringPropertySet(
    211                                executorService,
    212                                converter,
    213                                context,
    214                                separator,
    215                                classLoader,
    216                                lock,
    217                                subChain);
    218        }
    219
    220        @Override
    221        public Lock readLock() {
    222                return lock.readLock();
    223        }
    224
    225        @Override
    226        public Lock writeLock() {
    227                return lock.writeLock();
    228        }
    229
    230        @Override
    231        public PropertySet<String> createVersion() {
    232                throw new UnsupportedOperationException();
    233        }
    234}