    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    }