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    }