1 /*
   2  * Copyright (c) 2003, 2008, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.rmi.rmic.newrmic.jrmp;
  27 
  28 import com.sun.javadoc.ClassDoc;
  29 import com.sun.javadoc.MethodDoc;
  30 import com.sun.javadoc.Parameter;
  31 import com.sun.javadoc.Type;
  32 import java.io.IOException;
  33 import java.io.ByteArrayOutputStream;
  34 import java.io.DataOutputStream;
  35 import java.security.MessageDigest;
  36 import java.security.DigestOutputStream;
  37 import java.security.NoSuchAlgorithmException;
  38 import java.util.ArrayList;
  39 import java.util.Arrays;
  40 import java.util.Comparator;
  41 import java.util.List;
  42 import java.util.HashMap;
  43 import java.util.Map;
  44 import sun.rmi.rmic.newrmic.BatchEnvironment;
  45 
  46 import static sun.rmi.rmic.newrmic.Constants.*;
  47 import static sun.rmi.rmic.newrmic.jrmp.Constants.*;
  48 
  49 /**
  50  * Encapsulates RMI-specific information about a remote implementation
  51  * class (a class that implements one or more remote interfaces).
  52  *
  53  * WARNING: The contents of this source file are not part of any
  54  * supported API.  Code that depends on them does so at its own risk:
  55  * they are subject to change or removal without notice.
  56  *
  57  * @author Peter Jones
  58  **/
  59 final class RemoteClass {
  60 
  61     /** rmic environment for this object */
  62     private final BatchEnvironment env;
  63 
  64     /** the remote implementation class this object represents */
  65     private final ClassDoc implClass;
  66 
  67     /** remote interfaces implemented by this class */
  68     private ClassDoc[] remoteInterfaces;
  69 
  70     /** the remote methods of this class */
  71     private Method[] remoteMethods;
  72 
  73     /** stub/skeleton "interface hash" for this class */
  74     private long interfaceHash;
  75 
  76     /**
  77      * Creates a RemoteClass instance that represents the RMI-specific
  78      * information about the specified remote implementation class.
  79      *
  80      * If the class is not a valid remote implementation class or if
  81      * some other error occurs, the return value will be null, and
  82      * errors will have been reported to the supplied
  83      * BatchEnvironment.
  84      **/
  85     static RemoteClass forClass(BatchEnvironment env, ClassDoc implClass) {
  86         RemoteClass remoteClass = new RemoteClass(env, implClass);
  87         if (remoteClass.init()) {
  88             return remoteClass;
  89         } else {
  90             return null;
  91         }
  92     }
  93 
  94     /**
  95      * Creates a RemoteClass instance for the specified class.  The
  96      * resulting object is not yet initialized.
  97      **/
  98     private RemoteClass(BatchEnvironment env, ClassDoc implClass) {
  99         this.env = env;
 100         this.implClass = implClass;
 101     }
 102 
 103     /**
 104      * Returns the ClassDoc for this remote implementation class.
 105      **/
 106     ClassDoc classDoc() {
 107         return implClass;
 108     }
 109 
 110     /**
 111      * Returns the remote interfaces implemented by this remote
 112      * implementation class.
 113      *
 114      * A remote interface is an interface that is a subinterface of
 115      * java.rmi.Remote.  The remote interfaces of a class are the
 116      * direct superinterfaces of the class and all of its superclasses
 117      * that are remote interfaces.
 118      *
 119      * The order of the array returned is arbitrary, and some elements
 120      * may be superfluous (i.e., superinterfaces of other interfaces
 121      * in the array).
 122      **/
 123     ClassDoc[] remoteInterfaces() {
 124         return remoteInterfaces.clone();
 125     }
 126 
 127     /**
 128      * Returns an array of RemoteClass.Method objects representing all
 129      * of the remote methods of this remote implementation class (all
 130      * of the member methods of the class's remote interfaces).
 131      *
 132      * The methods in the array are ordered according to a comparison
 133      * of strings consisting of their name followed by their
 134      * descriptor, so each method's index in the array corresponds to
 135      * its "operation number" in the JDK 1.1 version of the JRMP
 136      * stub/skeleton protocol.
 137      **/
 138     Method[] remoteMethods() {
 139         return remoteMethods.clone();
 140     }
 141 
 142     /**
 143      * Returns the "interface hash" used to match a stub/skeleton pair
 144      * for this remote implementation class in the JDK 1.1 version of
 145      * the JRMP stub/skeleton protocol.
 146      **/
 147     long interfaceHash() {
 148         return interfaceHash;
 149     }
 150 
 151     /**
 152      * Validates this remote implementation class and computes the
 153      * RMI-specific information.  Returns true if successful, or false
 154      * if an error occurred.
 155      **/
 156     private boolean init() {
 157         /*
 158          * Verify that it is really a class, not an interface.
 159          */
 160         if (implClass.isInterface()) {
 161             env.error("rmic.cant.make.stubs.for.interface",
 162                       implClass.qualifiedName());
 163             return false;
 164         }
 165 
 166         /*
 167          * Find all of the remote interfaces of our remote
 168          * implementation class-- for each class up the superclass
 169          * chain, add each directly-implemented interface that somehow
 170          * extends Remote to a list.
 171          */
 172         List<ClassDoc> remotesImplemented = new ArrayList<ClassDoc>();
 173         for (ClassDoc cl = implClass; cl != null; cl = cl.superclass()) {
 174             for (ClassDoc intf : cl.interfaces()) {
 175                 /*
 176                  * Add interface to the list if it extends Remote and
 177                  * it is not already there.
 178                  */
 179                 if (!remotesImplemented.contains(intf) &&
 180                     intf.subclassOf(env.docRemote()))
 181                 {
 182                     remotesImplemented.add(intf);
 183                     if (env.verbose()) {
 184                         env.output("[found remote interface: " +
 185                                    intf.qualifiedName() + "]");
 186                     }
 187                 }
 188             }
 189 
 190             /*
 191              * Verify that the candidate remote implementation class
 192              * implements at least one remote interface directly.
 193              */
 194             if (cl == implClass && remotesImplemented.isEmpty()) {
 195                 if (implClass.subclassOf(env.docRemote())) {
 196                     /*
 197                      * This error message is used if the class does
 198                      * implement a remote interface through one of its
 199                      * superclasses, but not directly.
 200                      */
 201                     env.error("rmic.must.implement.remote.directly",
 202                               implClass.qualifiedName());
 203                 } else {
 204                     /*
 205                      * This error message is used if the class does
 206                      * not implement a remote interface at all.
 207                      */
 208                     env.error("rmic.must.implement.remote",
 209                               implClass.qualifiedName());
 210                 }
 211                 return false;
 212             }
 213         }
 214 
 215         /*
 216          * Convert list of remote interfaces to an array
 217          * (order is not important for this array).
 218          */
 219         remoteInterfaces =
 220             remotesImplemented.toArray(
 221                 new ClassDoc[remotesImplemented.size()]);
 222 
 223         /*
 224          * Collect the methods from all of the remote interfaces into
 225          * a table, which maps from method name-and-descriptor string
 226          * to Method object.
 227          */
 228         Map<String,Method> methods = new HashMap<String,Method>();
 229         boolean errors = false;
 230         for (ClassDoc intf : remotesImplemented) {
 231             if (!collectRemoteMethods(intf, methods)) {
 232                 /*
 233                  * Continue iterating despite errors in order to
 234                  * generate more complete error report.
 235                  */
 236                 errors = true;
 237             }
 238         }
 239         if (errors) {
 240             return false;
 241         }
 242 
 243         /*
 244          * Sort table of remote methods into an array.  The elements
 245          * are sorted in ascending order of the string of the method's
 246          * name and descriptor, so that each elements index is equal
 247          * to its operation number in the JDK 1.1 version of the JRMP
 248          * stub/skeleton protocol.
 249          */
 250         String[] orderedKeys =
 251             methods.keySet().toArray(new String[methods.size()]);
 252         Arrays.sort(orderedKeys);
 253         remoteMethods = new Method[methods.size()];
 254         for (int i = 0; i < remoteMethods.length; i++) {
 255             remoteMethods[i] = methods.get(orderedKeys[i]);
 256             if (env.verbose()) {
 257                 String msg = "[found remote method <" + i + ">: " +
 258                     remoteMethods[i].operationString();
 259                 ClassDoc[] exceptions = remoteMethods[i].exceptionTypes();
 260                 if (exceptions.length > 0) {
 261                     msg += " throws ";
 262                     for (int j = 0; j < exceptions.length; j++) {
 263                         if (j > 0) {
 264                             msg += ", ";
 265                         }
 266                         msg +=  exceptions[j].qualifiedName();
 267                     }
 268                 }
 269                 msg += "\n\tname and descriptor = \"" +
 270                     remoteMethods[i].nameAndDescriptor();
 271                 msg += "\n\tmethod hash = " +
 272                     remoteMethods[i].methodHash() + "]";
 273                 env.output(msg);
 274             }
 275         }
 276 
 277         /*
 278          * Finally, pre-compute the interface hash to be used by
 279          * stubs/skeletons for this remote class in the JDK 1.1
 280          * version of the JRMP stub/skeleton protocol.
 281          */
 282         interfaceHash = computeInterfaceHash();
 283 
 284         return true;
 285     }
 286 
 287     /**
 288      * Collects and validates all methods from the specified interface
 289      * and all of its superinterfaces as remote methods.  Remote
 290      * methods are added to the supplied table.  Returns true if
 291      * successful, or false if an error occurred.
 292      **/
 293     private boolean collectRemoteMethods(ClassDoc intf,
 294                                          Map<String,Method> table)
 295     {
 296         if (!intf.isInterface()) {
 297             throw new AssertionError(
 298                 intf.qualifiedName() + " not an interface");
 299         }
 300 
 301         boolean errors = false;
 302 
 303         /*
 304          * Search interface's declared methods.
 305          */
 306     nextMethod:
 307         for (MethodDoc method : intf.methods()) {
 308 
 309             /*
 310              * Verify that each method throws RemoteException (or a
 311              * superclass of RemoteException).
 312              */
 313             boolean hasRemoteException = false;
 314             for (ClassDoc ex : method.thrownExceptions()) {
 315                 if (env.docRemoteException().subclassOf(ex)) {
 316                     hasRemoteException = true;
 317                     break;
 318                 }
 319             }
 320 
 321             /*
 322              * If this method did not throw RemoteException as required,
 323              * generate the error but continue, so that multiple such
 324              * errors can be reported.
 325              */
 326             if (!hasRemoteException) {
 327                 env.error("rmic.must.throw.remoteexception",
 328                           intf.qualifiedName(),
 329                           method.name() + method.signature());
 330                 errors = true;
 331                 continue nextMethod;
 332             }
 333 
 334             /*
 335              * Verify that the implementation of this method throws only
 336              * java.lang.Exception or its subclasses (fix bugid 4092486).
 337              * JRMP does not support remote methods throwing
 338              * java.lang.Throwable or other subclasses.
 339              */
 340             MethodDoc implMethod = findImplMethod(method);
 341             if (implMethod != null) {           // should not be null
 342                 for (ClassDoc ex : implMethod.thrownExceptions()) {
 343                     if (!ex.subclassOf(env.docException())) {
 344                         env.error("rmic.must.only.throw.exception",
 345                                   implMethod.name() + implMethod.signature(),
 346                                   ex.qualifiedName());
 347                         errors = true;
 348                         continue nextMethod;
 349                     }
 350                 }
 351             }
 352 
 353             /*
 354              * Create RemoteClass.Method object to represent this method
 355              * found in a remote interface.
 356              */
 357             Method newMethod = new Method(method);
 358 
 359             /*
 360              * Store remote method's representation in the table of
 361              * remote methods found, keyed by its name and descriptor.
 362              *
 363              * If the table already contains an entry with the same
 364              * method name and descriptor, then we must replace the
 365              * old entry with a Method object that represents a legal
 366              * combination of the old and the new methods;
 367              * specifically, the combined method must have a throws
 368              * clause that contains (only) all of the checked
 369              * exceptions that can be thrown by both the old and the
 370              * new method (see bugid 4070653).
 371              */
 372             String key = newMethod.nameAndDescriptor();
 373             Method oldMethod = table.get(key);
 374             if (oldMethod != null) {
 375                 newMethod = newMethod.mergeWith(oldMethod);
 376             }
 377             table.put(key, newMethod);
 378         }
 379 
 380         /*
 381          * Recursively collect methods for all superinterfaces.
 382          */
 383         for (ClassDoc superintf : intf.interfaces()) {
 384             if (!collectRemoteMethods(superintf, table)) {
 385                 errors = true;
 386             }
 387         }
 388 
 389         return !errors;
 390     }
 391 
 392     /**
 393      * Returns the MethodDoc for the method of this remote
 394      * implementation class that implements the specified remote
 395      * method of a remote interface.  Returns null if no matching
 396      * method was found in this remote implementation class.
 397      **/
 398     private MethodDoc findImplMethod(MethodDoc interfaceMethod) {
 399         String name = interfaceMethod.name();
 400         String desc = Util.methodDescriptorOf(interfaceMethod);
 401         for (MethodDoc implMethod : implClass.methods()) {
 402             if (name.equals(implMethod.name()) &&
 403                 desc.equals(Util.methodDescriptorOf(implMethod)))
 404             {
 405                 return implMethod;
 406             }
 407         }
 408         return null;
 409     }
 410 
 411     /**
 412      * Computes the "interface hash" of the stub/skeleton pair for
 413      * this remote implementation class.  This is the 64-bit value
 414      * used to enforce compatibility between a stub class and a
 415      * skeleton class in the JDK 1.1 version of the JRMP stub/skeleton
 416      * protocol.
 417      *
 418      * It is calculated using the first 64 bits of an SHA digest.  The
 419      * digest is of a stream consisting of the following data:
 420      *     (int) stub version number, always 1
 421      *     for each remote method, in order of operation number:
 422      *         (UTF-8) method name
 423      *         (UTF-8) method descriptor
 424      *         for each declared exception, in alphabetical name order:
 425      *             (UTF-8) name of exception class
 426      * (where "UTF-8" includes a 16-bit length prefix as written by
 427      * java.io.DataOutput.writeUTF).
 428      **/
 429     private long computeInterfaceHash() {
 430         long hash = 0;
 431         ByteArrayOutputStream sink = new ByteArrayOutputStream(512);
 432         try {
 433             MessageDigest md = MessageDigest.getInstance("SHA");
 434             DataOutputStream out = new DataOutputStream(
 435                 new DigestOutputStream(sink, md));
 436 
 437             out.writeInt(INTERFACE_HASH_STUB_VERSION);
 438 
 439             for (Method method : remoteMethods) {
 440                 MethodDoc methodDoc = method.methodDoc();
 441 
 442                 out.writeUTF(methodDoc.name());
 443                 out.writeUTF(Util.methodDescriptorOf(methodDoc));
 444                                 // descriptors already use binary names
 445 
 446                 ClassDoc exceptions[] = methodDoc.thrownExceptions();
 447                 Arrays.sort(exceptions, new ClassDocComparator());
 448                 for (ClassDoc ex : exceptions) {
 449                     out.writeUTF(Util.binaryNameOf(ex));
 450                 }
 451             }
 452             out.flush();
 453 
 454             // use only the first 64 bits of the digest for the hash
 455             byte hashArray[] = md.digest();
 456             for (int i = 0; i < Math.min(8, hashArray.length); i++) {
 457                 hash += ((long) (hashArray[i] & 0xFF)) << (i * 8);
 458             }
 459         } catch (IOException e) {
 460             throw new AssertionError(e);
 461         } catch (NoSuchAlgorithmException e) {
 462             throw new AssertionError(e);
 463         }
 464 
 465         return hash;
 466     }
 467 
 468     /**
 469      * Compares ClassDoc instances according to the lexicographic
 470      * order of their binary names.
 471      **/
 472     private static class ClassDocComparator implements Comparator<ClassDoc> {
 473         public int compare(ClassDoc o1, ClassDoc o2) {
 474             return Util.binaryNameOf(o1).compareTo(Util.binaryNameOf(o2));
 475         }
 476     }
 477 
 478     /**
 479      * Encapsulates RMI-specific information about a particular remote
 480      * method in the remote implementation class represented by the
 481      * enclosing RemoteClass.
 482      **/
 483     final class Method implements Cloneable {
 484 
 485         /**
 486          * MethodDoc for this remove method, from one of the remote
 487          * interfaces that this method was found in.
 488          *
 489          * Note that this MethodDoc may be only one of multiple that
 490          * correspond to this remote method object, if multiple of
 491          * this class's remote interfaces contain methods with the
 492          * same name and descriptor.  Therefore, this MethodDoc may
 493          * declare more exceptions thrown that this remote method
 494          * does.
 495          **/
 496         private final MethodDoc methodDoc;
 497 
 498         /** java.rmi.server.Operation string for this remote method */
 499         private final String operationString;
 500 
 501         /** name and descriptor of this remote method */
 502         private final String nameAndDescriptor;
 503 
 504         /** JRMP "method hash" for this remote method */
 505         private final long methodHash;
 506 
 507         /**
 508          * Exceptions declared to be thrown by this remote method.
 509          *
 510          * This list may include superfluous entries, such as
 511          * unchecked exceptions and subclasses of other entries.
 512          **/
 513         private ClassDoc[] exceptionTypes;
 514 
 515         /**
 516          * Creates a new Method instance for the specified method.
 517          **/
 518         Method(MethodDoc methodDoc) {
 519             this.methodDoc = methodDoc;
 520             exceptionTypes = methodDoc.thrownExceptions();
 521             /*
 522              * Sort exception types to improve consistency with
 523              * previous implementations.
 524              */
 525             Arrays.sort(exceptionTypes, new ClassDocComparator());
 526             operationString = computeOperationString();
 527             nameAndDescriptor =
 528                 methodDoc.name() + Util.methodDescriptorOf(methodDoc);
 529             methodHash = computeMethodHash();
 530         }
 531 
 532         /**
 533          * Returns the MethodDoc object corresponding to this method
 534          * of a remote interface.
 535          **/
 536         MethodDoc methodDoc() {
 537             return methodDoc;
 538         }
 539 
 540         /**
 541          * Returns the parameter types declared by this method.
 542          **/
 543         Type[] parameterTypes() {
 544             Parameter[] parameters = methodDoc.parameters();
 545             Type[] paramTypes = new Type[parameters.length];
 546             for (int i = 0; i < paramTypes.length; i++) {
 547                 paramTypes[i] = parameters[i].type();
 548             }
 549             return paramTypes;
 550         }
 551 
 552         /**
 553          * Returns the exception types declared to be thrown by this
 554          * remote method.
 555          *
 556          * For methods with the same name and descriptor inherited
 557          * from multiple remote interfaces, the array will contain the
 558          * set of exceptions declared in all of the interfaces'
 559          * methods that can be legally thrown by all of them.
 560          **/
 561         ClassDoc[] exceptionTypes() {
 562             return exceptionTypes.clone();
 563         }
 564 
 565         /**
 566          * Returns the JRMP "method hash" used to identify this remote
 567          * method in the JDK 1.2 version of the stub protocol.
 568          **/
 569         long methodHash() {
 570             return methodHash;
 571         }
 572 
 573         /**
 574          * Returns the string representation of this method
 575          * appropriate for the construction of a
 576          * java.rmi.server.Operation object.
 577          **/
 578         String operationString() {
 579             return operationString;
 580         }
 581 
 582         /**
 583          * Returns a string consisting of this method's name followed
 584          * by its descriptor.
 585          **/
 586         String nameAndDescriptor() {
 587             return nameAndDescriptor;
 588         }
 589 
 590         /**
 591          * Returns a new Method object that is a legal combination of
 592          * this Method object and another one.
 593          *
 594          * Doing this requires determining the exceptions declared by
 595          * the combined method, which must be (only) all of the
 596          * exceptions declared in both old Methods that may thrown in
 597          * either of them.
 598          **/
 599         Method mergeWith(Method other) {
 600             if (!nameAndDescriptor().equals(other.nameAndDescriptor())) {
 601                 throw new AssertionError(
 602                     "attempt to merge method \"" +
 603                     other.nameAndDescriptor() + "\" with \"" +
 604                     nameAndDescriptor());
 605             }
 606 
 607             List<ClassDoc> legalExceptions = new ArrayList<ClassDoc>();
 608             collectCompatibleExceptions(
 609                 other.exceptionTypes, exceptionTypes, legalExceptions);
 610             collectCompatibleExceptions(
 611                 exceptionTypes, other.exceptionTypes, legalExceptions);
 612 
 613             Method merged = clone();
 614             merged.exceptionTypes =
 615                 legalExceptions.toArray(new ClassDoc[legalExceptions.size()]);
 616 
 617             return merged;
 618         }
 619 
 620         /**
 621          * Cloning is supported by returning a shallow copy of this
 622          * object.
 623          **/
 624         protected Method clone() {
 625             try {
 626                 return (Method) super.clone();
 627             } catch (CloneNotSupportedException e) {
 628                 throw new AssertionError(e);
 629             }
 630         }
 631 
 632         /**
 633          * Adds to the supplied list all exceptions in the "froms"
 634          * array that are subclasses of an exception in the "withs"
 635          * array.
 636          **/
 637         private void collectCompatibleExceptions(ClassDoc[] froms,
 638                                                  ClassDoc[] withs,
 639                                                  List<ClassDoc> list)
 640         {
 641             for (ClassDoc from : froms) {
 642                 if (!list.contains(from)) {
 643                     for (ClassDoc with : withs) {
 644                         if (from.subclassOf(with)) {
 645                             list.add(from);
 646                             break;
 647                         }
 648                     }
 649                 }
 650             }
 651         }
 652 
 653         /**
 654          * Computes the JRMP "method hash" of this remote method.  The
 655          * method hash is a long containing the first 64 bits of the
 656          * SHA digest from the UTF-8 encoded string of the method name
 657          * and descriptor.
 658          **/
 659         private long computeMethodHash() {
 660             long hash = 0;
 661             ByteArrayOutputStream sink = new ByteArrayOutputStream(512);
 662             try {
 663                 MessageDigest md = MessageDigest.getInstance("SHA");
 664                 DataOutputStream out = new DataOutputStream(
 665                     new DigestOutputStream(sink, md));
 666 
 667                 String methodString = nameAndDescriptor();
 668                 out.writeUTF(methodString);
 669 
 670                 // use only the first 64 bits of the digest for the hash
 671                 out.flush();
 672                 byte hashArray[] = md.digest();
 673                 for (int i = 0; i < Math.min(8, hashArray.length); i++) {
 674                     hash += ((long) (hashArray[i] & 0xFF)) << (i * 8);
 675                 }
 676             } catch (IOException e) {
 677                 throw new AssertionError(e);
 678             } catch (NoSuchAlgorithmException e) {
 679                 throw new AssertionError(e);
 680             }
 681 
 682             return hash;
 683         }
 684 
 685         /**
 686          * Computes the string representation of this method
 687          * appropriate for the construction of a
 688          * java.rmi.server.Operation object.
 689          **/
 690         private String computeOperationString() {
 691             /*
 692              * To be consistent with previous implementations, we use
 693              * the deprecated style of placing the "[]" for the return
 694              * type (if any) after the parameter list.
 695              */
 696             Type returnType = methodDoc.returnType();
 697             String op = returnType.qualifiedTypeName() + " " +
 698                 methodDoc.name() + "(";
 699             Parameter[] parameters = methodDoc.parameters();
 700             for (int i = 0; i < parameters.length; i++) {
 701                 if (i > 0) {
 702                     op += ", ";
 703                 }
 704                 op += parameters[i].type().toString();
 705             }
 706             op += ")" + returnType.dimension();
 707             return op;
 708         }
 709     }
 710 }