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    }