    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    }