001    package com.hammurapi.reasoning.impl;
002    
003    import java.lang.reflect.Method;
004    import java.util.ArrayList;
005    import java.util.Collection;
006    import java.util.Collections;
007    import java.util.List;
008    import java.util.Map;
009    import java.util.concurrent.Future;
010    import java.util.concurrent.FutureTask;
011    import java.util.concurrent.atomic.AtomicInteger;
012    import java.util.logging.Logger;
013    
014    import com.hammurapi.flow.runtime.ProcessingPathElement;
015    import com.hammurapi.flow.runtime.PropertySet;
016    import com.hammurapi.flow.runtime.impl.PropertySetMap;
017    import com.hammurapi.flow.runtime.impl.Joiner.InputConsumer;
018    import com.hammurapi.reasoning.ExceptionHandler;
019    import com.hammurapi.reasoning.Handle;
020    import com.hammurapi.reasoning.ReasoningException;
021    import com.hammurapi.reasoning.spi.InferenceContext;
022    import com.hammurapi.reasoning.impl.KnowledgeBase.PutDerivedResult;
023    import com.hammurapi.util.Context;
024    
025    public class InferenceMethodNode<F> extends ReasoningNodeBase<F> {
026            private static final Logger logger = Logger.getLogger(InferenceMethodNode.class.getName());
027    
028            private static AtomicInteger invocationCounter = new AtomicInteger();
029            
030    
031            @SuppressWarnings("unchecked")
032            @Override
033            protected Collection<Future<?>> process(
034                            KnowledgeBase<F> flowState,
035                            List<InferenceToken<F>>[] inputs, 
036                            final PropertySet properties,
037                            Context context, List<ProcessingPathElement> processingPath,
038                            final InputConsumer consumer,
039                            final int activator) throws Exception {
040                            
041                    final Object[] args = new Object[parameterTypes.length];
042                    final HandleImpl<F>[] handles = new HandleImpl[args.length];
043                    final InferenceToken<F>[] inferenceTokens = new InferenceToken[args.length];
044                    final int[] argToInputMap = new int[args.length];
045                    int actualArgs = 0;
046                    for (int i=0; i<inputs.length; ++i) {
047                            PinEntry<F> pe = getInputPins().get(i);
048                            for (int j=0; j<inputs[i].size(); ++j, ++actualArgs) {
049                                    TokenInfo ti = (TokenInfo) pe.config.get(j);
050                                    InferenceToken<F> it = inputs[i].get(j);
051                                    if (it.isConsumed()) {
052                                            return Collections.emptyList();
053                                    }
054                                    HandleImpl<F> h = (HandleImpl<F>) it.getHandle();
055                                    Object arg = h.get();
056                                    if (arg==null) {
057                                            return Collections.emptyList();                                 
058                                    }
059                                    args[ti.getParameterIndex()] = arg;
060                                    handles[ti.getParameterIndex()] = h;
061                                    inferenceTokens[ti.getParameterIndex()] = inputs[i].get(j);
062                                    argToInputMap[ti.getParameterIndex()] = i;
063                            }
064                    }
065                    if (actualArgs!=args.length) {
066                            throw new IllegalArgumentException("Number of inputs ("+actualArgs+") doesn't match number of method arguments "+method);
067                    }
068                                                    
069                    final List<F> conclusions = new ArrayList<F>();
070                    
071                    InferenceContext inferenceContext = new InferenceContext() {
072                            
073                            private Map<String, ?> icProperties = new PropertySetMap(properties);
074    
075                            @Override
076                            public void addUndo(Runnable undoCommand) {
077                                    FutureTask<F> ft = new FutureTask<F>(undoCommand, null);
078                                    for (HandleImpl<F> handle: handles) {
079                                            handle.addUndo(ft);
080                                    }                               
081                            }
082    
083                            @Override
084                            public boolean consumeInput(int position, boolean local) {                              
085                                    if (local) {
086                                            if (consumer==null) {
087                                                    return false;
088                                            }
089                                            consumer.consume(argToInputMap[position]);
090                                            return true;
091                                    } 
092                                                                    
093                                    if (argToInputMap[position]==activator) {
094                                            inferenceTokens[position].consume();
095                                            return true;
096                                    } 
097                                    
098                                    return false;                                   
099                            }
100    
101                            @Override
102                            public boolean contains(Object fact) throws ReasoningException {
103                                    return knowledgeBase.contains(fact);
104                            }
105    
106                            @Override
107                            public Collection<?> getObjects() throws ReasoningException {
108                                    return knowledgeBase.getObjects();
109                            }
110    
111                            @Override
112                            public Map<String, ?> getProperties() throws ReasoningException {
113                                    return icProperties;
114                            }
115    
116                            @Override
117                            public int[] parameterIndices() {
118                                    return null;
119                            }
120    
121                            @Override
122                            public void post(Object... aConclusions) {
123                                    for (Object conclusion: aConclusions) {
124                                            if (conclusion==null) {
125                                                    throw new NullPointerException("Conclusion can not be null");
126                                            }
127                                            conclusions.add((F) conclusion);
128                                            onConclusion(args, (F) conclusion);
129                                    }
130                            }
131    
132                            @Override
133                            public void remove(Object fact) throws ReasoningException {
134                                    knowledgeBase.remove((F) fact, true);
135                            }
136    
137                            @Override
138                            public Method targetMethod() {
139                                    return null;
140                            }
141    
142                            @Override
143                            public void put(Object fact) throws ReasoningException {
144                                    knowledgeBase.put((F) fact);                            
145                            }
146    
147                            @Override
148                            public void update(Object fact) throws ReasoningException {
149                                    update(fact, fact);
150                            }
151    
152                            @Override
153                            public void update(Object originalFact, Object newFact) throws ReasoningException {
154                                    Handle<F> handle = knowledgeBase.getHandle(originalFact);
155                                    if (handle==null) {
156                                            throw new ReasoningException("Fact not found in the knowledge base, can't update");
157                                    }
158                                    
159                                    knowledgeBase.updateObject(handle, (F) newFact);                                
160                            }
161                            
162                    };
163                    
164                    InferenceContextDispatcher.setThreadInferenceContext(inferenceContext);         
165                    try {
166                            F conclusion = (F) method.invoke(rule, args);
167                            if (conclusion!=null) {
168                                    conclusions.add(conclusion);
169                                    onConclusion(args, conclusion);
170                            }
171                            
172                    } catch (Exception e) {
173                            if (!handleException(args, e)) {
174                                    throw e;
175                            }
176                            conclusions.clear();
177                    } finally {
178                            InferenceContextDispatcher.unsetThreadInferenceContext();
179                    }
180                    
181                    Collection<Future<?>> ret = new ArrayList<Future<?>>();
182    
183                    StringBuilder sb = isFine ? new StringBuilder() : null;
184                    if (isFine) {
185                            sb.append("Invocation #"+invocationCounter.getAndIncrement() + ": "+method);
186                            sb.append("\n\tArguments");
187                            for (Object arg: args) {
188                                    sb.append("\n\t\t"+arg);
189                            }
190                            sb.append("\n\tConclusions");
191                    }
192                    
193                    for (F conclusion: conclusions) {       
194                            List<Handle<F>> handleList = new ArrayList<Handle<F>>();
195                            for (List<InferenceToken<F>> itl: inputs) {
196                                    for (InferenceToken<F> it: itl) {
197                                            handleList.add(it.getHandle());                                         
198                                    }
199                            }                       
200                            
201                            PutDerivedResult<F> putDerivedResult = knowledgeBase.putConclusion(conclusion, rule, ruleName, ruleDescription, method, handleList);
202                            if (putDerivedResult.isNew()) {
203                                    if (isFine) {
204                                            logger.fine(conclusion+" - dispatching");
205                                    }
206                                    InferenceToken<F> inferenceToken = new InferenceToken<F>(putDerivedResult.getHandle());
207                                    
208                                    boolean dispatched = false; 
209                                    for (PinEntry<F> pin: pins) {
210                                            if (pin.getName().startsWith(Constants.OUTPUT)) {
211                                                    boolean matches = false;
212                                                    for (Object alt: pin.config) {
213                                                            List<TokenInfo> lti=(List<TokenInfo>) alt;
214                                                            if (lti.size()==1) {
215                                                                    TokenInfo ti = lti.get(0);
216                                                                    if (ti.getType().isInstance(conclusion)) {
217                                                                            matches = true;
218                                                                            break;
219                                                                    }
220                                                            }
221                                                    }
222                                                    if (matches) {
223                                                            dispatched = true;
224                                                            for (Output<F> output: pin.outputs) {
225                                                                    ret.addAll(output.invocable.invoke(flowState, new InferenceToken[] {inferenceToken}, properties, context, processingPath));
226                                                            }
227                                                    }
228                                            }
229                                    }
230                                    if (!dispatched) {
231                                            throw new IllegalArgumentException("Conclusion of type "+conclusion.getClass()+" was not dispatched to any output pins");
232                                    }
233                            } else {
234                                    if (isFine) {
235                                            logger.fine(conclusion + " - rejected by the knowledge base");
236                                    }
237                            }
238                    }
239                    
240                    if (isFine) {
241                            logger.fine(sb.toString());
242                    }
243                    
244                    return ret;
245            }
246    
247            /**
248             * This method is invoked before conclusion is put to the knowledge base. 
249             * Rule classes can override this method to augment conclusions, e.g. inject some properties.
250             * @param conclusion
251             */
252            protected void onConclusion(Object[] args, F conclusion) {              
253                    
254            }       
255            
256            /**
257             * Handles method invocation exception. This implementation invokes ExceptionListener.onException()
258             * if exception listener is not null.  
259             * @param e Exception.
260             * @return True if exception was handled.
261             * @throws Exception 
262             */
263            protected boolean handleException(Object[] args, Exception e) throws Exception {
264                    ExceptionHandler exceptionListener = knowledgeBase.getExceptionHandler();
265                    if (exceptionListener==null) {
266                            return false;
267                    }
268                    exceptionListener.handleException(e);
269                    return true;
270            }
271            
272    }