    001    package com.hammurapi.reasoning.impl;
    002
    003    import java.io.File;
    004    import java.io.StringReader;
    005    import java.lang.annotation.Annotation;
    006    import java.lang.reflect.Method;
    007    import java.lang.reflect.Modifier;
    008    import java.net.URL;
    009    import java.util.ArrayList;
    010    import java.util.Arrays;
    011    import java.util.Collections;
    012    import java.util.Comparator;
    013    import java.util.HashMap;
    014    import java.util.HashSet;
    015    import java.util.Iterator;
    016    import java.util.List;
    017    import java.util.Map;
    018    import java.util.ServiceLoader;
    019    import java.util.Set;
    020    import java.util.logging.Level;
    021    import java.util.logging.Logger;
    022
    023    import org.eclipse.emf.common.util.Diagnostic;
    024    import org.eclipse.emf.common.util.TreeIterator;
    025    import org.eclipse.emf.common.util.URI;
    026    import org.eclipse.emf.ecore.EObject;
    027    import org.eclipse.emf.ecore.resource.Resource;
    028    import org.eclipse.emf.ecore.util.Diagnostician;
    029    import org.eclipse.emf.ecore.util.EcoreUtil;
    030
    031    import antlr.RecognitionException;
    032    import antlr.TokenStreamException;
    033    import antlr.collections.AST;
    034
    035    import com.hammurapi.config.Collection;
    036    import com.hammurapi.config.ConfigFactory;
    037    import com.hammurapi.config.Factory;
    038    import com.hammurapi.config.Named;
    039    import com.hammurapi.config.NamedCollection;
    040    import com.hammurapi.config.NamedObjectDefinition;
    041    import com.hammurapi.config.ObjectDefinition;
    042    import com.hammurapi.config.PropertySource;
    043    import com.hammurapi.config.bootstrap.ConfigurationException;
    044    import com.hammurapi.config.impl.ObjectDefinitionImpl;
    045    import com.hammurapi.config.runtime.FactoryConfig;
    046    import com.hammurapi.config.runtime.ResourceLoader;
    047    import com.hammurapi.config.util.ConfigUtil;
    048    import com.hammurapi.flow.Flow;
    049    import com.hammurapi.flow.FlowFactory;
    050    import com.hammurapi.flow.Node;
    051    import com.hammurapi.flow.Pin;
    052    import com.hammurapi.flow.Transition;
    053    import com.hammurapi.flow.runtime.impl.PassThroughTransition;
    054    import com.hammurapi.grammar.java.JavaLexer;
    055    import com.hammurapi.grammar.java.JavaRecognizer;
    056    import com.hammurapi.grammar.java.JavaTokenTypes;
    057    import com.hammurapi.reasoning.spi.Accept;
    058    import com.hammurapi.reasoning.spi.Condition;
    059    import com.hammurapi.reasoning.spi.Infer;
    060    import com.hammurapi.reasoning.spi.model.Rule;
    061    import com.hammurapi.reasoning.spi.model.RuleSet;
    062
    063    /**
    064     * Compiles (transforms) rule set to flow definition.
    065     * @author Pavel Vlasov
    066     *
    067     */
    068    public class RuleSetToFlowCompiler implements Constants {
    069
    070            private static final String PARAMETER_INDEX = "parameterIndex";
    071            private static final String TYPE = "type";
    072
    073            private static int pinCounter;
    074
    075            private static final Logger logger = Logger.getLogger(RuleSetToFlowCompiler.class.getName());
    076
    077            private class PinInfo {
    078                    int priority;
    079                    Pin pin;
    080                    int getPriority() {
    081                            return priority;
    082                    }
    083                    Pin getPin() {
    084                            return pin;
    085                    }
    086                    PinInfo(int priority, Pin pin) {
    087                            super();
    088                            this.priority = priority;
    089                            this.pin = pin;
    090                    }
    091            }
    092
    093            private class RuleInfo {
    094                    Rule rule;
    095                    Class<?> ruleClass;
    096
    097                    public RuleInfo(Rule rule, FactoryConfig ruleFactoryConfig) throws ConfigurationException, ClassNotFoundException {
    098                            this.rule = rule;
    099                            ClassLoader ruleClassLoader = ObjectDefinitionImpl.propertySourceClassLoader(rule, ruleFactoryConfig);
    100                            ruleClass = ruleClassLoader.loadClass(rule.getType());
    101                    }
    102            }
    103
    104            /**
    105             * Compiles rule set to flow.
    106             * @param ruleSet Rule set. The rule set gets modified during compilation and shall be
    107             * discarded after compilation.
    108             * @param classLoader Classloader to load rule classes for introspection. If null, then
    109             * this class' classloader is used.
    110             * @return Flow definition.
    111             * @throws ConfigurationException
    112             */
    113            public Flow compile(RuleSet source, FactoryConfig factoryConfig) throws ConfigurationException {
    114                    Flow ret = FlowFactory.eINSTANCE.createFlow();
    115                    RuleSet ruleSet = (RuleSet) EcoreUtil.copy(source);
    116    //              ret.setFacadeInterface(ForwardReasoningSession.class.getName());
    117                    ret.setDescription("Compiled rule set "+ruleSet.getName());
    118
    119                    if (ruleSet.getName()!=null) {
    120                            ConfigUtil.addScalarProperty(ret, "name", ruleSet.getName(), true);
    121                    }
    122
    123                    if (ruleSet.getDescription()!=null) {
    124                            ConfigUtil.addScalarProperty(ret, "description", ruleSet.getDescription(), true);
    125                    }
    126
    127                    Pin flowInputPin = FlowFactory.eINSTANCE.createPin();
    128                    flowInputPin.setName(PUT);
    129                    ret.getPin().add(flowInputPin);
    130
    131                    Map<Class<?>, List<PinInfo>> classSources = new HashMap<Class<?>, List<PinInfo>>();
    132                    Map<Class<?>, List<PinInfo>> classConsumers = new HashMap<Class<?>, List<PinInfo>>();
    133
    134                    ArrayList<RuleInfo> allRules = new ArrayList<RuleInfo>();
    135                    collectRules(ruleSet, factoryConfig, allRules);
    136
    137                    ConfigUtil.pump(ruleSet, ret);
    138                    if (ret.getType()==null || ret.getType().trim().length()==0 || String.class.getName().equals(ret.getType().trim())) {
    139                            ret.setType(RuleSetFlow.class.getName());
    140                    }
    141
    142                    Iterator<RuleInfo> rit = allRules.iterator();
    143                    NamedCollection rulesCollection = ConfigFactory.eINSTANCE.createNamedCollection();
    144                    rulesCollection.setName("rules");
    145                    rulesCollection.setDescription("Collection of rule definitions");
    146                    rulesCollection.setType(ArrayList.class.getName());
    147                    ret.getProperty().add(rulesCollection);
    148                    for (int i=0; rit.hasNext(); ++i) {
    149                            RuleInfo ruleInfo = rit.next();
    150                            rulesCollection.getElement().add(ruleInfo.rule);
    151
    152                            int rulePriority = ruleInfo.rule.getPriority();
    153                            Method[] methods = ruleInfo.ruleClass.getMethods();
    154                            for (Method method: methods) {
    155                                    Infer inferAnnotation = getInferAnnotation(method);
    156                                    if (inferAnnotation!=null && !Modifier.isVolatile(method.getModifiers())) {
    157                                            Class<?>[] pt = method.getParameterTypes();
    158                                            int methodPriority = inferAnnotation.priority();
    159                                            if (methodPriority==0) {
    160                                                    methodPriority = rulePriority;
    161                                            }
    162
    163                                            Node inferenceNode = createInferenceMethodNode(ruleSet, ruleInfo.rule, method, ret);
    164                                            inferenceNode.setName(method.getName());
    165                                            inferenceNode.setDescription("Inference node for method '"+method+"' in rule #"+i+" '"+ruleInfo.rule.getName()+"'");
    166                                            if (pt.length==0) {
    167                                                    throw new ConfigurationException("Inference method shall have at least one parameter: "+method);
    168                                            } else {
    169                                                    setParameterTypes(inferenceNode, pt);
    170                                            }
    171
    172                                            ConfigUtil.addScalarProperty(inferenceNode, "methodName", method.getName(), true);
    173                                            if (ruleInfo.rule.getName()!=null) {
    174                                                    ConfigUtil.addScalarProperty(inferenceNode, "ruleName", ruleInfo.rule.getName(), true);
    175                                            }
    176                                            if(ruleInfo.rule.getDescription()!=null) {
    177                                                    ConfigUtil.addScalarProperty(inferenceNode, "ruleDescription", ruleInfo.rule.getDescription(), true);
    178                                            }
    179                                            ConfigUtil.addScalarProperty(inferenceNode, RULE_INDEX, i, true);
    180
    181                                            // Infer attributes - priority, value - conclusion classes.
    182                                            ConfigUtil.addScalarProperty(inferenceNode, "priority", methodPriority, true);
    183                                            Class<?>[] conclusionTypes = inferAnnotation.value();
    184                                            if (conclusionTypes.length==0) {
    185                                                    if (!void.class.equals(method.getReturnType())) {
    186                                                            conclusionTypes = new Class<?>[] {method.getReturnType()};
    187                                                    }
    188                                            }
    189
    190                                            // Outputs
    191                                            for (Class<?> conclusionType: conclusionTypes) {
    192                                                    Pin conclusionPin = createOutputPin(inferenceNode, -1, conclusionType);
    193                                                    List<PinInfo> conclusionBucket = classSources.get(conclusionType);
    194                                                    if (conclusionBucket==null) {
    195                                                            conclusionBucket = new ArrayList<PinInfo>();
    196                                                            classSources.put(conclusionType, conclusionBucket);
    197                                                    }
    198                                                    conclusionBucket.add(new PinInfo(methodPriority, conclusionPin));
    199                                            }
    200
    201                                            // Create parameter pins
    202                                            Pin[] parameterPins = new Pin[pt.length];
    203                                            for (int pIdx=0; pIdx<pt.length; ++pIdx) {
    204                                                    parameterPins[pIdx] = createInputPin(inferenceNode, pIdx, pt[pIdx]);
    205                                            }
    206
    207                                            List<ConditionEntry> conditionList = new ArrayList<ConditionEntry>();
    208                                            // Method level conditions
    209                                            Condition condition = method.getAnnotation(Condition.class);
    210                                            if (condition!=null) {
    211                                                    for (String c: condition.value()) {
    212                                                            try {
    213                                                                    ExpressionConditionEntry ce = analyzeCondition(c, pt.length, true);
    214                                                                    ce.position = conditionList.size();
    215    //                                                                      ce.methodLevel = true;
    216                                                                    conditionList.add(ce);
    217                                                            } catch (RecognitionException e) {
    218                                                                    throw new ConfigurationException("Error in condition: '"+c+"': "+e,e);
    219                                                            } catch (TokenStreamException e) {
    220                                                                    throw new ConfigurationException("Error in condition: '"+c+"': "+e,e);
    221                                                            }
    222                                                    }
    223                                            }
    224
    225                                            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
    226
    227                                            // Parameters
    228                                            for (int pIdx=0; pIdx<pt.length; ++pIdx) {
    229
    230                                                    // Find accept methods
    231                                                    for (Method acceptMethod: methods) {
    232                                                            Accept acceptAnnotation = acceptMethod.getAnnotation(Accept.class);
    233                                                            if (acceptAnnotation!=null) {
    234                                                                    if (!boolean.class.equals(acceptMethod.getReturnType())) {
    235                                                                            throw new ConfigurationException("Accept method shall return boolean: "+acceptMethod);
    236                                                                    }
    237                                                                    Class<?>[] apt = acceptMethod.getParameterTypes();
    238                                                                    if (apt.length==0) {
    239                                                                            throw new ConfigurationException("Accept method shall have parameters.");
    240                                                                    }
    241
    242                                                                    if (acceptAnnotation.method().trim().length()==0 || acceptAnnotation.method().equals(method.getName())) { // Method name match
    243                                                                            if (apt.length==1) {
    244                                                                                    if (apt[0].isAssignableFrom(pt[pIdx])) { // Parameter compatibility.
    245                                                                                            for (int apIdx: acceptAnnotation.parameterPositions()) {
    246                                                                                                    if (apIdx==-1 || apIdx==pIdx) { // Position match
    247                                                                                                            if (acceptAnnotation.parameterTypes().length==0 || Arrays.equals(acceptAnnotation.parameterTypes(), pt)) { // Parameter types match
    248                                                                                                                    AcceptConditionEntry ce = new AcceptConditionEntry(acceptMethod, pIdx);
    249                                                                                                                    ce.position = conditionList.size();
    250                                                                                                                    conditionList.add(ce);
    251                                                                                                            }
    252                                                                                                    }
    253                                                                                            }
    254                                                                                    }
    255                                                                            } else {
    256                                                                                    throw new UnsupportedOperationException("Multi-parameter accept methods are not supported yet");
    257                                                                            }
    258                                                                    }
    259                                                            }
    260                                                    }
    261
    262                                                    // Parameter level conditions
    263                                                    for (Annotation pAnn: parameterAnnotations[pIdx]) {
    264                                                            if (pAnn instanceof Condition) {
    265                                                                    for (String c: ((Condition) pAnn).value()) {
    266                                                                            ConditionEntry ce = new ExpressionConditionEntry(c, Collections.singletonMap(pIdx, "arg"), null);
    267                                                                            ce.position = conditionList.size();
    268                                                                            conditionList.add(ce);
    269                                                                    }
    270                                                            }
    271                                                    }
    272                                            }
    273
    274                                            // Sort conditions in the reverse order of number of parameters to join.
    275                                            Collections.sort(conditionList, new Comparator<ConditionEntry>() {
    276
    277                                                    @Override
    278                                                    public int compare(ConditionEntry c1, ConditionEntry c2) {
    279                                                            int s1 = c1.getParamIndexes().size();
    280                                                            int s2 = c2.getParamIndexes().size();
    281                                                            return s1==s2 ? c2.position - c1.position : s2 - s1;
    282                                                    }
    283                                            });
    284
    285                                            // Insert condition nodes starting from parameter pins, replace pins
    286                                            int conditionNo = 0;
    287                                            for (ConditionEntry ce: conditionList) {
    288                                                    Node conditionNode;
    289                                                    if (ce instanceof ExpressionConditionEntry) {
    290                                                            conditionNode = createNode(ret, ExpressionConditionNode.class);
    291                                                            ExpressionConditionEntry ece = (ExpressionConditionEntry) ce;
    292                                                            ConfigUtil.addScalarProperty(conditionNode, "condition", ece.text, true);
    293                                                            for (Map.Entry<Integer,String> e: ece.paramNames.entrySet()) {
    294                                                                    addConditionArgument(conditionNode, e.getValue(), pt[e.getKey()], e.getKey());
    295                                                            }
    296                                                            conditionNode.setName(ece.text);
    297                                                    } else if (ce instanceof AcceptConditionEntry) {
    298                                                            conditionNode = createNode(ret, AcceptMethodConditionNode.class);
    299                                                            Method acceptMethod = ((AcceptConditionEntry) ce).acceptMethod;
    300                                                            ConfigUtil.addScalarProperty(conditionNode, "methodName", acceptMethod.getName(), true);
    301                                                            setParameterTypes(conditionNode, acceptMethod.getParameterTypes());
    302                                                            conditionNode.setName(acceptMethod.getName());
    303                                                            setParameterMap(conditionNode, ((AcceptConditionEntry) ce).pMap);
    304                                                    } else {
    305                                                            throw new ConfigurationException("Unexpected condition type: "+ce.getClass().getName());
    306                                                    }
    307
    308                                                    if (ruleInfo.rule.getName()!=null) {
    309                                                            ConfigUtil.addScalarProperty(inferenceNode, "ruleName", ruleInfo.rule.getName(), true);
    310                                                    }
    311                                                    if(ruleInfo.rule.getDescription()!=null) {
    312                                                            ConfigUtil.addScalarProperty(inferenceNode, "ruleDescription", ruleInfo.rule.getDescription(), true);
    313                                                    }
    314
    315                                                    ConfigUtil.addScalarProperty(conditionNode, RULE_INDEX, i, true);
    316                                                    ConfigUtil.addScalarProperty(conditionNode, CONDITION_NO, conditionNo++, false);
    317
    318                                                    if (ce.getParamIndexes().isEmpty()) {
    319                                                            throw new ConfigurationException("Condition doesn't use any arguments");
    320                                                    } else if (ce.getParamIndexes().size()==1) {
    321                                                            Integer parameterIndex = ce.getParamIndexes().iterator().next();
    322                                                            Pin inputPin = createInputPin(conditionNode, parameterIndex, pt[parameterIndex]);
    323                                                            Pin outputPin = createOutputPin(conditionNode, parameterIndex, pt[parameterIndex]);
    324
    325                                                            connect(outputPin, parameterPins[parameterIndex], PassThroughTransition.class, methodPriority, ret);
    326                                                            // TODO - use toKey addScalarProperty(acceptTransition, END_CONNECTION_KEY, "parameter:"+pIdx);
    327                                                            parameterPins[parameterIndex] = inputPin;
    328                                                    } else {
    329                                                            Pin[] toMerge = new Pin[ce.getParamIndexes().size()];
    330                                                            java.util.Collection<TokenInfo> tic = new ArrayList<TokenInfo>();
    331                                                            int cpi = 0;
    332                                                            for (Integer param: ce.getParamIndexes()) {
    333                                                                    tic.add(new TokenInfo(param, pt[param]));
    334                                                                    toMerge[cpi++]=parameterPins[param];
    335                                                            }
    336                                                            Pin merged = mergeInputPins(toMerge);
    337                                                            Pin conditionOutput = createOutputPin(conditionNode, tic);
    338                                                            connect(conditionOutput, merged, PassThroughTransition.class, methodPriority, ret);
    339                                                            for (Integer param: ce.getParamIndexes()) {
    340                                                                    parameterPins[param] = createInputPin(conditionNode, param, pt[param]);
    341                                                            }
    342                                                    }
    343                                            }
    344
    345                                            // Add parameter pins to consumer buckets
    346                                            for (int pIdx=0; pIdx<pt.length; ++pIdx) {
    347                                                    List<PinInfo> consumerBucket = classConsumers.get(pt[pIdx]);
    348                                                    if (consumerBucket==null) {
    349                                                            consumerBucket = new ArrayList<PinInfo>();
    350                                                            classConsumers.put(pt[pIdx], consumerBucket);
    351                                                    }
    352                                                    consumerBucket.add(new PinInfo(methodPriority, parameterPins[pIdx]));
    353                                            }
    354                                    }
    355                            }
    356                    }
    357
    358                    for (Map.Entry<Class<?>, List<PinInfo>> consumerEntry: classConsumers.entrySet()) {
    359                            Node passThroughNode = FlowFactory.eINSTANCE.createNode();
    360                            passThroughNode.setDescription("Pass-through (NOP) node for consumers of type "+consumerEntry.getKey());
    361                            ret.getFlowElement().add(passThroughNode);
    362                            Pin passThroughPin = FlowFactory.eINSTANCE.createPin();
    363                            passThroughPin.setName(DEFAULT);
    364                            passThroughNode.getPin().add(passThroughPin);
    365                            passThroughNode.setType(ReasoningPassThroughNode.class.getName());
    366
    367                            Transition inputTransition = connect(flowInputPin, passThroughPin, TypeFilterTransition.class, 0, ret);
    368                            inputTransition.setDescription("Input transition for type "+consumerEntry.getKey());
    369                            ConfigUtil.addScalarProperty(inputTransition, TYPE, consumerEntry.getKey(), true);
    370                            inputTransition.setName(consumerEntry.getKey().getName());
    371
    372                            if (consumerEntry.getValue().isEmpty()) {
    373                                    throw new IllegalStateException("Consumer bucket shall not be empty for "+consumerEntry.getKey());
    374                            }
    375                            for (PinInfo consumerPinInfo: consumerEntry.getValue()) {
    376                                    connect(passThroughPin, consumerPinInfo.getPin(), PassThroughTransition.class, consumerPinInfo.getPriority(), ret);
    377                            }
    378
    379                            for (Map.Entry<Class<?>, List<PinInfo>> sourceEntry: classSources.entrySet()) {
    380                                    if (consumerEntry.getKey().isAssignableFrom(sourceEntry.getKey())) { // Compatible types, connect.
    381                                            if (sourceEntry.getValue().isEmpty()) {
    382                                                    throw new IllegalStateException("Source bucket shall not be empty for "+sourceEntry.getKey());
    383                                            }
    384                                            for (PinInfo sourcePinInfo: sourceEntry.getValue()) {
    385                                                    connect(sourcePinInfo.getPin(), passThroughPin, PassThroughTransition.class,  sourcePinInfo.getPriority(), ret);
    386                                            }
    387                                    }
    388                            }
    389                            if (passThroughPin.getOutput().isEmpty()) {
    390                                    throw new IllegalStateException("No consumers for "+consumerEntry.getKey());
    391                            }
    392                    }
    393
    394                    // Optimizes flow, chains optimizers.
    395                    Iterator<RuleSetFlowOptimizer> rsFo = ServiceLoader.load(RuleSetFlowOptimizer.class, this.getClass().getClassLoader()).iterator();
    396                    List<RuleSetFlowOptimizer> rsFol = new ArrayList<RuleSetFlowOptimizer>();
    397                    while (rsFo.hasNext()) {
    398                            rsFol.add(rsFo.next());
    399                    }
    400                    Collections.sort(rsFol, new Comparator<RuleSetFlowOptimizer>() {
    401
    402                            @Override
    403                            public int compare(RuleSetFlowOptimizer o1, RuleSetFlowOptimizer o2) {
    404                                    int diff = o1.getOrder() - o2.getOrder();
    405                                    return diff==0 ? o1.hashCode() - o2.hashCode() : diff;
    406                            }
    407
    408                    });
    409
    410                    for (RuleSetFlowOptimizer rsfo: rsFol) {
    411                            rsfo.optimize(ret);
    412                    }
    413
    414                    TreeIterator<EObject> rcit = ret.eAllContents();
    415                    while (rcit.hasNext()) {
    416                            EObject next = rcit.next();
    417                            if (next instanceof Transition) {
    418                                    Transition t = (Transition) next;
    419                                    if (t.getToPin()==null) {
    420                                            throw new IllegalStateException("To pin is null for "+t);
    421                                    }
    422                                    if (t.getFromPin()==null) {
    423                                            throw new IllegalStateException("From pin is null for "+t);
    424                                    }
    425                            }
    426                    }
    427
    428                    return ret;
    429            }
    430
    431            private void collectRules(RuleSet ruleSet, FactoryConfig factoryConfig, ArrayList<RuleInfo> allRules) throws ConfigurationException {
    432                    for (RuleSet base: ruleSet.getBase()) {
    433                            collectRules(base, factoryConfig, allRules);
    434                    }
    435
    436                    ClassLoader ruleSetClassLoader = ObjectDefinitionImpl.propertySourceClassLoader(ruleSet, factoryConfig);
    437                    FactoryConfig ruleFactoryConfig;
    438                    try {
    439                            ruleFactoryConfig = (FactoryConfig) factoryConfig.clone();
    440                    } catch (CloneNotSupportedException ex) {
    441                            throw new ConfigurationException(ex);
    442                    }
    443                    ruleFactoryConfig.setClassLoader(ruleSetClassLoader);
    444                    for (Rule rule: ruleSet.getRule()) {
    445                            try {
    446                                    allRules.add(new RuleInfo(rule, ruleFactoryConfig));
    447                            } catch (ClassNotFoundException e) {
    448                                    throw new ConfigurationException("Rule class not found: "+e, e);
    449                            }
    450                    }
    451            }
    452
    453            /**
    454             * Creates inference method node.
    455             * Subclasses can override this method to provide custom
    456             * node implementations and additional configuration.
    457             * @param method
    458             * @param rule
    459             * @param ruleSet
    460             * @param ret
    461             * @return
    462             */
    463            protected Node createInferenceMethodNode(RuleSet ruleSet, Rule rule, Method method, Flow owner) {
    464                    return createNode(owner, InferenceMethodNode.class);
    465            }
    466
    467            /**
    468             * Extracts inference information from the method.
    469             * This implementation takes it from Infer annotation.
    470             * Subclasses can do it in a different way.
    471             * @param method
    472             * @return
    473             */
    474            protected Infer getInferAnnotation(Method method) {
    475                    return method.getAnnotation(Infer.class);
    476            }
    477
    478            /**
    479             * Merges input pins.
    480             * @param pin1
    481             * @param pin2
    482             */
    483            private Pin mergeInputPins(Pin... pins) {
    484                    if (pins.length==0) {
    485                            throw new IllegalArgumentException("No input pins to merge");
    486                    }
    487                    // Sort pins so the first pin always belongs to the outmost condition.
    488                    Arrays.sort(pins, new Comparator<Pin>() {
    489
    490                            @Override
    491                            public int compare(Pin o1, Pin o2) {
    492                                    if (belongsToCondition(o1)) {
    493                                            if (!belongsToCondition(o2)) {
    494                                                    return -1;
    495                                            }
    496
    497                                            int cn1 = getConditionNo(o1);
    498                                            if (cn1==-1) {
    499                                                    throw new IllegalStateException("Condition number shall not be -1");
    500                                            }
    501                                            int cn2 = getConditionNo(o2);
    502                                            if (cn2==-1) {
    503                                                    throw new IllegalStateException("Condition number shall not be -1");
    504                                            }
    505                                            if (cn1!=cn2) {
    506                                                    return cn2-cn1;
    507                                            }
    508                                    }
    509
    510                                    // Both belong to method node
    511                                    return o1.getName().compareTo(o2.getName());
    512                            }
    513
    514                            private int getConditionNo(Pin pin) {
    515                                    for (Named property: pin.getNode().getProperty()) {
    516                                            if (CONDITION_NO.equals(property.getName()) && property instanceof NamedObjectDefinition) {
    517                                                    return Integer.parseInt(((NamedObjectDefinition) property).getValue());
    518                                            }
    519                                    }
    520                                    return -1;
    521                            }
    522
    523                            private boolean belongsToCondition(Pin o1) {
    524                                    String type = o1.getNode().getType();
    525                                    try {
    526                                            return ConditionNode.class.isAssignableFrom(Class.forName(type));
    527                                    } catch (ClassNotFoundException e) {
    528                                            logger.log(Level.WARNING, "Condition class not found: "+type, e);
    529                                            return false;
    530                                    }
    531                            }
    532
    533                    });
    534
    535                    for (int i=1; i<pins.length; ++i) {
    536                            java.util.Collection<Factory> toMove = new ArrayList<Factory>();
    537                            toMove.addAll(((Collection) pins[i].getPinConfig()).getElement());
    538                            addTokens(toMove, (Collection) pins[0].getPinConfig());
    539                            if (pins[0].getNode()==pins[i].getNode()) {
    540                                    // Same node pins - remove second pin, no need to change output pins.
    541                                    pins[i].getNode().getPin().remove(pins[i]);
    542                            } else {
    543                                    // Different nodes. Change pin[0] name, change output name, merge output with the second pin.
    544                                    merge(pins[0].getNode(), pins[i], toMove);
    545                            }
    546                    }
    547                    return pins[0];
    548            }
    549
    550            private void addTokens(java.util.Collection<Factory> tokens, Collection target) {
    551                    for (Factory f: tokens) {
    552                            if (f instanceof ObjectDefinition) {
    553                                    ObjectDefinition token = (ObjectDefinition) f;
    554                                    if (!TokenInfo.class.getName().equals(token.getType())) {
    555                                            throw new IllegalArgumentException("Invalid token info type: "+token.getType());
    556                                    }
    557                                    String className = null;
    558                                    int pIdx = -1;
    559                                    for (Named property: token.getProperty()) {
    560                                            if (TYPE.equals(property.getName())) {
    561                                                    className = ((ObjectDefinition) property).getValue();
    562                                            } else if (PARAMETER_INDEX.equals(property.getName())) {
    563                                                    pIdx = Integer.parseInt(((ObjectDefinition) property).getValue());
    564                                            }
    565                                    }
    566                                    target.getElement().add(createTokenInfo(pIdx, className));
    567                            } else {
    568                                    throw new IllegalArgumentException("Invalid token type: "+f.getClass());
    569                            }
    570                    }
    571            }
    572
    573            /**
    574             * Merges input pins.
    575             * @param pin1
    576             * @param pin2
    577             */
    578            private void merge(Node cNode, Pin input2, java.util.Collection<Factory> tokens) {
    579                    if (!ConditionNode.class.getName().equals(cNode.getType())) {
    580                            throw new IllegalArgumentException("Not a condition node: "+cNode);
    581                    }
    582                    for (Pin output: cNode.getPin()) {
    583                            if (output.getName().startsWith(OUTPUT)) {
    584                                    for (Factory f: ((Collection) output.getPinConfig()).getElement()) {
    585                                            addTokens(tokens, (Collection) f);
    586                                    }
    587                                    if (output.getOutput().size()!=1) {
    588                                            throw new IllegalStateException("Condition node output shall have one transition");
    589                                    }
    590                                    Transition transition = output.getOutput().get(0);
    591                                    Pin outputTarget = transition.getToPin();
    592                                    if (input2.getNode()==outputTarget.getNode()) {
    593                                            // Merge two input nodes, connect to
    594                                            addTokens(tokens, (Collection) outputTarget.getPinConfig());
    595                                            input2.getNode().getPin().remove(input2);
    596                                    } else {
    597                                            addTokens(tokens, (Collection) input2.getPinConfig());
    598                                            transition.setToPin(input2);
    599                                            if (input2.getInput().size()!=1) {
    600                                                    throw new IllegalStateException("Condition node input shall have one transition");
    601                                            }
    602                                            merge(input2.getNode(), outputTarget, tokens);
    603                                    }
    604                                    return;
    605                            }
    606                    }
    607                    throw new IllegalStateException("Could not find output pin for condition node "+cNode);
    608            }
    609
    610
    611            protected Node createNode(Flow flow, Class<?> type) {
    612                    Node ret = FlowFactory.eINSTANCE.createNode();
    613                    flow.getFlowElement().add(ret);
    614                    ret.setType(type.getName());
    615                    return ret;
    616            }
    617
    618            private Pin createInputPin(Node node, int idx, Class<?> parameterType) {
    619                    Pin ret = FlowFactory.eINSTANCE.createPin();
    620                    ret.setName(INPUT + pinCounter++);
    621                    node.getPin().add(ret);
    622
    623                    Collection top = ConfigFactory.eINSTANCE.createCollection();
    624                    ret.setPinConfig(top);
    625
    626                    top.getElement().add(createTokenInfo(idx, parameterType));
    627
    628                    return ret;
    629            }
    630
    631            private ObjectDefinition createTokenInfo(int idx, Class<?> type) {
    632                    return createTokenInfo(idx, type.getName());
    633            }
    634
    635            private ObjectDefinition createTokenInfo(int idx, String typeName) {
    636                    ObjectDefinition tokenInfo = ConfigFactory.eINSTANCE.createObjectDefinition();
    637                    tokenInfo.setType(TokenInfo.class.getName());
    638
    639                    NamedObjectDefinition ct = ConfigFactory.eINSTANCE.createNamedObjectDefinition();
    640                    ct.setType(Class.class.getName());
    641                    ct.setValue(typeName);
    642                    ct.setName(TYPE);
    643                    tokenInfo.getProperty().add(ct);
    644
    645                    if (idx!=-1) {
    646                            NamedObjectDefinition pIdx = ConfigFactory.eINSTANCE.createNamedObjectDefinition();
    647                            pIdx.setType(Integer.class.getName());
    648                            pIdx.setValue(String.valueOf(idx));
    649                            pIdx.setName(PARAMETER_INDEX);
    650                            tokenInfo.getProperty().add(pIdx);
    651                    }
    652
    653                    return tokenInfo;
    654            }
    655
    656            private Pin createOutputPin(Node node, int idx, Class<?> conclusionType) {
    657                    Pin ret = FlowFactory.eINSTANCE.createPin();
    658                    ret.setName(OUTPUT + pinCounter++);
    659                    node.getPin().add(ret);
    660
    661                    Collection top = ConfigFactory.eINSTANCE.createCollection();
    662                    ret.setPinConfig(top);
    663
    664                    Collection second = ConfigFactory.eINSTANCE.createCollection();
    665                    top.getElement().add(second);
    666
    667                    second.getElement().add(createTokenInfo(idx, conclusionType));
    668
    669                    return ret;
    670            }
    671
    672            private Pin createOutputPin(Node node, java.util.Collection<TokenInfo> tic) {
    673                    Pin ret = FlowFactory.eINSTANCE.createPin();
    674                    ret.setName(OUTPUT + pinCounter++);
    675                    node.getPin().add(ret);
    676
    677                    Collection top = ConfigFactory.eINSTANCE.createCollection();
    678                    ret.setPinConfig(top);
    679
    680                    Collection second = ConfigFactory.eINSTANCE.createCollection();
    681                    top.getElement().add(second);
    682
    683                    for (TokenInfo ti: tic) {
    684                            second.getElement().add(createTokenInfo(ti.getParameterIndex(), ti.getType()));
    685                    }
    686
    687                    return ret;
    688            }
    689
    690            private Transition connect(Pin from, Pin to, Class<?> transitionType, int priority, Flow flow) {
    691                    if (from==null) {
    692                            throw new NullPointerException("From pin is null");
    693                    }
    694                    if (to==null) {
    695                            throw new NullPointerException("To pin is null");
    696                    }
    697                    Transition ret = FlowFactory.eINSTANCE.createTransition();
    698                    flow.getFlowElement().add(ret);
    699                    ret.setFromPin(from);
    700                    ret.setToPin(to);
    701                    ret.setType(transitionType.getName());
    702
    703                    ObjectDefinition priorityKeyFrom = ConfigFactory.eINSTANCE.createObjectDefinition();
    704                    priorityKeyFrom.setType(Integer.class.getName());
    705                    priorityKeyFrom.setValue(String.valueOf(priority));
    706                    ret.setFromKey(priorityKeyFrom);
    707
    708                    // Not needed, for consistency
    709                    ObjectDefinition priorityKeyTo = ConfigFactory.eINSTANCE.createObjectDefinition();
    710                    priorityKeyTo.setType(Integer.class.getName());
    711                    priorityKeyTo.setValue(String.valueOf(priority));
    712                    ret.setToKey(priorityKeyTo);
    713
    714                    return ret;
    715            }
    716
    717            private void setParameterTypes(PropertySource target, Class<?>[] pt) {
    718                    NamedCollection parameterTypes = ConfigFactory.eINSTANCE.createNamedCollection();
    719                    parameterTypes.setName("parameterTypes");
    720                    target.getProperty().add(parameterTypes);
    721                    for (Class<?> pType: pt) {
    722                            ObjectDefinition ptDef = ConfigFactory.eINSTANCE.createObjectDefinition();
    723                            ptDef.setValue(pType.getName());
    724                            ptDef.setType(Class.class.getName());
    725                            parameterTypes.getElement().add(ptDef);
    726                    }
    727            }
    728
    729            private void setParameterMap(PropertySource target, int[] pMap) {
    730                    NamedCollection parameterMap = ConfigFactory.eINSTANCE.createNamedCollection();
    731                    parameterMap.setName("parameterMap");
    732                    target.getProperty().add(parameterMap);
    733                    for (int pIdx: pMap) {
    734                            ObjectDefinition ptDef = ConfigFactory.eINSTANCE.createObjectDefinition();
    735                            ptDef.setValue(String.valueOf(pIdx));
    736                            ptDef.setType(Integer.class.getName());
    737                            parameterMap.getElement().add(ptDef);
    738                    }
    739            }
    740
    741            private static void addConditionArgument(PropertySource target, String argName, Class<?> argType, int parameterPosition) {
    742                    NamedObjectDefinition prop = ConfigFactory.eINSTANCE.createNamedObjectDefinition();
    743                    prop.setName("conditionArgument");
    744                    prop.setType(ConditionArgument.class.getName());
    745                    prop.setRuntime(true);
    746                    target.getProperty().add(prop);
    747
    748                    ConfigUtil.addScalarProperty(prop, "type", argType, true);
    749                    ConfigUtil.addScalarProperty(prop, "name", argName, true);
    750                    ConfigUtil.addScalarProperty(prop, PARAMETER_INDEX, parameterPosition, true);
    751            }
    752
    753            private static abstract class ConditionEntry {
    754                    int position;
    755
    756                    abstract java.util.Collection<Integer> getParamIndexes();
    757            }
    758
    759            private static class AcceptConditionEntry extends ConditionEntry {
    760                    Method acceptMethod;
    761                    int[] pMap; // Array of the size of number of accept method parameters, each element contains index of target method parameter bound to accept method parameter.
    762
    763                    AcceptConditionEntry(Method acceptMethod, int... pMap) {
    764                            this.acceptMethod = acceptMethod;
    765                            this.pMap = pMap;
    766                    }
    767
    768                    @Override
    769                    java.util.Collection<Integer> getParamIndexes() {
    770                            ArrayList<Integer> ret = new ArrayList<Integer>();
    771                            for (int i: pMap) {
    772                                    ret.add(i);
    773                            }
    774                            return ret;
    775                    }
    776            }
    777
    778            private static class ExpressionConditionEntry extends ConditionEntry {
    779                    Map<Integer, String> paramNames;
    780                    String text;
    781                    AST node;
    782    //              private int[] paramMap;
    783    //              boolean methodLevel;
    784
    785                    ExpressionConditionEntry(String text, Map<Integer, String> paramNames, AST node) {
    786                            super();
    787                            this.text = text;
    788                            this.node = node;
    789                            this.paramNames = paramNames;
    790                    }
    791
    792                    @Override
    793                    public String toString() {
    794                            return "{'"+text+"', "+position+"}";
    795                    }
    796
    797                    @Override
    798                    java.util.Collection<Integer> getParamIndexes() {
    799                            return paramNames.keySet();
    800                    }
    801
    802            }
    803
    804            // TODO - Break conditions along && and || ???
    805            static ExpressionConditionEntry analyzeCondition(String condition, int params, boolean methodLevel) throws RecognitionException, TokenStreamException {
    806                    Map<Integer, String> paramNames = new HashMap<Integer, String>();
    807                    int colonIdx = condition.indexOf(":");
    808                    int questionIdx = condition.indexOf("?");
    809                    if (colonIdx==-1 || (questionIdx!=-1 && colonIdx>questionIdx)) {
    810                            // No parameter spec, use argX
    811                            for (int i=0; i<params; ++i) {
    812                                    paramNames.put(i, methodLevel ? "arg"+i : "arg");
    813                            }
    814                    } else {
    815                            String[] pNames = condition.substring(0, colonIdx).split(",");
    816                            for (int i=0; i<pNames.length; ++i) {
    817                                    String pName = pNames[i].trim();
    818                                    if (pName.length()>0) {
    819                                            paramNames.put(i, pName);
    820                                    }
    821                            }
    822                            condition = condition.substring(colonIdx+1);
    823                    }
    824
    825                    JavaLexer lexer = new JavaLexer(new StringReader("{ result = "+condition+"; }"));
    826                    JavaRecognizer parser = new JavaRecognizer(lexer);
    827                    parser.compoundStatement();
    828                    AST ast = parser.getAST();
    829                    Set<Integer> ids = new HashSet<Integer>();
    830                    AST expressionNode = ast.getFirstChild().getFirstChild().getFirstChild().getNextSibling();
    831                    extractIds(expressionNode, ids, paramNames);
    832                    paramNames.keySet().retainAll(ids); // Leave only used id's
    833                    return new ExpressionConditionEntry(condition, paramNames, expressionNode);
    834            }
    835
    836            private static void extractIds(AST ast, Set<Integer> ids, Map<Integer, String> paramNames) {
    837                    switch (ast.getType()) {
    838                    case JavaTokenTypes.IDENT:
    839                            // Top-level ident - can be argument name.
    840                            for (Map.Entry<Integer, String> e: paramNames.entrySet()) {
    841                                    if (e.getValue().equals(ast.getText())) {
    842                                            ids.add(e.getKey());
    843                                    }
    844                            }
    845                            break;
    846                    case JavaTokenTypes.METHOD_CALL:
    847                            // Analyze name - if first element is argument name.
    848                            extractIds(ast.getFirstChild(), ids, paramNames);
    849                            // Analyze method arguments
    850                            extractIds(ast.getFirstChild().getNextSibling(), ids, paramNames);
    851                            break;
    852                    case JavaTokenTypes.DOT:
    853                            // DOT - analyze if the first child is argument.
    854                            List<AST> astl = deDot(ast);
    855                            extractIds(astl.get(0), ids, paramNames);
    856                            break;
    857                    default:
    858                            // Analyze children
    859                            for (AST child = ast.getFirstChild(); child!=null; child=child.getNextSibling()) {
    860                                    extractIds(child, ids, paramNames);
    861                            }
    862                    }
    863            }
    864
    865            private static List<AST> deDot(AST ast) {
    866                    List<AST> ret = new ArrayList<AST>();
    867                    if (ast.getType()==JavaTokenTypes.DOT) {
    868                            ret.addAll(deDot(ast.getFirstChild()));
    869                            ret.addAll(deDot(ast.getFirstChild().getNextSibling()));
    870                    } else {
    871                            ret.add(ast);
    872                    }
    873                    return ret;
    874            }
    875
    876            private static void dump(AST ast, String indent) {
    877                    System.out.println(indent+JavaRecognizer._tokenNames[ast.getType()]+" "+ast.getText());
    878                    for (AST child = ast.getFirstChild(); child!=null; child=child.getNextSibling()) {
    879                            dump(child, indent+"  ");
    880                    }
    881            }
    882
    883    //      public static void main(String[] args) throws Exception {
    884    //              ExpressionConditionEntry ce = analyzeCondition("child, , parent: !child.getSubject().equals(parent.getSubject(arg2)) && child.getObject().equals(parent.getObject())", 3, true);
    885    //              dump(ce.node, "");
    886    //      }
    887
    888            /**
    889             * @param args
    890             */
    891            public static void main(String[] args) throws Exception {
    892                    System.out.println("Usage: java [options] "+RuleSetToFlowCompiler.class.getName()+" <source file> <output file>");
    893                    Resource resource = ResourceLoader.load(args[0]);
    894                    System.out.println("Loaded " + args[0]);
    895
    896                    // Validate the contents of the loaded resource.
    897                    //
    898                    for (EObject eObject : resource.getContents()) {
    899                            Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eObject);
    900                            if (diagnostic.getSeverity() != Diagnostic.OK) {
    901                                    printDiagnostic(diagnostic, "");
    902                            }
    903                    }
    904
    905                    RuleSetToFlowCompiler compiler = new RuleSetToFlowCompiler();
    906                    File sourceFile = new File(args[0]);
    907                    URL contextUrl = sourceFile.isFile() ? sourceFile.toURI().toURL() : new URL(args[0]);
    908                    FactoryConfig factoryConfig = new FactoryConfig();
    909                    factoryConfig.setContextUrl(contextUrl);
    910                    Flow flow = compiler.compile((RuleSet) resource.getContents().get(0), factoryConfig);
    911                    Resource flowResource = resource.getResourceSet().createResource(URI.createFileURI(new File(args[1]).getAbsolutePath()));
    912                    flowResource.getContents().add(flow);
    913    //              flowResource.save(System.out, null);
    914                    flowResource.save(null);
    915            }
    916
    917            protected static void printDiagnostic(Diagnostic diagnostic, String indent) {
    918                    System.out.print(indent);
    919                    System.out.println(diagnostic.getMessage());
    920                    for (Diagnostic child : diagnostic.getChildren()) {
    921                            printDiagnostic(child, indent + "  ");
    922                    }
    923            }
    924
    925    }