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}