001    /*
002     * hammurapi-rules @mesopotamia.version@
003     * Hammurapi rules engine. 
004     * Copyright (C) 2005  Hammurapi Group
005     *
006     * This program is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 2 of the License, or (at your option) any later version.
010     *
011     * This program is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     *
016     * You should have received a copy of the GNU Lesser General Public
017     * License along with this library; if not, write to the Free Software
018     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
019     *
020     * URL: http://http://www.hammurapi.biz
021     * e-Mail: support@hammurapi.biz
022     */
023    package biz.hammurapi.rules;
024    
025    import java.lang.reflect.Method;
026    import java.util.ArrayList;
027    import java.util.Arrays;
028    import java.util.Collection;
029    import java.util.HashMap;
030    import java.util.HashSet;
031    import java.util.Iterator;
032    import java.util.Map;
033    
034    import biz.hammurapi.config.ConfigurationException;
035    import biz.hammurapi.dispatch.InvocationHandler;
036    import biz.hammurapi.dispatch.ResultConsumer;
037    import biz.hammurapi.util.Observable;
038    import biz.hammurapi.util.Observer;
039    import biz.hammurapi.util.Versioned;
040    
041    
042    /**
043     * Base class for rules.
044     * @author Pavel Vlasov
045     * @revision $Revision$
046     */
047    public class Rule extends AbstractRule {
048            
049            /**
050             * Interface to detect changes in arguments passed to inference methods.
051             * Default implementation detects changes in versioned and observable objects.
052             * @author Pavel Vlasov
053             * @revision $Revision$
054             */
055            public interface ChangeDetector {
056                    boolean hasChanged();
057            }
058            
059            /**
060             * Creates default change detector, which detects changes in versioned and observable objects.
061             * Subclasses can override this method to detect changes as appropriate for the application 
062             * domain.
063             * @param obj
064             * @return Change detector or null if change detection is not needed.
065             */
066            protected ChangeDetector newChangeDetector(final Object obj) {
067                    return new ChangeDetector() {
068                            private int version;
069                            private boolean changed;
070                            
071                            private Observer observer=new Observer() {
072    
073                                    public void update(Observable observable, Object arg) {
074                                            changed=true;
075                                    }
076                                    
077                            };
078                            
079                            {
080                                    if (obj instanceof Versioned) {
081                                            version=((Versioned) obj).getObjectVersion();
082                                    } else if (obj instanceof Observable) {
083                                            ((Observable) obj).addObserver(observer);
084                                    }
085                            }
086    
087                            public boolean hasChanged() {
088                                    if (obj instanceof Versioned) {
089                                            return version!=((Versioned) obj).getObjectVersion();
090                                    } else if (obj instanceof Observable) {
091                                            ((Observable) obj).removeObserver(observer);
092                                            return changed;
093                                    }
094                                    return false;
095                            }
096                            
097                    };
098            }
099            
100            /**
101             * Holds reference to the method currently being invoked.
102             * Used for injecting derivations in add() method.
103             */
104            private ThreadLocal currentResultConsumerWrapper=new ThreadLocal();
105    
106            /**
107             * 
108             */
109            private static final long serialVersionUID = 5320143875248203766L;
110            
111            private Collection removeHandlers=new ArrayList();
112            private Collection addHandlers=new ArrayList();
113    
114            private String inferMethodName;
115    
116            private String removeMethodName;
117    
118            private String acceptMethodName;
119    
120            /**
121             * Each addtion to this class increments version number.
122             * @author Pavel Vlasov
123             * @revision $Revision$
124             */
125            private class VersionedHashSet extends HashSet {
126                    private int version;
127                    
128                    public int getVersion() {
129                            return version;
130                    }
131                    
132                    public boolean add(Object arg) {
133                            if (super.add(arg)) {
134                                    ++version;
135                                    return true;
136                            }
137                            
138                            return false;
139                    }
140            }
141            
142            /**
143             * Default constructor.
144             * Uses "infer" for infer methods, "remove" for remove methods,
145             * and "accept" for filters.
146             *
147             */
148            public Rule() {
149                    this("infer", "remove", "accept");
150            }
151                    
152            /**
153             * Instances of this interface are passed to <code>accept()</code> methods as second parameter. This parameter
154             * can be used if one <code>accept()</code> method is bound to several <code>join()</code> methods or to
155             * several <code>join()</code> method's parameters. 
156             * @author Pavel Vlasov
157             * @revision $Revision$
158             */
159            public interface AcceptInfo {
160                    
161                    /**
162                     * @return Parameter index in the target method.
163                     */
164                    int parameterIndex();
165                    
166                    /**
167                     * @return Target <code>join()</code> method.
168                     */
169                    Method targetMethod();          
170            }
171            
172            /**
173             * Creates handlers for join method
174             * @param method Join method.
175             * @param acceptMethods accept methods associated with this join method.
176             */
177            void createJoinHandlers(final Method method, final Method[] acceptMethods) {
178                    Class[] parameterTypes = method.getParameterTypes();
179                    final Collection[] collections=new Collection[parameterTypes.length];
180                    
181                    StringBuffer msb=new StringBuffer(method.getName());
182                    msb.append("(");
183                    for (int i=0; i<parameterTypes.length; i++) {
184                            if (i>0) {
185                                    msb.append(",");
186                            }
187                            msb.append(parameterTypes[i].getName());
188                    }
189                    msb.append(")");
190                    
191                    final String signature=msb.toString();
192    
193                    for (int i=0; i<parameterTypes.length; i++) {
194                            collections[i]=getCollection(signature+"["+i+"]", collections);
195                    }
196                    
197                    for (int i=0; i<parameterTypes.length; i++) {                                
198                            final int parameterIndex=i;
199                            final Class parameterType=parameterTypes[i];
200                            
201                            addHandlers.add(new InvocationHandler() {
202    
203                                    public void invoke(Object arg, ResultConsumer resultConsumer) throws Throwable {
204                                            if (acceptMethods[parameterIndex]!=null) {
205                                                    AcceptInfo ai=new AcceptInfo() {
206    
207                                                            public int parameterIndex() {
208                                                                    return parameterIndex;
209                                                            }
210    
211                                                            public Method targetMethod() {
212                                                                    return method;
213                                                            }
214                                                            
215                                                            public String toString() {
216                                                                    return "[AcceptInfo] "+method+", parameter index: "+parameterIndex;
217                                                            }
218                                                            
219                                                    };
220                                                    
221                                                    // Don't do anything if not accepted.
222                                                    if (Boolean.FALSE.equals(acceptMethods[parameterIndex].invoke(Rule.this, new Object[] {arg, ai}))) {
223                                                            return;
224                                                    }
225                                            }
226                                                                                    
227                                            Collection actions = doTracing ? new ArrayList() : null;
228                                            
229                                            synchronized (collections) {                                            
230                                                    // Add argument to its collection, run join if it was actually added
231                                                    if (collections[parameterIndex].add(arg)) {
232                                                            Object[] args=new Object[collections.length];                                                   
233                                                            args[parameterIndex]=arg;
234                                                            VersionedHashSet negators = new VersionedHashSet(); // Negators posted in this iteration over collections.
235                                                            int[] negatorVersions = new int[collections.length];
236                                                            for (int i=0; i<collections.length; i++) {
237                                                                    negatorVersions[i]=negators.getVersion();
238                                                            }
239                                                            
240                                                            doJoin(args, 0, negators, negatorVersions, actions, resultConsumer);
241                                                    }
242                                            }
243                                            
244                                            if (doTracing && !actions.isEmpty()) {
245                                                    ((ActionTracer) owner).addActions(actions);
246                                            }
247                                    }
248                                    
249                                    public String toString() {
250                                            return "["+method.getName()+" handler] Target method: "+method+", target parameter: "+parameterIndex+" ("+parameterType+")";
251                                    }
252    
253                                    /**
254                                     * @return last non-null argument.
255                                     */
256                                    private int doJoin(Object[] args, int idx, VersionedHashSet negators, int[] negatorVersions, Collection actions, ResultConsumer rawConsumer) throws Throwable {
257                                            if (idx==args.length) {                                                                                         
258                                                    Object prevResultConsumerWrapper=currentResultConsumerWrapper.get();
259                                                    try {
260                                                            ResultConsumerWrapper resultConsumer = new ResultConsumerWrapper(method, args, negators, actions, rawConsumer);
261                                                            currentResultConsumerWrapper.set(resultConsumer);
262                                                            // change detection setup                                                       
263                                                            ChangeDetector[] cda=new ChangeDetector[args.length];
264                                                            for (int i=0; i<cda.length; i++) {
265                                                                    cda[i]=newChangeDetector(args[i]);
266                                                            }
267                                                            
268                                                            Object ret=method.invoke(Rule.this, args);
269                                                            ++invocationCounter;
270                                                            
271                                                            // change detection
272                                                            for (int i=0; i<cda.length; i++) {
273                                                                    if (cda[i]!=null && cda[i].hasChanged()) {
274                                                                            update(args[i]);
275                                                                    }
276                                                            }                                                       
277                                                                    
278                                                            if (ret!=null) {
279                                                                    resultConsumer.consume(ret);
280                                                                    
281                                                                    for (int i=0; i<args.length; i++) {                                                                  
282                                                                            Iterator it=negators.iterator();
283                                                                            while (it.hasNext()) {
284                                                                                    if (Conclusion.object2Negator(args[i], (Negator) it.next())) {
285                                                                                            args[i]=null;
286                                                                                            return i-1; // Last non-negated argument index.
287                                                                                    }
288                                                                            }                                                                       
289                                                                    }                                                               
290                                                            }
291                                                            return idx;
292                                                    } finally {
293                                                            currentResultConsumerWrapper.set(prevResultConsumerWrapper);
294                                                    }
295                                            } else if (idx==parameterIndex) {
296                                                    return doJoin(args, idx+1, negators, negatorVersions, actions, rawConsumer);                                            
297                                            } else {
298                                                     // Figure out whether negators shall be applied to collection entries
299                                                    boolean applyNegators = negators.getVersion() > negatorVersions[idx];
300                                                    negatorVersions[idx]=negators.getVersion();
301                                                    
302                                                    Iterator it=collections[idx].iterator();
303                                                    Z: while (it.hasNext()) {
304                                                            args[idx]=it.next();
305                                                            
306                                                            // Apply negators to the next element if needed
307                                                            if (applyNegators) {
308                                                                    Iterator nit=negators.iterator();
309                                                                    while (nit.hasNext()) {
310                                                                            if (Conclusion.object2Negator(args[idx], (Negator) nit.next())) {
311                                                                                    it.remove();
312                                                                                    continue Z;
313                                                                            }
314                                                                    }
315                                                            } 
316                                                            
317                                                            int lna = doJoin(args, idx+1, negators, negatorVersions, actions, rawConsumer);
318                                                            if (lna<idx) {
319                                                                    return lna;
320                                                            }
321                                                            applyNegators = applyNegators || negators.getVersion() > negatorVersions[idx];
322                                                    }
323                                                    return idx;
324                                            }
325                                    }
326    
327                                    public Class getParameterType() {
328                                            return parameterType;
329                                    }
330    
331                                    public Class[] getFactTypes() {
332                                            Class[] ret = getMethodFactTypes(method.getParameterTypes());
333                                            if (ret!=null) {
334                                                    return ret;
335                                            }
336                                            
337                                            Class mrt = method.getReturnType();
338                                            return void.class.equals(mrt) ? new Class[] {} : new Class[] {mrt};
339                                    }
340                                    
341                            });
342                    
343                    }                                       
344            }               
345            
346            /**
347             * 
348             * @param inferMethodName Methods with this name and one or more arguments are invoked when an object of type 
349             * compatible with one of parameters is posted to the object bus. 
350             * @param removeMethodName Single-argument methods with this name will be invoked when rule set's remove
351             * method with compatible type is invoked. Generally rules shall not implement this method because collection
352             * manager and handle manager take care of removal of the object and conclusions made based on this object from
353             * the knowledge base.
354             * @param acceptMethodName Methods with this name and two arguments - the first of equal type and the second
355             * of <code>AcceptInfo</code> type are used to filter inputs to infer methods with more than one parameter. 
356             * Type of the first argument of <code>accept</code> method and corresponding argument of <code>infer</code>
357             * must be equal. <code>accept()</code> method's return type must be <code>boolean</code>.
358             */
359            protected Rule(String inferMethodName, String removeMethodName, String acceptMethodName) {
360                    this.inferMethodName=inferMethodName;
361                    this.removeMethodName=removeMethodName;
362                    this.acceptMethodName=acceptMethodName;
363            }
364    
365            private long invocationCounter;
366            
367            /**
368             * @return Number of invocations. Call of reset() method zeroes the counter.
369             */
370            public long getInvocationCounter() {
371                    return invocationCounter;
372            }
373            
374            /**
375             * Wraps result consumer to inject derivations and do other stuff.
376             * @author Pavel
377             *
378             */
379            private class ResultConsumerWrapper implements ResultConsumer {
380    
381                    private Method method;
382                    private Object[] args;
383                    private Collection negators;
384                    private Collection actions;
385                    private ResultConsumer master;
386                    
387                    /**
388                     * @param method
389                     * @param consumer
390                     */
391                    ResultConsumerWrapper(
392                                    Method method, 
393                                    Object[] args, 
394                                    Collection negators, 
395                                    Collection actions, 
396                                    ResultConsumer resultConsumer) {
397                            
398                            this.method = method;
399                            this.args=args;
400                            this.negators=negators;
401                            this.actions=actions;
402                            this.master = resultConsumer;
403                    }
404                    
405                    public void consume(Object fact) {
406                            if (fact!=null) {
407                                    if (fact instanceof Conclusion) {
408                                            Derivation derivation=new Derivation(Rule.this, method);
409                                            for (int i=0; i<args.length; i++) {
410                                                    derivation.addSourceFact(args[i]);                              
411                                            }
412                                            ((Conclusion) fact).addDerivation(derivation);                          
413                                    }
414                                    
415                                    if (fact instanceof Negator) {
416                                            addNegator((Negator) fact);
417                                    }
418                                    
419                                    if (doTracing) {
420                                            addPostTrace(fact);
421                                    }                       
422                                    
423                                    master.consume(fact);
424                            }
425                    }
426                    
427                    boolean addNegator(Negator negator) {
428                            if (negators!=null && negators.add(negator)) {
429                                    return true;
430                            }
431                            
432                            return false;
433                    }
434    
435                    public void addPostTrace(Object fact) {
436                            for (int i=0; i<args.length; i++) {
437                                    actions.add(((ActionTracer) owner).createPostAction(args[i], fact));
438                            }
439                    }
440                    
441                    public void addRemoveTrace(Object fact) {
442                            for (int i=0; i<args.length; i++) {
443                                    actions.add(((ActionTracer) owner).createPostAction(args[i], fact));
444                            }
445                    }               
446                    
447                    
448            };
449            
450            
451            
452            private void createHandlers(final String methodName, Collection handlers) {
453                    
454                    Method[] methods=getClass().getMethods();                       
455                    for (int i=0; i<methods.length; i++) {
456                            if (methods[i].getParameterTypes().length == 1) {
457                                    if (methodName.equals(methods[i].getName())) {
458                                            final Method method=methods[i];
459                                            final Class actualParameterType = method.getParameterTypes()[0];
460                                            
461                                            // Make sure that target method will accept parameterType arguments
462                                            handlers.add(
463                                                            new InvocationHandler() {
464    
465                                                                    public void invoke(Object arg, final ResultConsumer rawConsumer) throws Throwable {                                                                                                                                                                                                             
466                                                                            // Invoke only with compatible parameters.
467                                                                            if (actualParameterType.isInstance(arg)) {
468                                                                                    Object prevResultConsumer=currentResultConsumerWrapper.get();
469                                                                                    try {
470                                                                                                                                                                                    
471                                                                                            Object[] args = new Object[] {arg};
472                                                                                            Collection actions = doTracing ? new ArrayList() : null;        
473                                                                                            ResultConsumerWrapper resultConsumer = new ResultConsumerWrapper(method, args, null, actions, rawConsumer);
474                                                                                            currentResultConsumerWrapper.set(resultConsumer);
475                                                                                            ChangeDetector cd=newChangeDetector(arg); 
476                                                                                            Object ret=method.invoke(Rule.this, args);
477                                                                                            ++invocationCounter;
478                                                                                            if (cd!=null && cd.hasChanged()) {
479                                                                                                    update(arg);
480                                                                                            }
481                                                                                            if (ret!=null) {
482                                                                                                    resultConsumer.consume(ret);
483                                                                                            }
484                                                                                            
485                                                                                            if (doTracing && !actions.isEmpty()) {
486                                                                                                    ((ActionTracer) owner).addActions(actions);
487                                                                                            }                                                                                       
488                                                                                    } finally {
489                                                                                            currentResultConsumerWrapper.set(prevResultConsumer);
490                                                                                    }
491                                                                            }
492                                                                    }
493    
494                                                                    public Class getParameterType() {
495                                                                            return actualParameterType;
496                                                                    }
497                                                                    
498                                                                    public String toString() {
499                                                                            return "["+methodName+" handler] Target method: "+method;
500                                                                    }
501    
502                                                                    public Class[] getFactTypes() {
503                                                                            Class[] ret = getMethodFactTypes(method.getParameterTypes());
504                                                                            if (ret!=null) {
505                                                                                    return ret;
506                                                                            }
507                                                                            
508                                                                            Class mrt = method.getReturnType();
509                                                                            return void.class.equals(mrt) ? new Class[] {} : new Class[] {mrt};
510                                                                    }
511                                                                    
512                                                            });
513                                    }
514                            }
515                    }               
516                    
517            }
518            
519            private void createJoinHandlers(String acceptMethodName, String joinMethodName) {
520                    
521                    Method[] methods=getClass().getMethods();                       
522                    for (int i=0; i<methods.length; i++) {
523                            if (methods[i].getParameterTypes().length > 1) {
524                                    if (joinMethodName.equals(methods[i].getName())) {
525                                            Method joinMethod=methods[i];
526                                            Class[] parameterTypes = joinMethod.getParameterTypes();
527                                            Method[] acceptMethods=new Method[parameterTypes.length];                                       
528                                            for (int j=0; j<parameterTypes.length; j++) { // For each parameter type
529                                                    for (int k=0; k<methods.length; k++) { // Find accept method
530                                                            Class[] acceptParameterTypes = methods[k].getParameterTypes();
531                                                            if (acceptMethodName.equals(methods[k].getName()) 
532                                                                            && acceptParameterTypes.length==2
533                                                                            && boolean.class.equals(methods[k].getReturnType())
534                                                                            && parameterTypes[j].equals(acceptParameterTypes[0])
535                                                                            && AcceptInfo.class.equals(acceptParameterTypes[1])) {
536                                                                    acceptMethods[j]=methods[k];
537                                                            }
538                                                    }
539                                            }
540                                            
541                                            createJoinHandlers(joinMethod, acceptMethods);
542                                    }                                       
543                            }
544                    }                               
545            }
546            
547            private boolean doTracing;
548            
549            private boolean started;
550            
551            /**
552             * Locates collection manager.
553             */
554            public void start() throws ConfigurationException {
555                    super.start();
556                    addHandlers.clear();
557                    removeHandlers.clear();
558                    createHandlers(inferMethodName, addHandlers);
559                    createJoinHandlers(acceptMethodName, inferMethodName);                          
560                    createHandlers(removeMethodName, removeHandlers);
561                    doTracing = owner instanceof ActionTracer;
562                    started = true;
563            }
564    
565            public Collection getInvocationHandlers() {
566                    return addHandlers;
567            }
568            
569            /**
570             * @return Collection of remove handlers.
571             */
572            public Collection getRemoveHandlers() {
573                    return removeHandlers;
574            }
575            
576            /**
577             * Adds new fact to knowledge base.
578             * Returning value from inference methods has the same effect.
579             * @param fact
580             */
581            protected void post(Object fact) {
582                    if (fact!=null) {
583                            ResultConsumerWrapper resultConsumer = (ResultConsumerWrapper) currentResultConsumerWrapper.get();                      
584                            if (resultConsumer==null) {
585                                    throw new IllegalStateException("post() can be invoked only within rule method call.");
586                            }
587                            
588                            resultConsumer.consume(fact);
589                    }
590            }       
591            
592            /**
593             * Invokes remove method of the knowledge base and adds trace action.
594             */
595            protected void remove(final Object fact) {
596                    if (fact!=null) {
597                            ResultConsumerWrapper resultConsumer = (ResultConsumerWrapper) currentResultConsumerWrapper.get();                      
598                            if (resultConsumer==null) {
599                                    throw new IllegalStateException("remove() can be invoked only within rule method call.");
600                            }
601                            
602                            if (doTracing) {
603                                    resultConsumer.addRemoveTrace(fact);
604                            }
605                            
606                            resultConsumer.consume(new KnowledgeBase.KnowledgeBaseCommand() {
607    
608                                    public void execute(KnowledgeBase knowledgeBase) {
609                                            knowledgeBase.remove(fact);
610                                            
611                                    }
612                                    
613                            });
614                    }
615            }
616            
617            /**
618             * Invokes update method of the knowledge base and adds trace action.
619             */
620            protected void update(final Object fact) {
621                    if (fact!=null) {
622                            ResultConsumerWrapper resultConsumer = (ResultConsumerWrapper) currentResultConsumerWrapper.get();                      
623                            if (resultConsumer==null) {
624                                    throw new IllegalStateException("update() can be invoked only within rule method call.");
625                            }
626                            
627                            if (doTracing) {
628                                    resultConsumer.addRemoveTrace(fact);
629                            }
630                            
631                            resultConsumer.consume(new KnowledgeBase.KnowledgeBaseCommand() {
632    
633                                    public void execute(KnowledgeBase knowledgeBase) {
634                                            knowledgeBase.remove(fact);
635                                            knowledgeBase.add(fact);
636                                    }
637                                    
638                            });
639                    }
640            }
641            
642            public void reset() {
643                    super.reset();
644                    resetInvocationCounter();
645            }
646            
647            /**
648             * Resets invocation counter.
649             * @return counter value before reset.
650             */
651            protected long resetInvocationCounter() {
652                    long ret = invocationCounter;
653                    invocationCounter = 0;
654                    return ret;
655            }
656            
657            // List<Class> -> Class[]
658            private Map methodFactTypes = new HashMap();
659            
660            /**
661             * Rule methods can return facts and post facts. Backward reasoning engine
662             * needs to know types of rule outputs. From rule class introspection
663             * the rule system knows about rule method return type, but it doesn't know
664             * about types posted through <code>post()</code>.
665             * 
666             * Also, return type might not be enough, as returned instances may implement
667             * interfaces which other rules are interested in, but which are not declared
668             * in the rule return type.
669             * 
670             * This method allows rules to inform the inference system about posted types.
671             * <P>
672             * This method shall be invoked before rule is started. Place invocations of 
673             * this method in rule constructors.
674             * <P>
675             * Only rules used in backward reasoning need/must call this method to ensure
676             * proper reasoning. For methods without fact types information set through 
677             * this method, the rule system uses method return type. 
678             * 
679             * @param parameterTypes Reasoning method parameter types. Method name is not 
680             * required as it is known.
681             * @param factTypes Fact types produced by given reasoning (inference) method.
682             * If rule method returns facts as well as posts them, then method return type 
683             * must be included in the factTypes array. 
684             */
685            protected final void setMethodFactTypes(Class[] parameterTypes, Class[] factTypes) {
686                    if (started) {
687                            throw new IllegalStateException("Method fact types cannot be set once rule is started.");
688                    }
689                    
690                    methodFactTypes.put(new ArrayList(Arrays.asList(parameterTypes)), factTypes);
691            }
692            
693            /**
694             * Convenience method, delegates to <code>setMethodFactTypes(Class[] parameterTypes, Class[] factTypes)</code>
695             * @param parameterType
696             * @param factTypes
697             */
698            protected final void setMethodFactTypes(Class parameterType, Class[] factTypes) {
699                    setMethodFactTypes(new Class[] {parameterType}, factTypes);
700            }
701            
702            /**
703             * Convenience method, delegates to <code>setMethodFactTypes(Class[] parameterTypes, Class[] factTypes)</code>
704             * @param parameterType
705             * @param factTypes
706             */
707            protected final void setMethodFactTypes(Class parameterType, Class factType) {
708                    setMethodFactTypes(parameterType, new Class[] {factType});
709            }
710            
711            /**
712             * Convenience method, delegates to <code>setMethodFactTypes(Class[] parameterTypes, Class[] factTypes)</code>
713             * @param parameterType
714             * @param factTypes
715             */
716            protected final void setMethodFactTypes(Class[] parameterTypes, Class factType) {
717                    setMethodFactTypes(parameterTypes, new Class[] {factType});
718            }
719            
720            private Class[] getMethodFactTypes(Class[] parameterTypes) {
721                    return (Class[]) methodFactTypes.get(new ArrayList(Arrays.asList(parameterTypes)));
722            }
723    }