1 | package com.hammurapi.eventbus; |
2 | |
3 | import java.lang.annotation.Annotation; |
4 | import java.lang.reflect.Method; |
5 | import java.util.ArrayList; |
6 | import java.util.Collection; |
7 | import java.util.Collections; |
8 | import java.util.List; |
9 | import java.util.Map; |
10 | import java.util.Set; |
11 | |
12 | import org.apache.bcel.classfile.JavaClass; |
13 | import org.apache.bcel.classfile.LocalVariable; |
14 | import org.apache.bcel.classfile.LocalVariableTable; |
15 | import org.apache.bcel.util.ClassLoaderRepository; |
16 | |
17 | import com.hammurapi.common.Condition; |
18 | import com.hammurapi.common.TokenExpander; |
19 | import com.hammurapi.extract.And; |
20 | import com.hammurapi.extract.CommutativeAnd; |
21 | import com.hammurapi.extract.ComparisonResult; |
22 | import com.hammurapi.extract.Extractor; |
23 | import com.hammurapi.extract.ExtractorFactory; |
24 | import com.hammurapi.extract.IndexedExtractor; |
25 | import com.hammurapi.extract.InstanceOfPredicate; |
26 | import com.hammurapi.extract.Predicate; |
27 | |
28 | /** |
29 | * Base class for Introspector and compiler. This class provides introspecting functionality. |
30 | * @author Pavel Vlasov |
31 | * |
32 | */ |
33 | public class IntrospectorBase<E,C> { |
34 | |
35 | public static class IntrospectionResult<E,C> { |
36 | |
37 | private Method method; |
38 | private Predicate<E,C>[] predicates; |
39 | private int offset; |
40 | private Class<E>[] parameterTypes; |
41 | private Handler handlerAnnotation; |
42 | |
43 | public Method getMethod() { |
44 | return method; |
45 | } |
46 | public Predicate<E, C>[] getPredicates() { |
47 | return predicates; |
48 | } |
49 | public int getOffset() { |
50 | return offset; |
51 | } |
52 | public Class<E>[] getParameterTypes() { |
53 | return parameterTypes; |
54 | } |
55 | public Handler getHandlerAnnotation() { |
56 | return handlerAnnotation; |
57 | } |
58 | |
59 | IntrospectionResult( |
60 | Method method, |
61 | int offset, |
62 | Class<E>[] parameterTypes, |
63 | Handler handlerAnnotation, |
64 | Predicate<E, C>... predicates) { |
65 | |
66 | super(); |
67 | this.method = method; |
68 | this.predicates = predicates; |
69 | this.offset = offset; |
70 | this.parameterTypes = parameterTypes; |
71 | this.handlerAnnotation = handlerAnnotation; |
72 | } |
73 | |
74 | } |
75 | |
76 | private static final String ARG = "arg"; |
77 | private static final String SEPARATOR = "://"; |
78 | private static final String JAVA = "java(*)"; |
79 | private ClassLoader classLoader; |
80 | private TokenExpander tokenExpander; |
81 | |
82 | public IntrospectorBase(ClassLoader classLoader, TokenExpander tokenExpander) { |
83 | this.classLoader = classLoader; |
84 | this.tokenExpander = tokenExpander; |
85 | } |
86 | |
87 | protected Collection<IntrospectionResult<E,C>> introspect(Class<C> handlerClass, Class<E> eventType) { |
88 | Collection<IntrospectionResult<E,C>> ret = new ArrayList<IntrospectionResult<E,C>>(); |
89 | for (Method method: handlerClass.getMethods()) { |
90 | final Method mthd = method; |
91 | final Handler ha = method.getAnnotation(Handler.class); |
92 | if (ha!=null) { |
93 | Collection<Predicate<E, C>> typeGuards = new ArrayList<Predicate<E, C>>(); |
94 | List<Predicate<E, C>> conditions = new ArrayList<Predicate<E, C>>(); |
95 | Class<E>[] pTypes = (Class<E>[]) method.getParameterTypes(); |
96 | Annotation[][] panna = method.getParameterAnnotations(); |
97 | boolean hedcp = pTypes.length>1 && EventDispatchContext.class.isAssignableFrom(pTypes[0]); |
98 | final int offset = hedcp ? 1 : 0; |
99 | |
100 | // Adjust parameters |
101 | final Class<E>[] pt; |
102 | if (hedcp) { |
103 | pt = new Class[pTypes.length-1]; |
104 | System.arraycopy(pTypes, 1, pt, 0, pt.length); |
105 | } else { |
106 | pt = pTypes; |
107 | } |
108 | for (int i=0; i<pt.length; ++i) { |
109 | Class<? extends E> parameterType = pt[i]; |
110 | // If parameter is a subclass of bus event type - create instanceof predicate. |
111 | if (!parameterType.equals(eventType)) { |
112 | // Stupid, but doesn't compile in command line otherwise. |
113 | Object indexedExtractor = new IndexedExtractor<E, C>(i); |
114 | Extractor<E, Object, C> extractor = (Extractor<E, Object, C>) indexedExtractor; |
115 | InstanceOfPredicate<E, C> io = new InstanceOfPredicate<E, C>(extractor, parameterType); |
116 | |
117 | typeGuards.add(io); |
118 | } |
119 | |
120 | for (Annotation pAnn: panna[i+offset]) { |
121 | if (pAnn instanceof Condition) { |
122 | for (String cnd: ((Condition) pAnn).value()) { |
123 | conditions.add(createPredicate(handlerClass, cnd, pt, i, method)); |
124 | } |
125 | } |
126 | } |
127 | } |
128 | for (String cnd: ha.value()) { |
129 | conditions.add(createPredicate(handlerClass, cnd, pt, -1, method)); |
130 | } |
131 | |
132 | |
133 | if (typeGuards.isEmpty()) { |
134 | Predicate<E, C>[] pa = (Predicate<E, C>[]) conditions.toArray(new Predicate[conditions.size()]); |
135 | ret.add(new IntrospectionResult<E, C>(method, offset, pt, ha, pa)); |
136 | } else if (conditions.isEmpty()) { |
137 | Predicate<E, C>[] pa = (Predicate<E, C>[]) typeGuards.toArray(new Predicate[typeGuards.size()]); |
138 | ret.add(new IntrospectionResult<E, C>(method, offset, pt, ha, pa)); |
139 | } else { |
140 | CommutativeAnd<E, C> tg = new CommutativeAnd<E, C>(0, null, typeGuards); |
141 | |
142 | Collections.sort(conditions, new PredicateCostCardinalityComparator<E, C>()); |
143 | CommutativeAnd<E, C> cnd = new CommutativeAnd<E, C>(0, null, conditions); |
144 | ret.add(new IntrospectionResult<E, C>(method, offset, pt, ha, new And<E, C>(0, null, tg.normalize(), cnd.normalize()).normalize())); |
145 | } |
146 | } |
147 | } |
148 | return ret; |
149 | } |
150 | |
151 | /** |
152 | * Creates predicate from string representation. |
153 | * @param instance |
154 | * @param cnd |
155 | * @param parameterTypes |
156 | * @param classLoader |
157 | * @param parameterIndex Parameter index for parameter level predicates, -1 for method level predicates. |
158 | * @return |
159 | */ |
160 | @SuppressWarnings("unchecked") |
161 | private Predicate<E, C> createPredicate( |
162 | Class<C> contextType, |
163 | String cnd, |
164 | final Class<E>[] parameterTypes, |
165 | final int parameterIndex, |
166 | Method method) { |
167 | int separatorIdx = cnd.indexOf(SEPARATOR); |
168 | if (separatorIdx>0) { |
169 | int quoteIdx = cnd.indexOf("\""); |
170 | if (quoteIdx>=0 && quoteIdx<separatorIdx) { |
171 | separatorIdx = -1; |
172 | } else { |
173 | quoteIdx = cnd.indexOf("'"); |
174 | if (quoteIdx>=0 && quoteIdx<separatorIdx) { |
175 | separatorIdx = -1; |
176 | } |
177 | } |
178 | } |
179 | |
180 | String languageName = separatorIdx==-1 ? JAVA : cnd.substring(0, separatorIdx); |
181 | if (separatorIdx>0) { |
182 | cnd = cnd.substring(separatorIdx+SEPARATOR.length()); |
183 | } |
184 | |
185 | // Parameter names are in quotes after language name, if it is explicitly specified |
186 | String[] parameterNames = new String[parameterTypes.length]; |
187 | boolean hasParameterNames = false; |
188 | int lpidx = languageName.indexOf("("); |
189 | if (lpidx>0) { |
190 | int rpidx = languageName.indexOf(")"); |
191 | if (rpidx>lpidx) { |
192 | hasParameterNames = true; |
193 | String[] pNames = parameterIndex==-1 ? languageName.substring(lpidx+1, rpidx).split(",") : new String[] {languageName.substring(lpidx+1, rpidx)}; |
194 | if (pNames.length>parameterNames.length) { |
195 | throw new DispatchNetworkException("Number of parameter names is greater than number of parameters"); |
196 | } |
197 | if (pNames.length==1 && pNames[0]!=null && "*".equals(pNames[0].trim())) { |
198 | pNames = parameterNames(method, parameterIndex); |
199 | } |
200 | for (int i=0; i<pNames.length; ++i) { |
201 | if (parameterIndex==-1) { |
202 | String pName = pNames[i].trim(); |
203 | if (pName.length()>0) { |
204 | parameterNames[i] = pName; |
205 | } |
206 | } else if (i==0) { |
207 | parameterNames[parameterIndex] = pNames[i]; |
208 | } |
209 | } |
210 | languageName=languageName.substring(0, lpidx); |
211 | } |
212 | } |
213 | |
214 | if (!hasParameterNames && parameterIndex!=-1) { |
215 | parameterNames[parameterIndex]=ARG; |
216 | } |
217 | |
218 | Extractor<E, Boolean, C> extractor = ExtractorFactory.INSTANCE.createExtractor(languageName, tokenExpander==null ? cnd : tokenExpander.expand(cnd), parameterNames, parameterTypes, Boolean.class, contextType, classLoader); |
219 | |
220 | if (extractor instanceof Predicate) { |
221 | return (Predicate<E, C>) extractor; |
222 | } |
223 | |
224 | if (extractor==null) { |
225 | throw new DispatchNetworkException("Could not create extractor for language "+languageName); |
226 | } |
227 | |
228 | return new PredicateWrapper<E,C>(extractor); |
229 | } |
230 | |
231 | /** |
232 | * Extracts parameter names from class file. Requires the class to be compiled with |
233 | * -g or -g:vars |
234 | * @param method |
235 | * @return |
236 | */ |
237 | private String[] parameterNames(Method method, int parameterIndex) { |
238 | try { |
239 | ClassLoaderRepository clr = new ClassLoaderRepository(method.getDeclaringClass().getClassLoader()); |
240 | JavaClass jc = clr.loadClass(method.getDeclaringClass()); |
241 | org.apache.bcel.classfile.Method jmth = jc.getMethod(method); |
242 | LocalVariableTable lvt = jmth.getLocalVariableTable(); |
243 | if (lvt==null) { |
244 | throw new DispatchNetworkException("Cannot retrieve parameter names from the class file, make sure it is compiled with debug information (-g or -g:vars option)"); |
245 | } |
246 | |
247 | Class<?>[] pTypes = method.getParameterTypes(); |
248 | boolean hasEventDispatchContextParameter = pTypes.length>1 && EventDispatchContext.class.isAssignableFrom(pTypes[0]); |
249 | |
250 | LocalVariable[] alvt = lvt.getLocalVariableTable(); |
251 | String[] ret = new String[hasEventDispatchContextParameter? method.getParameterTypes().length - 1 : method.getParameterTypes().length]; |
252 | int offset = jmth.isStatic() ? 0 : 1; |
253 | |
254 | if (hasEventDispatchContextParameter) { |
255 | ++offset; |
256 | } |
257 | |
258 | for (int i=0; i<ret.length; ++i) { |
259 | ret[i] = alvt[i+offset].getName(); |
260 | } |
261 | return parameterIndex==-1 ? ret : new String[] {ret[parameterIndex]}; |
262 | } catch (ClassNotFoundException e) { |
263 | throw new DispatchNetworkException(e); |
264 | } |
265 | } |
266 | |
267 | } |
268 | |
269 | class PredicateWrapper<E, C> implements Predicate<E, C> { |
270 | |
271 | private Extractor<E, Boolean, C> extractor; |
272 | |
273 | public PredicateWrapper(Extractor<E, Boolean, C> extractor) { |
274 | this.extractor = extractor; |
275 | } |
276 | |
277 | @Override |
278 | public ComparisonResult compareTo(Extractor<E, Boolean, C> otherPredicate) { |
279 | return equals(otherPredicate) ? ComparisonResult.EQUAL_NM : ComparisonResult.NOT_EQUAL_NM; |
280 | } |
281 | |
282 | @Override |
283 | public Boolean extract( |
284 | C context, |
285 | Map<C, Map<Extractor<E, ? super Boolean, C>, ? super Boolean>> cache, |
286 | E... obj) { |
287 | |
288 | return extractor.extract(context, cache, obj); |
289 | } |
290 | |
291 | @Override |
292 | public boolean isContextDependent() { |
293 | return extractor.isContextDependent(); |
294 | } |
295 | |
296 | @Override |
297 | public Set<Integer> parameterIndices() { |
298 | return extractor.parameterIndices(); |
299 | } |
300 | |
301 | @SuppressWarnings("unchecked") |
302 | @Override |
303 | public boolean equals(Object otherPredicate) { |
304 | if (this==otherPredicate) { |
305 | return true; |
306 | } |
307 | |
308 | return otherPredicate instanceof PredicateWrapper && extractor.equals(((PredicateWrapper<E,C>) otherPredicate).extractor); |
309 | } |
310 | |
311 | @Override |
312 | public double getCost() { |
313 | return extractor.getCost(); |
314 | } |
315 | |
316 | } |