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    }