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}