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}