001/* CachedMethod provides methods for reflecting methods based on token types.
002
003 Copyright (c) 1998-2015 The Regents of the University of California and
004 Research in Motion Limited.
005 All rights reserved.
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the above
009 copyright notice and the following two paragraphs appear in all copies
010 of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA OR RESEARCH IN MOTION
013 LIMITED BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL,
014 INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS
015 SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA
016 OR RESEARCH IN MOTION LIMITED HAVE BEEN ADVISED OF THE POSSIBILITY OF
017 SUCH DAMAGE.
018
019 THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION LIMITED
020 SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
021 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
022 PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
023 BASIS, AND THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION
024 LIMITED HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
025 ENHANCEMENTS, OR MODIFICATIONS.
026
027
028 */
029package ptolemy.data.expr;
030
031import java.lang.reflect.InvocationTargetException;
032import java.lang.reflect.Method;
033import java.util.Hashtable;
034import java.util.Iterator;
035
036import ptolemy.data.ArrayToken;
037import ptolemy.data.MatrixToken;
038import ptolemy.data.type.ArrayType;
039import ptolemy.data.type.BaseType;
040import ptolemy.data.type.MatrixType;
041import ptolemy.data.type.Type;
042import ptolemy.data.type.TypeLattice;
043import ptolemy.kernel.util.IllegalActionException;
044import ptolemy.kernel.util.InternalErrorException;
045
046///////////////////////////////////////////////////////////////////
047//// CachedMethod
048
049/**
050 An instance of this class represents a method or function that is
051 invoked by the Ptolemy II expression evaluator.  Instances of this
052 class are returned by the static findMethod() method, and can be
053 invoked by the apply() method.  This class is used by the expression
054 language to find Java methods that are bound in expressions that use
055 function application, i.e. an ASTPtFunctionApplicationNode, and in method
056 invocation, i.e. an ASTPtMethodNode.
057
058 <p> This class is used to represent two distinct types of Java methods
059 that can be invoked.  The METHOD type corresponds to an instance
060 method of a java class, invoked on an object of an appropriate class
061 (the <i>base class</i>).  The FUNCTION type corresponds to a static
062 method of a java class.  These types corresponds to the two distinct
063 expression constructs that can be used to invoke Java methods.  The
064 type of construct reflected can be queried using the
065 getCachedMethodType() method, which returns either {@link #FUNCTION}
066 or {@link #METHOD}.  Additionally, this class can be used to represent
067 Java methods that were not found.  If the CachedMethod corresponds to
068 an invokeable Java method, then the isValid() method will return true.
069 CachedMethods that are not valid cannot be invoked by the invoke()
070 method.
071
072 <p> This class provides several services that distinguish it from
073 Java's built-in reflection mechanism:
074 <ol>
075 <li> Methods are found based on name and the types of ptolemy token
076 arguments, represented by instances of the ptolemy.data.type.Type
077 base class.
078 <li> FUNCTIONS are searched for in a set of classes registered with the
079 parser.
080 <li> METHODS are searched for a base class, and in all superclasses of
081 the base class.
082 <li> Found methods, represented by instances of this class, are cached
083 and indexed to improve the speed of method lookup.  The cache is
084 synchronized so that it can be safely accessed from multiple
085 threads.
086 <li> Allows for the possibility of several automatic conversions that
087 increase the applicability of single methods
088 </ol>
089
090 <p> The automatic conversions that are allowed on the arguments of
091 reflected Java methods can be particularly tricky to understand.  The
092 findMethod() method is fairly aggressive about finding valid methods
093 to invoke.  In particular, given a set of arguments with token types,
094 the findMethod() method might return a cached method that:
095
096 <ol>
097 <li> Accepts token arguments of exactly the same type.
098 <li> Accepts token arguments that are of a type that the given types can
099 be automatically converted to, as determined by the Ptolemy type
100 lattice.
101 <li> Accepts the corresponding Java native type of either of the first
102 two cases, i.e. an IntToken argument may reflect a method that
103 accepts a Java int.
104 <li> Accepts a corresponding Java array type, if the argument type is an
105 ArrayType.
106 <li> Accepts a corresponding Java array of array type, if the argument
107 type is a MatrixType.
108 </ol>
109
110 The underlying conversions are implemented by the {@link
111 ConversionUtilities} class, which has more specific documentation the
112 underlying conversions.  The inverse of the same conversions are
113 performed on the results of a Java method invocation, in order to
114 convert the result back into a Ptolemy token.
115
116 <p> Since there may be many methods that match a particular function
117 application or method invocation, under the above conversions, the
118 findMethod() method attempts to return the most specific Java method
119 that can be called.  Generally speaking, conversions are preferred in
120 the above order.  If one Java method is not clearly preferable to all
121 others, then the findMethod() method will throw an exception.  This
122 may happen if there are multiple functions defined with varying
123 argument types.
124
125 <p> Additionally, the findMethod() method may return a CachedMethod
126 that automatically "maps" arrays and matrices over a scalar function.
127 The result of invoking the CachedMethod is an array or matrix of
128 whatever type is returned by the original function.
129
130 <p> As an example of how this works, evaluation of the expression
131 "fix([0.5, 0.1; 0.4, 0.3], 16, 1)" performs results in the invocation
132 of the method named "fix" in the ptolemy.data.expr.FixPointFunctions
133 that takes a Java double and two Java ints and returns an instance of
134 ptolemy.math.FixPoint.  This function is invoked once for each element
135 of the matrix (converting each DoubleToken into the corresponding
136 double, and each IntToken into the corresponding int), and the results
137 are packaged back into a 2x2 FixMatrixToken.
138
139 <p> Additional classes to be searched for static methods can be added
140 through the method registerFunctionClass() in PtParser.  This class
141 assumes that new classes are added to the search path before models
142 are constructed, and simply clears the internal cache and index when
143 new classes are registered.
144
145 @author Zoltan Kemenczy, Research in Motion Limited., Steve Neuendorffer, Edward Lee
146 @version $Id$
147 @since Ptolemy II 2.0
148 @Pt.ProposedRating Green (neuendor)
149 @Pt.AcceptedRating Yellow (neuendor)
150 @see ptolemy.data.expr.ASTPtFunctionApplicationNode
151 @see ptolemy.data.expr.PtParser
152 */
153public class CachedMethod {
154    /** Construct a new CachedMethod.  Generally speaking, it is not
155     *  necessary for any users of this class to invoke this method.
156     *  The static findMethod() method finds the appropriate method
157     *  for a given set of argument types and invokes this
158     *  constructor to create a cached method.
159     *
160     *  @param methodName The name of the encapsulated method.
161     *  @param argumentTypes An array of token types that can be passed to
162     *  the method, subject to the given set of conversions.  For a
163     *  FUNCTION, the number of argument types must be the same as the
164     *  number of arguments to the given method.  For a METHOD, there
165     *  is an additional type in the array (the first) corresponding
166     *  to the type of object the method is getting invoked on.
167     *  @param method The Java method that will be invoked by the
168     *  invoke() method.  If this parameter is null, then the method is
169     *  not valid and cannot be invoked.
170     *  @param conversions An array of conversions that will convert
171     *  arguments of the corresponding argument types to arguments
172     *  that the method will accept.  If the method accepts Token
173     *  arguments, then this array will contain IDENTITY_CONVERSION
174     *  conversions.  This array must be the same size as the number
175     *  of arguments to the method.
176     *  @param type The type of the method.
177     *  @exception IllegalActionException If the return type of the
178     *  cached method cannot be determined.
179     */
180    protected CachedMethod(String methodName, Type[] argumentTypes,
181            Method method, ArgumentConversion[] conversions, int type)
182            throws IllegalActionException {
183        // Note clones for safety...
184        _methodName = methodName;
185        // Kepler (jdk1.4?) requires this cast
186        _argumentTypes = argumentTypes.clone();
187        _method = method;
188
189        if (conversions != null) {
190            // Kepler (jdk1.4?) requires this cast
191            _conversions = conversions.clone();
192        } else {
193            _conversions = null;
194        }
195
196        _type = type;
197
198        _returnType = null;
199
200        // Compute the hashcode, based on the method name and argument
201        // types.
202        _hashcode = methodName.hashCode();
203
204        for (Type argumentType : argumentTypes) {
205            _hashcode += argumentType.hashCode();
206        }
207
208        // Determine the return type of the method, given our argument types.
209        // Do this LAST, since invoking the type constraint method might throw
210        // IllegalActionException, which we throw out of the constructor.
211        if (_method != null) {
212            // The default is to look at the return type of the method.
213            Class returnClass = _method.getReturnType();
214            _returnType = ConversionUtilities
215                    .convertJavaTypeToTokenType(returnClass);
216
217            // Check to see if there is a function that
218            // provides a better return type.
219            try {
220                int args = _argumentTypes.length;
221                Class[] typeArray = new Class[args];
222                Class typeClass = Type.class;
223                java.util.Arrays.fill(typeArray, typeClass);
224
225                Method typeFunction = _method.getDeclaringClass()
226                        .getMethod(_methodName + "ReturnType", typeArray);
227
228                // Invoke the function, and save the return type.
229                try {
230                    // Cast to (Object []) so as to avoid varargs warning.
231                    _returnType = (Type) typeFunction.invoke(null,
232                            (Object[]) _argumentTypes);
233                } catch (IllegalAccessException ex) {
234                    throw new RuntimeException(ex); // TODO
235                } catch (InvocationTargetException ex) {
236                    throw new RuntimeException(ex); // TODO
237                }
238            } catch (NoSuchMethodException ex) {
239                // Ignore.  Just use the default return type above.
240            }
241        }
242    }
243
244    ///////////////////////////////////////////////////////////////////
245    ////                         public methods                    ////
246
247    /** Clear the cache.  This is generally called by the PtParser class
248     *  when new classes are registered to be searched.
249     */
250    public static void clear() {
251        _cachedMethods.clear();
252    }
253
254    /** Return true if the argument is an instance of CachedMethod
255     *  that represents the same method or function as this instance.
256     *  Note that if this returns true, then both this instance and
257     *  the argument will have the same hashcode, as required by the
258     *  Object base class.
259     *  @param object The object to compare to.
260     *  @return True if the argument represents the same method or function.
261     */
262    @Override
263    public boolean equals(Object object) {
264        if (object == this) {
265            return true;
266        }
267
268        if (!(object instanceof CachedMethod)) {
269            return false;
270        }
271
272        CachedMethod cachedMethod = (CachedMethod) object;
273
274        if (!_methodName.equals(cachedMethod._methodName)) {
275            return false;
276        }
277
278        if ((_type & FUNCTION + METHOD) != (cachedMethod._type
279                & FUNCTION + METHOD)) {
280            return false;
281        }
282
283        if (_argumentTypes.length != cachedMethod._argumentTypes.length) {
284            return false;
285        }
286
287        for (int i = 0; i < _argumentTypes.length; i++) {
288            if (!_argumentTypes[i].equals(cachedMethod._argumentTypes[i])) {
289                return false;
290            }
291        }
292
293        return true;
294    }
295
296    /** Find a method or function with the specified name and argument types.
297     *  The last argument is either METHOD or FUNCTION to distinguish the
298     *  two cases.  For the METHOD case, the first argument type is the class
299     *  on which the method is to be invoked.  For the FUNCTION case, the
300     *  function is a static method of a registered class.
301     *  This method attempts to find the specified function in the cache,
302     *  and searches the registered classes only if the function is not
303     *  in the cache.
304     *
305     *  <p> This method first attempts to resolve the function in the
306     *  registered function classes by finding a method and a set of
307     *  argument conversions that allow the method to be invoked on
308     *  the given types.  If the above fails and at least one argument
309     *  is an array type or matrix type, a map is attempted over those
310     *  argument types and the registered function classes are
311     *  searched again. This process is repeated until all arguments
312     *  are scalars or a function signature match is found.
313     *
314     *  @param methodName The method or function name.
315     *  @param argumentTypes The argument types, including as the first element
316     *   the type of object on which the method is invoked, if this is a
317     *   method invocation.
318     *  @param type FUNCTION or METHOD.
319     *  @return A cached method that is valid if a matching method was found.
320     *  @exception IllegalActionException If the method cannot be found.
321     */
322    public static CachedMethod findMethod(String methodName,
323            Type[] argumentTypes, int type) throws IllegalActionException {
324        // Check to see if there is a cache already.
325        CachedMethod cachedMethod = _getCachedMethod(methodName, argumentTypes,
326                type);
327
328        if (cachedMethod != null) {
329            //    System.out.println("in cache");
330            return cachedMethod;
331        }
332
333        //  System.out.println("not in cache");
334        // First look for the method or function in the normal place.
335        if (type == METHOD) {
336            cachedMethod = _findMETHOD(methodName, argumentTypes);
337        } else if (type == FUNCTION) {
338            cachedMethod = _findFUNCTION(methodName, argumentTypes);
339        } else {
340            throw new IllegalActionException("Attempted to find a method "
341                    + "with an invalid type = " + type);
342        }
343
344        // We didn't find in the normal place, so try unrolling
345        // array and matrices.
346        if (cachedMethod == null) {
347            // System.out.println("Checking for array map");
348            // Go Look for an ArrayMapped method, instead.
349            // Check if any arguments are of array type.
350            boolean hasArray = false;
351            boolean[] isArrayArg = new boolean[argumentTypes.length];
352            Type[] newArgTypes = new Type[argumentTypes.length];
353
354            for (int i = 0; i < argumentTypes.length; i++) {
355                // System.out.println("argType[" + i + "] = "
356                //         + argumentTypes[i].getClass());
357                if (argumentTypes[i] instanceof ArrayType) {
358                    hasArray = true;
359                    newArgTypes[i] = ((ArrayType) argumentTypes[i])
360                            .getElementType();
361                    isArrayArg[i] = true;
362                } else {
363                    newArgTypes[i] = argumentTypes[i];
364                    isArrayArg[i] = false;
365                }
366            }
367
368            if (hasArray) {
369                CachedMethod mapCachedMethod = findMethod(methodName,
370                        newArgTypes, type);
371
372                if (mapCachedMethod.isValid()) {
373                    cachedMethod = new ArrayMapCachedMethod(methodName,
374                            argumentTypes, type, mapCachedMethod, isArrayArg);
375                }
376            }
377        }
378
379        if (cachedMethod == null) {
380            // System.out.println("Checking for matrix map");
381            // Go Look for a MatrixMapped method, instead.
382            // Check if any arguments are of matrix type.
383            boolean hasArray = false;
384            boolean[] isArrayArg = new boolean[argumentTypes.length];
385            Type[] newArgTypes = new Type[argumentTypes.length];
386
387            for (int i = 0; i < argumentTypes.length; i++) {
388                // System.out.println("argType[" + i + "] = "
389                //        + argumentTypes[i].getClass());
390                if (argumentTypes[i] instanceof MatrixType) {
391                    hasArray = true;
392                    newArgTypes[i] = ((MatrixType) argumentTypes[i])
393                            .getElementType();
394                    isArrayArg[i] = true;
395                } else {
396                    newArgTypes[i] = argumentTypes[i];
397                    isArrayArg[i] = false;
398                }
399            }
400
401            if (hasArray) {
402                CachedMethod mapCachedMethod = findMethod(methodName,
403                        newArgTypes, type);
404
405                if (mapCachedMethod.isValid()) {
406                    cachedMethod = new MatrixMapCachedMethod(methodName,
407                            argumentTypes, type, mapCachedMethod, isArrayArg);
408                }
409            }
410        }
411
412        if (cachedMethod == null) {
413            // System.out.println("not found...");
414            // If we haven't found anything by this point, then give
415            // up...  Store an invalid cached method, so we don't try
416            // the same search any more.
417            cachedMethod = new CachedMethod(methodName, argumentTypes, null,
418                    null, type);
419        }
420
421        // Add the method we found, or the placeholder for the missing method.
422        _addCachedMethod(cachedMethod);
423        return cachedMethod;
424    }
425
426    /** Return the type of this class, which is one of METHOD or FUNCTION.
427     *  @return The type of this class.
428     */
429    public int getCachedMethodType() {
430        return _type;
431    }
432
433    /** Return the conversions the are applied to the arguments of
434     *  this function or method.  Note that in most cases, it is not
435     *  necessary to call this method, as the invoke() method provides
436     *  all the necessary information.  It is provided for code, such
437     *  as the code generator that need more than the usual amount of
438     *  information about methods that have been found.
439     *  @return The conversions applied to the arguments.
440     */
441    public ArgumentConversion[] getConversions() {
442        return _conversions;
443    }
444
445    /** Return the method giving the operation associated with this
446     *  object, or null if none was found.  Note that in most cases,
447     *  it is not necessary to call this method, as the invoke()
448     *  method provides all the necessary information.  It is provided
449     *  for code, such as the code generator that need more than the
450     *  usual amount of information about methods that have been
451     *  found.
452     *  @return The method associated with this instance.
453     *  @exception IllegalActionException If the method was not found,
454     *  or this class represents a method mapped over an array or
455     *  matrix.
456     */
457    public Method getMethod() throws IllegalActionException {
458        if (isValid()) {
459            return _method;
460        } else {
461            throw new IllegalActionException(
462                    "No method " + toString() + " was found!");
463        }
464    }
465
466    /** Return the type of the token that results from an invocation
467     *  of this method.  Note that in most cases, it is not necessary
468     *  to call this method, as the invoke() method provides all the
469     *  necessary information.  It is provided for code, such as the
470     *  code generator that need more than the usual amount of
471     *  information about methods that have been found.
472     *  @return The type of the token that results from an invocation of
473     *  this method.
474     *  @exception IllegalActionException If a method or function with
475     *  the correct argument types was not found.
476     */
477    public Type getReturnType() throws IllegalActionException {
478        if (_returnType == null) {
479            throw new IllegalActionException("The return type of the method "
480                    + toString() + " cannot be determined because "
481                    + "no matching method was found.");
482        }
483
484        return _returnType;
485    }
486
487    /** Return the hash code.  This method is overridden to be
488     *  consistent with the overridden equals method.
489     *  @return A hash code.
490     */
491    @Override
492    public int hashCode() {
493        return _hashcode;
494    }
495
496    /** Apply the operation represented by this object to
497     *  the specified arguments.  This method performs any necessary
498     *  conversions on token arguments, and, if necessary,
499     *  converts the returned value into a token.  This method may be
500     *  overridden by derived classes to implement non-standard conversions.
501     *  @param argValues An array of Token objects that will be used
502     *  as the arguments.
503     *  @return The result of the method invocation, as a Token.
504     *  @exception IllegalActionException If this cached method is
505     *   not valid, or the invoked method throws it.
506     */
507    public ptolemy.data.Token invoke(Object[] argValues)
508            throws IllegalActionException {
509        //     System.out.println("invoking " + getMethod().toString() + " on:");
510        //         for (int i = 0; i < argValues.length; i++) {
511        //             System.out.println("arg " + i + " = " + argValues[i]);
512        //         }
513        Object result = null;
514
515        Method method = getMethod();
516
517        if (isMethod()) {
518            int num = argValues.length;
519            Object[] methodArgValues = new Object[num - 1];
520
521            if (num == 1) {
522                methodArgValues = null;
523            }
524
525            for (int i = 1; i < num; i++) {
526                methodArgValues[i - 1] = _conversions[i - 1]
527                        .convert((ptolemy.data.Token) argValues[i]);
528            }
529
530            // for (int i = 0; i < num - 1; i++) {
531            //    System.out.println("ConvertedArg " + i + " = "
532            //            + methodArgValues[i] + " class = "
533            //            + methodArgValues[i].getClass());
534            // }
535            try {
536                result = method.invoke(argValues[0], methodArgValues);
537            } catch (RuntimeException ex) {
538                // Avoid mungeing runtime exceptions, since they really
539                // are coding bugs.
540                throw ex;
541            } catch (InvocationTargetException ex) {
542                throw new IllegalActionException(null, ex.getCause(),
543                        "Error invoking method " + method + " on object "
544                                + argValues[0] + "\n");
545            } catch (Exception ex) {
546                throw new IllegalActionException(null, ex,
547                        "Error invoking method " + method + " on object "
548                                + argValues[0] + "\n");
549            }
550            // Provide better error feedback when attempting to get
551            // a value for a nonexistent key.
552            if (result == null && method.getName().equals("get")) {
553                throw new IllegalActionException(null,
554                        "No such key: " + argValues[1]);
555            }
556
557            return ConversionUtilities.convertJavaTypeToToken(result);
558        } else if (isFunction()) {
559            int num = argValues.length;
560            Object[] methodArgValues = new Object[num];
561
562            if (num == 0) {
563                methodArgValues = null;
564            }
565
566            for (int i = 0; i < num; i++) {
567                // System.out.println("Conversion = " + _conversions[i]);
568                methodArgValues[i] = _conversions[i]
569                        .convert((ptolemy.data.Token) argValues[i]);
570            }
571
572            // for (int i = 0; i < num; i++) {
573            //    System.out.println("ConvertedArg " + i + " = "
574            //           + methodArgValues[i] + " class = "
575            //           + methodArgValues[i].getClass());
576            // }
577            try {
578                result = method.invoke(method.getDeclaringClass(),
579                        methodArgValues);
580            } catch (RuntimeException ex) {
581                // Avoid mungeing runtime exceptions, since they really
582                // are coding bugs.
583                throw ex;
584            } catch (InvocationTargetException ex) {
585                throw new IllegalActionException(null, ex.getCause(),
586                        "Error invoking function " + method + "\n");
587            } catch (Exception ex) {
588                throw new IllegalActionException(null, ex,
589                        "Error invoking function " + method + "\n");
590            }
591
592            return ConversionUtilities.convertJavaTypeToToken(result);
593        }
594
595        throw new IllegalActionException("Cannot invoke function " + method
596                + " that is not simple function or method");
597    }
598
599    /** Return true if this instance represents a function (vs. a method).
600     *  @return True if this instance represents a function.
601     */
602    public boolean isFunction() {
603        return (_type & FUNCTION) == FUNCTION;
604    }
605
606    /** Return true if this instance represents a method (vs. a function).
607     *  @return True if this instance represents a method.
608     */
609    public boolean isMethod() {
610        return (_type & METHOD) == METHOD;
611    }
612
613    /** Return true if the search for the method or function represented
614     *  by this object found an invokeable method.
615     *  @return True if a method was found.
616     */
617    public boolean isValid() {
618        return _method != null;
619    }
620
621    /** Return a verbose description of the cached method being invoked.
622     *  @return A verbose description of the cached method being invoked.
623     */
624    public String methodDescription() {
625        if (isValid()) {
626            return _method.toString();
627        } else {
628            return "INVALID METHOD!!!";
629        }
630    }
631
632    /** Return a string representation.
633     *  @return A string representation.
634     */
635    @Override
636    public String toString() {
637        int initialArg = 0;
638        StringBuffer buffer = new StringBuffer();
639
640        if (isMethod()) {
641            initialArg = 1;
642            buffer.append(_argumentTypes[0].toString());
643            buffer.append(".");
644        }
645
646        buffer.append(_methodName);
647        buffer.append("(");
648
649        for (int i = initialArg; i < _argumentTypes.length; i++) {
650            if (i == initialArg) {
651                buffer.append(_argumentTypes[i].toString());
652            } else {
653                buffer.append(", " + _argumentTypes[i].toString());
654            }
655        }
656
657        buffer.append(")");
658        return buffer.toString();
659    }
660
661    ///////////////////////////////////////////////////////////////////
662    ////                         public variables                  ////
663
664    /** Indicator of a function (vs. method). */
665    public static final int FUNCTION = 8;
666
667    /** Indicator of a method (vs. function). */
668    public static final int METHOD = 16;
669
670    // Note that conversions are ordered by preference..
671    // IMPOSSIBLE_CONVERSION is the least preferable conversion, and
672    // has type of zero.
673
674    /** Impossible argument conversion. */
675    public static final ArgumentConversion IMPOSSIBLE_CONVERSION = new ArgumentConversion(
676            0);
677
678    /** Conversion from an ArrayToken to a Token array (Token[]). */
679    public static final ArgumentConversion ARRAYTOKEN_CONVERSION = new ArgumentConversion(
680            1) {
681        @Override
682        public Object convert(ptolemy.data.Token input)
683                throws IllegalActionException {
684            // Convert ArrayToken to Token[]
685            return ((ArrayToken) input).arrayValue();
686        }
687    };
688
689    /** Conversion up to a higher type has preference 2... */
690    /** Conversion from tokens to Java native types. */
691    public static final ArgumentConversion NATIVE_CONVERSION = new ArgumentConversion(
692            3) {
693        @Override
694        public Object convert(ptolemy.data.Token input)
695                throws IllegalActionException {
696            // Convert tokens to native types.
697            return ConversionUtilities.convertTokenToJavaType(input);
698        }
699    };
700
701    /** Identity conversion.  Does nothing. */
702    public static final ArgumentConversion IDENTITY_CONVERSION = new ArgumentConversion(
703            4) {
704        @Override
705        public Object convert(ptolemy.data.Token input)
706                throws IllegalActionException {
707            // The do nothing conversion.
708            return input;
709        }
710    };
711
712    ///////////////////////////////////////////////////////////////////
713    ////                         protected methods                 ////
714
715    /** Return true if the conversions in the first argument are
716     *  preferable to the conversions in the third argument, for methods
717     *  with argument types given by the second and fourth arguments.
718     *  To return true, every conversion in the first method
719     *  must be preferable or equal to conversions in the second.
720     *  The two arguments are required to have the same length, or
721     *  else an InternalErrorException will be thrown.
722     *  @param conversions1 The first set of conversions.
723     *  @param arguments1 The arguments of the first method.
724     *  @param conversions2 The second set of conversions.
725     *  @param arguments2 The arguments of the second method.
726     *  @return True if the first set of conversions is preferable
727     *   to the second.
728     */
729    protected static boolean _areConversionsPreferable(
730            ArgumentConversion[] conversions1, Class[] arguments1,
731            ArgumentConversion[] conversions2, Class[] arguments2) {
732        if (conversions1.length != conversions2.length) {
733            throw new InternalErrorException(
734                    "Conversion arrays have to have the same length.");
735        }
736
737        for (int j = 0; j < conversions1.length; j++) {
738            //  System.out.println("comparing " + conversions1[j]);
739            //  System.out.println("to        " + conversions2[j]);
740            if (conversions2[j].isPreferableTo(conversions1[j])) {
741                // Found one conversion where the second argument is
742                // preferable.  That is enough to return false.
743                // System.out.println("second arg (" + conversions2[j]
744                //        + ") is preferable to " + conversions1[j]
745                //        + ", returning false");
746                return false;
747            } else if (conversions2[j].equals(conversions1[j])) {
748                // Conversions are the same.
749                // Use the types of the arguments to get more specific.
750                // System.out.println("conversions are the same");
751                Class class1 = arguments1[j];
752                Class class2 = arguments2[j];
753
754                try {
755                    Type type1 = ConversionUtilities
756                            .convertJavaTypeToTokenType(class1);
757                    Type type2 = ConversionUtilities
758                            .convertJavaTypeToTokenType(class2);
759
760                    if (TypeLattice.compare(type2,
761                            type1) == ptolemy.graph.CPO.LOWER) {
762                        // Found one conversion where the second method
763                        // is preferable. This is enough to return false.
764                        // An argument that is lower is the lower in the
765                        // type lattice is preferable because it is
766                        // more specific.
767                        //System.out.println("Found one conversion where "
768                        //        + type2 + " is preferable to "
769                        //        + type1 + " returning false");
770
771                        return false;
772                    }
773                } catch (IllegalActionException ex) {
774                    // Failed to find a token type, so can't perform
775                    // the comparison.  Ignore the error so that it remains
776                    // possible to return true.  This allows the latest
777                    // matching method to be used.
778                }
779            }
780        }
781
782        // No argument was found where the second is preferable,
783        // so we return true.
784        //System.out.println("No argument was found were the "
785        //        + "second conversion is preferable, returning true");
786        return true;
787    }
788
789    /** Return a conversion to convert the second argument into the class
790     *  given by the first argument.
791     *  @param formal The class to which the type shall be converted.
792     *  @param actual The type to be converted.
793     *  @return The best correct conversion, or IMPOSSIBLE_CONVERSION
794     *  if no such conversion exists.
795     */
796    protected static ArgumentConversion _getConversion(Class formal,
797            Type actual) {
798        // No conversion necessary.
799        if (formal.isAssignableFrom(actual.getTokenClass())) {
800            return IDENTITY_CONVERSION;
801        }
802
803        // ArrayTokens can be converted to Token[]
804        if (actual instanceof ArrayType && formal.isArray()
805                && formal.getComponentType()
806                        .isAssignableFrom(ptolemy.data.Token.class)) {
807            return ARRAYTOKEN_CONVERSION;
808        }
809
810        try {
811            // Tokens can be converted to native types.
812            if (formal.isAssignableFrom(
813                    ConversionUtilities.convertTokenTypeToJavaType(actual))) {
814                return NATIVE_CONVERSION;
815            }
816        } catch (IllegalActionException ex) {
817            // Ignore..
818            //          ex.printStackTrace();
819        }
820
821        try {
822            // We have to do this because Java is stupid and doesn't
823            // give us a way to tell if primitive arguments are
824            // acceptable
825            if (formal.isPrimitive() || formal.isArray()) {
826                Type type = ConversionUtilities
827                        .convertJavaTypeToTokenType(formal);
828
829                if (ptolemy.graph.CPO.LOWER == TypeLattice.compare(actual,
830                        type)) {
831                    return new TypeArgumentConversion(type, NATIVE_CONVERSION);
832                }
833            }
834        } catch (IllegalActionException ex) {
835            // Ignore..
836            //          ex.printStackTrace();
837        } catch (InternalErrorException ex2) {
838            // Ignore.
839
840            // If formal is a char, then convertJavaTypeToTokenType(formal)
841            // will throw an InternalErrorException.
842            // One way to trigger this is to have an expression with
843            // the value: "valueOf(input)".  What happens is that
844            // String.valueOf(char) and String.valueOf(char[]) will be
845            // checked, which causes convertTypeTypeToTokenType() to
846            // throw the InternalErrorException.
847            //new Exception("formal: " + formal
848            //        + " formal.isPrimitive(): "
849            //        + formal.isPrimitive()
850            //        + " formal.isArray(): "
851            //        + formal.isArray(), ex2).printStackTrace();
852        }
853        return IMPOSSIBLE_CONVERSION;
854    }
855
856    /** Return the first method in the specified class that has the
857     *  specified name and can be invoked with the specified argument
858     *  types.  The last argument is an array that is populated with
859     *  the conversions that will be required to invoke this method.
860     *  This method walks through all the superclasses of the given
861     *  class, and returns the best match (resulting in the most
862     *  preferable set of argument conversions) to the given argument
863     *  types.  It returns null if there is no match.
864     *  @param library A class to be searched.
865     *  @param methodName The name of the method.
866     *  @param argumentTypes The types of the arguments.
867     *  @param conversions An array of the same length as <i>argumentTypes</i>
868     *   that will be populated by this method with the conversions to
869     *   use for the arguments.
870     *  @return the first method in the specified class that has the
871     *  specified name and can be invoked with the specified argument
872     *  types.
873     */
874    protected static Method _polymorphicGetMethod(Class library,
875            String methodName, Type[] argumentTypes,
876            ArgumentConversion[] conversions) {
877        // This method might appear to duplicate the operation of the
878        // getMethod() method in java.lang.Class.  However, that class
879        // does not support polymorphism, or traversal through
880        // superclasses.  It is simpler to just walk the class
881        // hierarchy ourselves.
882        Method matchedMethod = null;
883        ArgumentConversion[] matchedConversions = new ArgumentConversion[conversions.length];
884
885        while (library != null) {
886            // We want to ascend the class hierarchy in a controlled way
887            // so we use getDeclaredMethods() and getSuperclass()
888            // instead of getMethods().  Note that this approach has the
889            // side effect that additional methods (not only public) are
890            // accessible.
891            Method[] methods;
892
893            try {
894                methods = library.getDeclaredMethods();
895            } catch (SecurityException security) {
896                // We are in an applet.
897                // This hack will likely only work for java.lang.Math.cos()
898                methods = library.getMethods();
899            }
900
901            for (int i = 0; i < methods.length; i++) {
902                // Check the name.
903                if (!methods[i].getName().equals(methodName)) {
904                    continue;
905                }
906
907                Class[] arguments = methods[i].getParameterTypes();
908                int actualArgCount;
909
910                if (argumentTypes == null) {
911                    actualArgCount = 0;
912                } else {
913                    actualArgCount = argumentTypes.length;
914                }
915
916                // Check the number of arguments.
917                if (arguments.length != actualArgCount) {
918                    continue;
919                }
920
921                //  System.out.println("checking method " + methods[i]);
922                // Check the compatibility of arguments.
923                boolean match = true;
924
925                for (int j = 0; j < arguments.length && match; j++) {
926                    ArgumentConversion conversion = _getConversion(arguments[j],
927                            argumentTypes[j]);
928
929                    // System.out.println("formalType is "
930                    //        + arguments[j] + " " + arguments[j].getName());
931                    // System.out.println("actualType is " + argumentTypes[j]
932                    //        + " " + argumentTypes[j].getClass().getName());
933                    match = match && conversion != IMPOSSIBLE_CONVERSION;
934                    // System.out.println("match: " + match + " conversion: " + conversion);
935
936                    conversions[j] = conversion;
937                }
938
939                // If there was a previous match, then check to see
940                // which one is preferable.
941                if (match && matchedMethod != null) {
942                    // Set match to false if previously found match is
943                    // preferable to this one.  matchedConversions is
944                    // the set of previously found conversions.
945                    match = _areConversionsPreferable(conversions, arguments,
946                            matchedConversions,
947                            matchedMethod.getParameterTypes());
948                }
949
950                if (match) {
951                    // System.out.println("still a match after _areConversionsPreferable");
952                    // If still a match, then remember the method for later,
953                    // so it can be checked against any other match found.
954                    matchedMethod = methods[i];
955                    System.arraycopy(conversions, 0, matchedConversions, 0,
956                            actualArgCount);
957                }
958            }
959
960            library = library.getSuperclass();
961        }
962
963        System.arraycopy(matchedConversions, 0, conversions, 0,
964                conversions.length);
965        return matchedMethod;
966    }
967
968    ///////////////////////////////////////////////////////////////////
969    ////                         private methods                   ////
970
971    /** Add the specified instance of this class to the cache.
972     *  @param cachedMethod The instance to add to the cache.
973     */
974    private static void _addCachedMethod(CachedMethod cachedMethod) {
975        _cachedMethods.put(cachedMethod, cachedMethod);
976    }
977
978    // Find a CachedMethod of type FUNCTION, in a registered class,
979    // that accepts arguments argumentTypes[0..length].  Return null if no
980    // method can be found.
981    private static CachedMethod _findFUNCTION(String methodName,
982            Type[] argumentTypes) throws IllegalActionException {
983        CachedMethod cachedMethod = null;
984        ArgumentConversion[] conversions = new ArgumentConversion[argumentTypes.length];
985
986        // Search the registered function classes
987        Iterator allClasses = PtParser.getRegisteredClasses().iterator();
988
989        // Keep track of multiple matches, to try to find the
990        // most specific one.
991        Method preferredMethod = null;
992        ArgumentConversion[] preferredConversions = null;
993
994        while (allClasses.hasNext() && cachedMethod == null) {
995            Class nextClass = (Class) allClasses.next();
996
997            //System.out.println("Examining registered class: "
998            //  + nextClass);
999            try {
1000                Method method = _polymorphicGetMethod(nextClass, methodName,
1001                        argumentTypes, conversions);
1002
1003                if (method != null) {
1004                    // System.out.println("Found match: " + method);
1005                    // Compare to previous match, if there has
1006                    // been one.
1007                    if (preferredMethod == null || _areConversionsPreferable(
1008                            conversions, method.getParameterTypes(),
1009                            preferredConversions,
1010                            preferredMethod.getParameterTypes())) {
1011                        // Either there is no previous match,
1012                        // or the current match is preferable
1013                        // or equivalent to the previous match.
1014                        preferredMethod = method;
1015                        // Kepler (jdk1.4?) requires this cast
1016                        preferredConversions = conversions.clone();
1017                    }
1018                }
1019            } catch (SecurityException security) {
1020                // If we are running under an Applet, then we
1021                // may end up here if, for example, we try
1022                // to invoke the non-existent quantize function on
1023                // java.lang.Math.
1024            }
1025        }
1026
1027        if (preferredMethod != null) {
1028            // System.out.println("*** Chosen method: "
1029            //        + preferredMethod);
1030            // System.out.println("*** Chosen conversions: "
1031            //        + preferredConversions[0]);
1032            cachedMethod = new CachedMethod(methodName, argumentTypes,
1033                    preferredMethod, preferredConversions, FUNCTION);
1034        }
1035
1036        return cachedMethod;
1037    }
1038
1039    // Find a CachedMethod of type METHOD, in a class that extends
1040    // from the type indicated by argumentTypes[0], that accepts arguments
1041    // argumentTypes[1..length].  Return null if no method can be found.
1042    private static CachedMethod _findMETHOD(String methodName,
1043            Type[] argumentTypes) throws IllegalActionException {
1044        CachedMethod cachedMethod = null;
1045
1046        // Try to reflect the method.
1047        int num = argumentTypes.length;
1048        ArgumentConversion[] conversions = new ArgumentConversion[num - 1];
1049
1050        Class destTokenClass = argumentTypes[0].getTokenClass();
1051        Type[] methodArgTypes;
1052
1053        if (num == 1) {
1054            methodArgTypes = null;
1055        } else {
1056            methodArgTypes = new Type[num - 1];
1057
1058            for (int i = 1; i < num; i++) {
1059                methodArgTypes[i - 1] = argumentTypes[i];
1060            }
1061        }
1062
1063        try {
1064            Method method = _polymorphicGetMethod(destTokenClass, methodName,
1065                    methodArgTypes, conversions);
1066
1067            if (method != null) {
1068                cachedMethod = new CachedMethod(methodName, argumentTypes,
1069                        method, conversions, METHOD);
1070            }
1071        } catch (SecurityException security) {
1072            // If we are running under an Applet, then we
1073            // may end up here if, for example, we try
1074            // to invoke the non-existent quantize function on
1075            // java.lang.Math.
1076        }
1077
1078        if (cachedMethod == null) {
1079            // Native convert the base class.
1080            // System.out.println("Checking for base conversion");
1081            destTokenClass = ConversionUtilities
1082                    .convertTokenTypeToJavaType(argumentTypes[0]);
1083
1084            Method method = _polymorphicGetMethod(destTokenClass, methodName,
1085                    methodArgTypes, conversions);
1086
1087            if (method != null) {
1088                cachedMethod = new BaseConvertCachedMethod(methodName,
1089                        argumentTypes, method, NATIVE_CONVERSION, conversions);
1090            }
1091        }
1092
1093        return cachedMethod;
1094    }
1095
1096    /** Return the CachedMethod that corresponds to methodName and
1097     *  argumentTypes if it had been cached previously.
1098     */
1099    private static CachedMethod _getCachedMethod(String methodName,
1100            Type[] argumentTypes, int type) throws IllegalActionException {
1101        CachedMethod key = new CachedMethod(methodName, argumentTypes, null,
1102                null, type);
1103
1104        // System.out.println("findMethod:" + key);
1105        CachedMethod method = _cachedMethods.get(key);
1106        return method;
1107    }
1108
1109    ///////////////////////////////////////////////////////////////////
1110    ////                         private variables                 ////
1111    // The method name.
1112    private String _methodName;
1113
1114    // The token types of the arguments.
1115    private Type[] _argumentTypes;
1116
1117    // The Java method to be invoked.
1118    private Method _method;
1119
1120    // Conversions that convert the types of the arguments to types
1121    // acceptable by the given method.
1122    private ArgumentConversion[] _conversions;
1123
1124    // The precomputed hashcode for this cached method.
1125    private int _hashcode;
1126
1127    // The return type of the the method, as determined from the method itself,
1128    // or from a monotonic function.
1129    private Type _returnType;
1130
1131    // The type.
1132    private int _type;
1133
1134    // The static table containing cached methods.  Note that a
1135    // synchronized hashtable is used to provide safe access to the
1136    // table of methods from multiple threads.
1137    private static Hashtable<CachedMethod, CachedMethod> _cachedMethods = new Hashtable<CachedMethod, CachedMethod>();
1138
1139    ///////////////////////////////////////////////////////////////////
1140    ////                         inner classes                     ////
1141    ///////////////////////////////////////////////////////////////////
1142    //// ArgumentConversion
1143
1144    /** Class representing an argument conversion.  Instances of this
1145     *  class are returned by getConversions().  Note that in most
1146     *  cases, it is not necessary to reference this class directly,
1147     *  as the invoke() method applies all the necessary conversions.
1148     *  It is provided for code, such as the code generator that need
1149     *  more than the usual amount of information about methods that
1150     *  have been found.
1151     *  <p>The preference is n index given an order to the preference of
1152     *  conversions.  Lower preferences represent less desirable
1153     *  conversions than higher preferences.
1154     */
1155    public static class ArgumentConversion {
1156        /** Construct an argument conversion.
1157         *  @param preference The preference of this conversion.
1158         *  The preference is n index given an order to the preference of
1159         *  conversions.  Lower preferences represent less desirable
1160         *  conversions than higher preferences.
1161         */
1162        private ArgumentConversion(int preference) {
1163            _preference = preference;
1164        }
1165
1166        /** Return the preference of this conversion, relative to
1167         *  other conversions.  The higher the preference, the more
1168         *  preferable the conversion.
1169         *  @return The preference of this conversion.
1170         */
1171        public int getPreference() {
1172            return _preference;
1173        }
1174
1175        /** Convert the given token into an object that can be used to
1176         *  invoke a method through the reflection mechanism.  Derived
1177         *  classes will override this method to provide different
1178         *  types of argument conversions.
1179         *  @param input The token to be converted
1180         *  @return The object that can be used to invoke a method
1181         *  through the reflection method.
1182         *  @exception IllegalActionException Always thrown in this
1183         *  base class.
1184         */
1185        public Object convert(ptolemy.data.Token input)
1186                throws IllegalActionException {
1187            throw new IllegalActionException(
1188                    "Cannot convert argument token " + input);
1189        }
1190
1191        /** Return true if this conversion is preferable to the given
1192         *  conversion.
1193         *  @param conversion The conversion to be tested.
1194         *  @return True if this conversion is prefereable to the given
1195         *  conversion.
1196         */
1197        public boolean isPreferableTo(ArgumentConversion conversion) {
1198            return _preference > conversion.getPreference();
1199        }
1200
1201        /** Return a string representation of this conversion.
1202         *  @return A string representation of this conversion.
1203         */
1204        @Override
1205        public String toString() {
1206            return "Conversion " + _preference;
1207        }
1208
1209        /**  The preference is n index given an order to the
1210         *  preference of conversions.  Lower preferences represent
1211         *  less desirable conversions than higher preferences.
1212         */
1213        protected int _preference;
1214    }
1215
1216    ///////////////////////////////////////////////////////////////////
1217    //// TypeArgumentConversion
1218
1219    /** A class representing an argument conversion to another ptolemy type,
1220     *  followed by the given conversion.
1221     *  This conversion always has preference two.
1222     */
1223    public static class TypeArgumentConversion extends ArgumentConversion {
1224        private TypeArgumentConversion(Type type,
1225                ArgumentConversion conversion) {
1226            super(2);
1227            _conversionType = type;
1228            _conversion = conversion;
1229        }
1230
1231        /** Convert the given token into an object that can be used to
1232         *  invoke a method through the reflection mechanism.  Derived
1233         *  classes will override this method to provide different
1234         *  types of argument conversions.
1235         */
1236        @Override
1237        public Object convert(ptolemy.data.Token input)
1238                throws IllegalActionException {
1239            ptolemy.data.Token token = _conversionType.convert(input);
1240            return _conversion.convert(token);
1241        }
1242
1243        /** Return true if this conversion is preferable to the given
1244         * conversion.
1245         */
1246        @Override
1247        public boolean isPreferableTo(ArgumentConversion conversion) {
1248            if (_preference > conversion.getPreference()) {
1249                return true;
1250            } else if (_preference == conversion.getPreference()) {
1251                // Assume it is a TypeArgumentConversion.
1252                TypeArgumentConversion argumentConversion = (TypeArgumentConversion) conversion;
1253
1254                // FIXME: compare types.
1255                // System.out.println("types: " + _conversionType + " " + argumentConversion._conversionType);
1256                if (TypeLattice.compare(_conversionType,
1257                        argumentConversion._conversionType) == ptolemy.graph.CPO.LOWER) {
1258                    return true;
1259                }
1260
1261                if (_conversionType == BaseType.INT
1262                        && argumentConversion._conversionType == BaseType.FLOAT) {
1263                    // If we evaluate abs(-1ub), we should get 1, not
1264                    // 1.0.  Return true a conversion to int is
1265                    // compared to a conversion to float, meaning a
1266                    // conversion to int is preferable to a conversion
1267                    // to float.
1268
1269                    return true;
1270                }
1271                return _conversion
1272                        .isPreferableTo(argumentConversion._conversion);
1273            } else {
1274                return false;
1275            }
1276        }
1277
1278        /** Return a string representation of this conversion.
1279         */
1280        @Override
1281        public String toString() {
1282            return "TypeConversion(" + _conversionType + ", " + _conversion
1283                    + ") " + _preference;
1284        }
1285
1286        private ptolemy.data.type.Type _conversionType;
1287
1288        private ArgumentConversion _conversion;
1289    }
1290
1291    ///////////////////////////////////////////////////////////////////
1292    //// BaseConvertCachedMethod
1293
1294    /** A cached method that converts the object on which the method
1295     *  is invoked as well as the arguments.  This allows us to, for
1296     *  example, invoke instance methods of ptolemy.math.Complex on
1297     *  tokens of type ComplexToken.  This cached method can only
1298     *  operate on methods.
1299     */
1300    public static class BaseConvertCachedMethod extends CachedMethod {
1301        private BaseConvertCachedMethod(String methodName, Type[] argumentTypes,
1302                Method method, ArgumentConversion baseConversion,
1303                ArgumentConversion[] conversions)
1304                throws IllegalActionException {
1305            super(methodName, argumentTypes, method, conversions, METHOD);
1306            _baseConversion = baseConversion;
1307        }
1308
1309        /** Return the conversion that is applied to the object
1310         *  upon which the method is invoked.
1311         *  @return The conversion that is applied to the object
1312         *  upon which the method is invoked.
1313         */
1314        public ArgumentConversion getBaseConversion() {
1315            return _baseConversion;
1316        }
1317
1318        @Override
1319        public ptolemy.data.Token invoke(Object[] argValues)
1320                throws IllegalActionException {
1321            argValues[0] = _baseConversion
1322                    .convert((ptolemy.data.Token) argValues[0]);
1323            return super.invoke(argValues);
1324        }
1325
1326        private ArgumentConversion _baseConversion;
1327    }
1328
1329    ///////////////////////////////////////////////////////////////////
1330    //// ArrayMapCachedMethod
1331
1332    /** A class representing the invocation of a scalar method on
1333     *  an array of elements.
1334     */
1335    public static class ArrayMapCachedMethod extends CachedMethod {
1336        /**
1337         * Constructs a CachedMethod$ArrayMapCachedMethod object.
1338         *
1339         * @param methodName The name of the method.
1340         * @param argumentTypes The types of the arguments.
1341         * @param type An integer specifying the type
1342         * @param cachedMethod The method to be invoked
1343         * @param reducedArgs    An array of booleans where if an
1344         * element of the array is true and the corresponding argument
1345         * is an ArrayToken, then invoke() handles those arguments
1346         * specially.
1347         * @exception IllegalActionException Not thrown in this derived
1348         * class, but the superclass throws it if the return type of
1349         * the cached method cannot be determined.
1350         */
1351        public ArrayMapCachedMethod(String methodName, Type[] argumentTypes,
1352                int type, CachedMethod cachedMethod, boolean[] reducedArgs)
1353                throws IllegalActionException {
1354            super(methodName, argumentTypes, null, null, type);
1355            _cachedMethod = cachedMethod;
1356            _reducedArgs = reducedArgs;
1357        }
1358
1359        /** Invoke the method represented by this CachedMethod.  This
1360         *  implements any conversions necessary to turn token arguments
1361         *  into other arguments, and to convert the result back into
1362         *  a token.
1363         *  @param argValues An array of token objects that will be used
1364         *   as the arguments.  Note that each element must be an
1365         *   ArrayToken and each ArrayToken must have the same length
1366         *   as the other ArrayTokens.
1367         *  @return The token result of the method invocation.
1368         *  @exception IllegalActionException If the invoked method
1369         *   throws it.
1370         */
1371        @Override
1372        public ptolemy.data.Token invoke(Object[] argValues)
1373                throws IllegalActionException {
1374            int dim = 0;
1375
1376            // Check the argument lengths.
1377            for (int i = 0; i < argValues.length; i++) {
1378                if (_reducedArgs[i]) {
1379                    if (argValues[i] instanceof ArrayToken) {
1380                        ArrayToken arrayToken = (ArrayToken) argValues[i];
1381
1382                        if (dim != 0 && arrayToken.length() != dim) {
1383                            throw new IllegalActionException("Argument " + i
1384                                    + " is a reducible arrayToken that "
1385                                    + "does not have compatible length!");
1386                        } else {
1387                            dim = arrayToken.length();
1388                        }
1389                    } else {
1390                        throw new IllegalActionException("Argument " + i
1391                                + " is not an instance of " + "ArrayToken!");
1392                    }
1393                }
1394            }
1395
1396            // Collect the not reducible args.
1397            // Kepler (jdk1.4?) requires this cast
1398            Object[] subArgs = argValues.clone();
1399            ptolemy.data.Token[] tokenArray = new ptolemy.data.Token[dim];
1400
1401            for (int j = 0; j < dim; j++) {
1402                for (int i = 0; i < argValues.length; i++) {
1403                    if (_reducedArgs[i]) {
1404                        subArgs[i] = ((ArrayToken) argValues[i]).getElement(j);
1405                    }
1406                }
1407
1408                tokenArray[j] = _cachedMethod.invoke(subArgs);
1409            }
1410
1411            return new ArrayToken(tokenArray);
1412        }
1413
1414        /** Override the base class to correctly implement the
1415         * isValid() method.
1416         */
1417        @Override
1418        public boolean isValid() {
1419            return _cachedMethod.isValid();
1420        }
1421
1422        /** Override the base class to return an array type with the
1423         *  element type being the return type of the underlying scalar
1424         *  method.
1425         *  @return An ArrayType with an appropriate element type.
1426         */
1427        @Override
1428        public Type getReturnType() throws IllegalActionException {
1429            if (!isValid()) {
1430                throw new IllegalActionException(
1431                        "The return type of the method " + toString()
1432                                + " cannot be determined because "
1433                                + "no matching method was found.");
1434            }
1435
1436            Type elementType = _cachedMethod.getReturnType();
1437            return new ArrayType(elementType);
1438        }
1439
1440        /** Return an appropriate description of the method being invoked.
1441         *  @return A description of the method being invoked.
1442         */
1443        @Override
1444        public String methodDescription() {
1445            return "ArrayMapped{" + _cachedMethod.methodDescription() + "}";
1446        }
1447
1448        private CachedMethod _cachedMethod;
1449
1450        private boolean[] _reducedArgs;
1451    }
1452
1453    ///////////////////////////////////////////////////////////////////
1454    //// MatrixMapCachedMethod
1455
1456    /** A class representing the invocation of a scalar method on
1457     *  a matrix of elements.
1458     */
1459    public static class MatrixMapCachedMethod extends CachedMethod {
1460        /**
1461         * Constructs a CachedMethod$MatrixMapCachedMethod object.
1462         *
1463         * @param methodName The name of the method.
1464         * @param argumentTypes The types of the arguments.
1465         * @param type An integer specifying the type
1466         * @param cachedMethod The method to be invoked
1467         * @param reducedArgs    An array of booleans where if an
1468         * element of the array is true and the corresponding argument
1469         * is an MatrixToken, then invoke() handles those arguments
1470         * specially.
1471         * @exception IllegalActionException Not thrown in this derived
1472         * class, but the superclass throws it if the return type of
1473         * the cached method cannot be determined.
1474         */
1475        public MatrixMapCachedMethod(String methodName, Type[] argumentTypes,
1476                int type, CachedMethod cachedMethod, boolean[] reducedArgs)
1477                throws IllegalActionException {
1478            super(methodName, argumentTypes, null, null, type);
1479            _cachedMethod = cachedMethod;
1480            _reducedArgs = reducedArgs;
1481        }
1482
1483        /** Run method represented by this cachedMethod.  This
1484         *  includes any conversions necessary to turn token arguments
1485         *  into other arguments, and to convert the result back into
1486         *  a token.
1487         *  @param argValues An array of token objects that will be used
1488         *  as the arguments.
1489         *  @return The token result of the method invocation.
1490         *  @exception IllegalActionException If the invoked method
1491         *  throws it.
1492         */
1493        @Override
1494        public ptolemy.data.Token invoke(Object[] argValues)
1495                throws IllegalActionException {
1496            int xdim = 0;
1497            int ydim = 0;
1498
1499            // Check the argument lengths.
1500            for (int i = 0; i < argValues.length; i++) {
1501                if (_reducedArgs[i]) {
1502                    if (argValues[i] instanceof MatrixToken) {
1503                        MatrixToken matrixToken = (MatrixToken) argValues[i];
1504
1505                        if (xdim != 0 && ydim != 0
1506                                && (matrixToken.getRowCount() != ydim
1507                                        || matrixToken
1508                                                .getColumnCount() != xdim)) {
1509                            throw new IllegalActionException("Argument " + i
1510                                    + " is a reducible matrixToken that "
1511                                    + "does not have compatible size!");
1512                        } else {
1513                            ydim = matrixToken.getRowCount();
1514                            xdim = matrixToken.getColumnCount();
1515                        }
1516                    } else {
1517                        throw new IllegalActionException("Argument " + i
1518                                + " is not an instance of " + "MatrixToken!");
1519                    }
1520                }
1521            }
1522
1523            // Collect the not reducible args.
1524            // Kepler (jdk1.4?) requires this cast
1525            Object[] subArgs = argValues.clone();
1526            ptolemy.data.Token[] tokenArray = new ptolemy.data.Token[xdim
1527                    * ydim];
1528
1529            int pos = 0;
1530
1531            for (int j = 0; j < ydim; j++) {
1532                for (int k = 0; k < xdim; k++) {
1533                    for (int i = 0; i < argValues.length; i++) {
1534                        if (_reducedArgs[i]) {
1535                            subArgs[i] = ((MatrixToken) argValues[i])
1536                                    .getElementAsToken(j, k);
1537                        }
1538                    }
1539
1540                    tokenArray[pos++] = _cachedMethod.invoke(subArgs);
1541                }
1542            }
1543
1544            return MatrixToken.arrayToMatrix(tokenArray, ydim, xdim);
1545        }
1546
1547        /** Override the base class to correctly implement the
1548         * isValid() method.
1549         */
1550        @Override
1551        public boolean isValid() {
1552            return _cachedMethod.isValid();
1553        }
1554
1555        @Override
1556        public Type getReturnType() throws IllegalActionException {
1557            if (!isValid()) {
1558                throw new IllegalActionException(
1559                        "The return type of the method " + toString()
1560                                + " cannot be determined because "
1561                                + "no matching method was found.");
1562            }
1563
1564            Type elementType = _cachedMethod.getReturnType();
1565            return MatrixType.getMatrixTypeForElementType(elementType);
1566        }
1567
1568        /** Return an appropriate description of the method being invoked.
1569         *  @return A description of the method being invoked.
1570         */
1571        @Override
1572        public String methodDescription() {
1573            return "MatrixMapped{" + _cachedMethod.methodDescription() + "}";
1574        }
1575
1576        private CachedMethod _cachedMethod;
1577
1578        private boolean[] _reducedArgs;
1579    }
1580}