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 }