001 package biz.hammurapi.cache; 002 003 import java.lang.ref.SoftReference; 004 import java.lang.reflect.Method; 005 import java.lang.reflect.Proxy; 006 import java.util.ArrayList; 007 import java.util.List; 008 import java.util.Map; 009 import java.util.concurrent.ConcurrentHashMap; 010 011 import biz.hammurapi.convert.FilterInvocationHandler; 012 import biz.hammurapi.wrap.WrapperHandler; 013 014 /** 015 * Creates proxy which caches return values of getXXX() method invocations 016 * in soft references 017 * @author Pavel 018 * 019 */ 020 public class SoftCachingProxyFactory { 021 022 private static final String GET = "get"; 023 024 /** 025 * Creates proxy which caches return values of getXXX() method 026 * invocations in soft references. Return values are keyed by 027 * method declaring type, method name, parameter types, and parameter 028 * values. Internal map is not cleaned up when SoftReference is cleared and 029 * entries do not have expiration time. 030 * Cache entries are invalidated when non-getXXX method is invoked 031 * and if that method is not declared by Object (i.e. hashCode() or 032 * equals() do not invalidate cache). 033 * The proxy prevent repeated invocations of the same method with same arguments 034 * from multiple threads. I.e. if one thread invoked a method and method invocation 035 * is in progress, other threads invoking same method with same parameters during 036 * method invocation will block and will receive the same return value. 037 * @return Caching proxy or master, if master doesn't implement any interfaces. 038 */ 039 public static Object createCachingProxy(final Object master) { 040 if (master==null) { 041 return null; 042 } 043 044 Class[] interfaces = WrapperHandler.getClassInterfaces(master.getClass()); 045 if (interfaces.length==0) { 046 return master; 047 } 048 049 final Map getMap = new ConcurrentHashMap(); // List[declaring type, method name, param types, arg values -> Return value entry. 050 051 FilterInvocationHandler ih = new FilterInvocationHandler() { 052 053 public Object getMaster() { 054 return master; 055 } 056 057 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 058 if (method.getDeclaringClass().getName().equals("java.lang.Object")) { 059 return method.invoke(master, args); 060 } 061 062 if (!method.getName().startsWith(GET) || void.class.equals(method.getReturnType())) { 063 getMap.clear(); 064 return method.invoke(master, args); 065 } 066 067 List invocationKey = new ArrayList(); 068 invocationKey.add(method.getDeclaringClass().getName()); 069 invocationKey.add(method.getName()); 070 Class[] pTypes = method.getParameterTypes(); 071 for (int i=0; pTypes!=null && i<pTypes.length; ++i) { 072 invocationKey.add(pTypes[i].getName()); 073 } 074 for (int i=0; args!=null && i<args.length; ++i) { 075 invocationKey.add(args[i]); 076 } 077 078 ReturnValueEntry rve = (ReturnValueEntry) getMap.get(invocationKey); 079 if (rve!=null) { 080 return rve.get(); 081 } 082 083 rve = new ReturnValueEntry(); 084 getMap.put(invocationKey, rve); 085 086 try { 087 Object ret = method.invoke(master, args); 088 rve.set(ret); 089 return ret; 090 } catch (Throwable th) { 091 rve.setProblem(th); 092 throw th; 093 } 094 } 095 096 }; 097 098 099 return Proxy.newProxyInstance(master.getClass().getClassLoader(), interfaces, ih); 100 } 101 102 private static class ReturnValueEntry { 103 104 private boolean initialized; 105 106 private SoftReference ref; 107 108 private Throwable problem; 109 110 /** 111 * Sets master object and notifies all threads waiting in get() method. 112 * @param obj 113 */ 114 synchronized void set(Object obj) { 115 this.ref = new SoftReference(obj); 116 initialized = true; 117 notifyAll(); 118 } 119 120 /** 121 * Sets master object and notifies all threads waiting in get() method. 122 * @param obj 123 */ 124 synchronized void setProblem(Throwable th) { 125 problem = th; 126 initialized = true; 127 notifyAll(); 128 } 129 130 /** 131 * Blocks until the master object is available 132 * @return master object. 133 */ 134 public synchronized Object get() throws Throwable { 135 if (!initialized) { 136 wait(); 137 } 138 139 if (problem!=null) { 140 throw problem; 141 } 142 143 return ref==null ? null : ref.get(); 144 } 145 146 } 147 148 }