SoftCachingProxyFactory.java

biz/hammurapi/cache/SoftCachingProxyFactory.java

Violations

Inspector Message Severity Location
Java Inspector 048 Copyrights information should be present in each file. 1
Java Inspector 043 Call 'wait ()' only inside a "while" loop 2 136:37
Java Inspector 049 Use a Collection instead of arrays Object[] 2 44:22
Java Inspector 049 Use a Collection instead of arrays Object[] 2 70:38
Java Inspector 077 To reduce probability of NullPointerException, use "string literal".equals(variable) instead of variable.equals("string literal"). 2 58:80
Java Inspector 089 Undocumented parameter master 2 39:9
Java Inspector 089 Undocumented method 2 53:25
Java Inspector 089 Undocumented method 2 57:25
Java Inspector 089 Undocumented exception Throwable 2 134:17
Java Inspector 026 Avoid hardwired string literals. Allowed literals: [] 3 58:81

Source code

1package biz.hammurapi.cache;
2
3import java.lang.ref.SoftReference;
4import java.lang.reflect.Method;
5import java.lang.reflect.Proxy;
6import java.util.ArrayList;
7import java.util.List;
8import java.util.Map;
9import java.util.concurrent.ConcurrentHashMap;
10
11import biz.hammurapi.convert.FilterInvocationHandler;
12import biz.hammurapi.wrap.WrapperHandler;
13
14/**
15 * Creates proxy which caches return values of getXXX() method invocations
16 * in soft references
17 * @author Pavel
18 *
19 */
20public class SoftCachingProxyFactory {
21
22 private static final String GET = "get";
23
24 /**
25 * Creates proxy which caches return values of getXXX() method
26 * invocations in soft references. Return values are keyed by
27 * method declaring type, method name, parameter types, and parameter
28 * values. Internal map is not cleaned up when SoftReference is cleared and
29 * entries do not have expiration time.
30 * Cache entries are invalidated when non-getXXX method is invoked
31 * and if that method is not declared by Object (i.e. hashCode() or
32 * equals() do not invalidate cache).
33 * The proxy prevent repeated invocations of the same method with same arguments
34 * from multiple threads. I.e. if one thread invoked a method and method invocation
35 * is in progress, other threads invoking same method with same parameters during
36 * method invocation will block and will receive the same return value.
37 * @return Caching proxy or master, if master doesn't implement any interfaces.
38 */
39 public static Object createCachingProxy(final Object master) {
40 if (master==null) {
41 return null;
42 }
43
44 Class[] interfaces = WrapperHandler.getClassInterfaces(master.getClass());
45 if (interfaces.length==0) {
46 return master;
47 }
48
49 final Map getMap = new ConcurrentHashMap(); // List[declaring type, method name, param types, arg values -> Return value entry.
50
51 FilterInvocationHandler ih = new FilterInvocationHandler() {
52
53 public Object getMaster() {
54 return master;
55 }
56
57 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
58 if (method.getDeclaringClass().getName().equals("java.lang.Object")) {
59 return method.invoke(master, args);
60 }
61
62 if (!method.getName().startsWith(GET) || void.class.equals(method.getReturnType())) {
63 getMap.clear();
64 return method.invoke(master, args);
65 }
66
67 List invocationKey = new ArrayList();
68 invocationKey.add(method.getDeclaringClass().getName());
69 invocationKey.add(method.getName());
70 Class[] pTypes = method.getParameterTypes();
71 for (int i=0; pTypes!=null && i<pTypes.length; ++i) {
72 invocationKey.add(pTypes[i].getName());
73 }
74 for (int i=0; args!=null && i<args.length; ++i) {
75 invocationKey.add(args[i]);
76 }
77
78 ReturnValueEntry rve = (ReturnValueEntry) getMap.get(invocationKey);
79 if (rve!=null) {
80 return rve.get();
81 }
82
83 rve = new ReturnValueEntry();
84 getMap.put(invocationKey, rve);
85
86 try {
87 Object ret = method.invoke(master, args);
88 rve.set(ret);
89 return ret;
90 } catch (Throwable th) {
91 rve.setProblem(th);
92 throw th;
93 }
94 }
95
96 };
97
98
99 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}
149