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 }