001 package com.hammurapi.eventbus;
002
003 import java.lang.annotation.Annotation;
004 import java.lang.reflect.Method;
005 import java.util.ArrayList;
006 import java.util.Collection;
007 import java.util.Collections;
008 import java.util.List;
009 import java.util.Map;
010 import java.util.Set;
011
012 import org.apache.bcel.classfile.JavaClass;
013 import org.apache.bcel.classfile.LocalVariable;
014 import org.apache.bcel.classfile.LocalVariableTable;
015 import org.apache.bcel.util.ClassLoaderRepository;
016
017 import com.hammurapi.common.Condition;
018 import com.hammurapi.common.TokenExpander;
019 import com.hammurapi.extract.And;
020 import com.hammurapi.extract.CommutativeAnd;
021 import com.hammurapi.extract.ComparisonResult;
022 import com.hammurapi.extract.Extractor;
023 import com.hammurapi.extract.ExtractorFactory;
024 import com.hammurapi.extract.IndexedExtractor;
025 import com.hammurapi.extract.InstanceOfPredicate;
026 import com.hammurapi.extract.Predicate;
027
028 /**
029 * Base class for Introspector and compiler. This class provides introspecting functionality.
030 * @author Pavel Vlasov
031 *
032 */
033 public class IntrospectorBase<E,C> {
034
035 public static class IntrospectionResult<E,C> {
036
037 private Method method;
038 private Predicate<E,C>[] predicates;
039 private int offset;
040 private Class<E>[] parameterTypes;
041 private Handler handlerAnnotation;
042
043 public Method getMethod() {
044 return method;
045 }
046 public Predicate<E, C>[] getPredicates() {
047 return predicates;
048 }
049 public int getOffset() {
050 return offset;
051 }
052 public Class<E>[] getParameterTypes() {
053 return parameterTypes;
054 }
055 public Handler getHandlerAnnotation() {
056 return handlerAnnotation;
057 }
058
059 IntrospectionResult(
060 Method method,
061 int offset,
062 Class<E>[] parameterTypes,
063 Handler handlerAnnotation,
064 Predicate<E, C>... predicates) {
065
066 super();
067 this.method = method;
068 this.predicates = predicates;
069 this.offset = offset;
070 this.parameterTypes = parameterTypes;
071 this.handlerAnnotation = handlerAnnotation;
072 }
073
074 }
075
076 private static final String ARG = "arg";
077 private static final String SEPARATOR = "://";
078 private static final String JAVA = "java(*)";
079 private ClassLoader classLoader;
080 private TokenExpander tokenExpander;
081
082 public IntrospectorBase(ClassLoader classLoader, TokenExpander tokenExpander) {
083 this.classLoader = classLoader;
084 this.tokenExpander = tokenExpander;
085 }
086
087 protected Collection<IntrospectionResult<E,C>> introspect(Class<C> handlerClass, Class<E> eventType) {
088 Collection<IntrospectionResult<E,C>> ret = new ArrayList<IntrospectionResult<E,C>>();
089 for (Method method: handlerClass.getMethods()) {
090 final Method mthd = method;
091 final Handler ha = method.getAnnotation(Handler.class);
092 if (ha!=null) {
093 Collection<Predicate<E, C>> typeGuards = new ArrayList<Predicate<E, C>>();
094 List<Predicate<E, C>> conditions = new ArrayList<Predicate<E, C>>();
095 Class<E>[] pTypes = (Class<E>[]) method.getParameterTypes();
096 Annotation[][] panna = method.getParameterAnnotations();
097 boolean hedcp = pTypes.length>1 && EventDispatchContext.class.isAssignableFrom(pTypes[0]);
098 final int offset = hedcp ? 1 : 0;
099
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 }