1 /*
   2  * Copyright (c) 2010, 2013, 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 package jdk.nashorn.internal.tools.nasgen;
  26 
  27 import static jdk.nashorn.internal.tools.nasgen.StringConstants.OBJECT_ARRAY_DESC;
  28 import static jdk.nashorn.internal.tools.nasgen.StringConstants.OBJECT_DESC;
  29 import static jdk.nashorn.internal.tools.nasgen.StringConstants.SCRIPTOBJECT_DESC;
  30 import static jdk.nashorn.internal.tools.nasgen.StringConstants.STRING_DESC;
  31 import jdk.internal.org.objectweb.asm.Opcodes;
  32 import jdk.internal.org.objectweb.asm.Type;
  33 import jdk.nashorn.internal.objects.annotations.Where;
  34 import jdk.nashorn.internal.runtime.ScriptObject;
  35 
  36 /**
  37  * Details about a Java method or field annotated with any of the field/method
  38  * annotations from the jdk.nashorn.internal.objects.annotations package.
  39  */
  40 public final class MemberInfo implements Cloneable {
  41     // class loader of this class
  42     private static final ClassLoader MY_LOADER = MemberInfo.class.getClassLoader();
  43 
  44     /**
  45      * The different kinds of available class annotations
  46      */
  47     public static enum Kind {
  48 
  49         /**
  50          * This is a script class
  51          */
  52         SCRIPT_CLASS,
  53         /**
  54          * This is a constructor
  55          */
  56         CONSTRUCTOR,
  57         /**
  58          * This is a function
  59          */
  60         FUNCTION,
  61         /**
  62          * This is a getter
  63          */
  64         GETTER,
  65         /**
  66          * This is a setter
  67          */
  68         SETTER,
  69         /**
  70          * This is a property
  71          */
  72         PROPERTY,
  73         /**
  74          * This is a specialized version of a function
  75          */
  76         SPECIALIZED_FUNCTION,
  77     }
  78 
  79     // keep in sync with jdk.nashorn.internal.objects.annotations.Attribute
  80     static final int DEFAULT_ATTRIBUTES = 0x0;
  81 
  82     static final int DEFAULT_ARITY = -2;
  83 
  84     // the kind of the script annotation - one of the above constants
  85     private MemberInfo.Kind kind;
  86     // script property name
  87     private String name;
  88     // documentation for this member
  89     private String documentation;
  90     // script property attributes
  91     private int attributes;
  92     // name of the java member
  93     private String javaName;
  94     // type descriptor of the java member
  95     private String javaDesc;
  96     // access bits of the Java field or method
  97     private int javaAccess;
  98     // initial value for static @Property fields
  99     private Object value;
 100     // class whose object is created to fill property value
 101     private String initClass;
 102     // arity of the Function or Constructor
 103     private int arity;
 104 
 105     private Where where;
 106 
 107     private Type linkLogicClass;
 108 
 109     private boolean isSpecializedConstructor;
 110 
 111     private boolean isOptimistic;
 112 
 113     /**
 114      * @return the kind
 115      */
 116     public Kind getKind() {
 117         return kind;
 118     }
 119 
 120     /**
 121      * @param kind the kind to set
 122      */
 123     public void setKind(final Kind kind) {
 124         this.kind = kind;
 125     }
 126 
 127     /**
 128      * @return the name
 129      */
 130     public String getName() {
 131         return name;
 132     }
 133 
 134     /**
 135      * @param name the name to set
 136      */
 137     public void setName(final String name) {
 138         this.name = name;
 139     }
 140 
 141     /**
 142      * @return the documentation
 143      */
 144     public String getDocumentation() {
 145         return documentation;
 146     }
 147 
 148     /**
 149      * @param doc the documentation to set
 150      */
 151     public void setDocumentation(final String doc) {
 152         this.documentation = doc;
 153     }
 154 
 155     /**
 156      * Tag something as specialized constructor or not
 157      * @param isSpecializedConstructor boolean, true if specialized constructor
 158      */
 159     public void setIsSpecializedConstructor(final boolean isSpecializedConstructor) {
 160         this.isSpecializedConstructor = isSpecializedConstructor;
 161     }
 162 
 163     /**
 164      * Check if something is a specialized constructor
 165      * @return true if specialized constructor
 166      */
 167     public boolean isSpecializedConstructor() {
 168         return isSpecializedConstructor;
 169     }
 170 
 171     /**
 172      * Check if this is an optimistic builtin function
 173      * @return true if optimistic builtin
 174      */
 175     public boolean isOptimistic() {
 176         return isOptimistic;
 177     }
 178 
 179     /**
 180      * Tag something as optimistic builtin or not
 181      * @param isOptimistic boolean, true if builtin constructor
 182      */
 183     public void setIsOptimistic(final boolean isOptimistic) {
 184         this.isOptimistic = isOptimistic;
 185     }
 186 
 187     /**
 188      * Get the SpecializedFunction guard for specializations, i.e. optimistic
 189      * builtins
 190      * @return specialization, null if none
 191      */
 192     public Type getLinkLogicClass() {
 193         return linkLogicClass;
 194     }
 195 
 196     /**
 197      * Set the SpecializedFunction link logic class for specializations, i.e. optimistic
 198      * builtins
 199      * @param linkLogicClass link logic class
 200      */
 201 
 202     public void setLinkLogicClass(final Type linkLogicClass) {
 203         this.linkLogicClass = linkLogicClass;
 204     }
 205 
 206     /**
 207      * @return the attributes
 208      */
 209     public int getAttributes() {
 210         return attributes;
 211     }
 212 
 213     /**
 214      * @param attributes the attributes to set
 215      */
 216     public void setAttributes(final int attributes) {
 217         this.attributes = attributes;
 218     }
 219 
 220     /**
 221      * @return the javaName
 222      */
 223     public String getJavaName() {
 224         return javaName;
 225     }
 226 
 227     /**
 228      * @param javaName the javaName to set
 229      */
 230     public void setJavaName(final String javaName) {
 231         this.javaName = javaName;
 232     }
 233 
 234     /**
 235      * @return the javaDesc
 236      */
 237     public String getJavaDesc() {
 238         return javaDesc;
 239     }
 240 
 241     void setJavaDesc(final String javaDesc) {
 242         this.javaDesc = javaDesc;
 243     }
 244 
 245     int getJavaAccess() {
 246         return javaAccess;
 247     }
 248 
 249     void setJavaAccess(final int access) {
 250         this.javaAccess = access;
 251     }
 252 
 253     Object getValue() {
 254         return value;
 255     }
 256 
 257     void setValue(final Object value) {
 258         this.value = value;
 259     }
 260 
 261     Where getWhere() {
 262         return where;
 263     }
 264 
 265     void setWhere(final Where where) {
 266         this.where = where;
 267     }
 268 
 269     boolean isFinal() {
 270         return (javaAccess & Opcodes.ACC_FINAL) != 0;
 271     }
 272 
 273     boolean isStatic() {
 274         return (javaAccess & Opcodes.ACC_STATIC) != 0;
 275     }
 276 
 277     boolean isStaticFinal() {
 278         return isStatic() && isFinal();
 279     }
 280 
 281     boolean isInstanceGetter() {
 282         return kind == Kind.GETTER && where == Where.INSTANCE;
 283     }
 284 
 285     /**
 286      * Check whether this MemberInfo is a getter that resides in the instance
 287      *
 288      * @return true if instance setter
 289      */
 290     boolean isInstanceSetter() {
 291         return kind == Kind.SETTER && where == Where.INSTANCE;
 292     }
 293 
 294     boolean isInstanceProperty() {
 295         return kind == Kind.PROPERTY && where == Where.INSTANCE;
 296     }
 297 
 298     boolean isInstanceFunction() {
 299         return kind == Kind.FUNCTION && where == Where.INSTANCE;
 300     }
 301 
 302     boolean isPrototypeGetter() {
 303         return kind == Kind.GETTER && where == Where.PROTOTYPE;
 304     }
 305 
 306     boolean isPrototypeSetter() {
 307         return kind == Kind.SETTER && where == Where.PROTOTYPE;
 308     }
 309 
 310     boolean isPrototypeProperty() {
 311         return kind == Kind.PROPERTY && where == Where.PROTOTYPE;
 312     }
 313 
 314     boolean isPrototypeFunction() {
 315         return kind == Kind.FUNCTION && where == Where.PROTOTYPE;
 316     }
 317 
 318     boolean isConstructorGetter() {
 319         return kind == Kind.GETTER && where == Where.CONSTRUCTOR;
 320     }
 321 
 322     boolean isConstructorSetter() {
 323         return kind == Kind.SETTER && where == Where.CONSTRUCTOR;
 324     }
 325 
 326     boolean isConstructorProperty() {
 327         return kind == Kind.PROPERTY && where == Where.CONSTRUCTOR;
 328     }
 329 
 330     boolean isConstructorFunction() {
 331         return kind == Kind.FUNCTION && where == Where.CONSTRUCTOR;
 332     }
 333 
 334     boolean isConstructor() {
 335         return kind == Kind.CONSTRUCTOR;
 336     }
 337 
 338     void verify() {
 339         switch (kind) {
 340             case CONSTRUCTOR: {
 341                 final Type returnType = Type.getReturnType(javaDesc);
 342                 if (!isJSObjectType(returnType)) {
 343                     error("return value of a @Constructor method should be of Object type, found " + returnType);
 344                 }
 345                 final Type[] argTypes = Type.getArgumentTypes(javaDesc);
 346                 if (argTypes.length < 2) {
 347                     error("@Constructor methods should have at least 2 args");
 348                 }
 349                 if (!argTypes[0].equals(Type.BOOLEAN_TYPE)) {
 350                     error("first argument of a @Constructor method should be of boolean type, found " + argTypes[0]);
 351                 }
 352                 if (!isJavaLangObject(argTypes[1])) {
 353                     error("second argument of a @Constructor method should be of Object type, found " + argTypes[0]);
 354                 }
 355 
 356                 if (argTypes.length > 2) {
 357                     for (int i = 2; i < argTypes.length - 1; i++) {
 358                         if (!isJavaLangObject(argTypes[i])) {
 359                             error(i + "'th argument of a @Constructor method should be of Object type, found " + argTypes[i]);
 360                         }
 361                     }
 362 
 363                     final String lastArgTypeDesc = argTypes[argTypes.length - 1].getDescriptor();
 364                     final boolean isVarArg = lastArgTypeDesc.equals(OBJECT_ARRAY_DESC);
 365                     if (!lastArgTypeDesc.equals(OBJECT_DESC) && !isVarArg) {
 366                         error("last argument of a @Constructor method is neither Object nor Object[] type: " + lastArgTypeDesc);
 367                     }
 368 
 369                     if (isVarArg && argTypes.length > 3) {
 370                         error("vararg of a @Constructor method has more than 3 arguments");
 371                     }
 372                 }
 373             }
 374             break;
 375             case FUNCTION: {
 376                 final Type returnType = Type.getReturnType(javaDesc);
 377                 if (!(isValidJSType(returnType) || Type.VOID_TYPE == returnType)) {
 378                     error("return value of a @Function method should be a valid JS type, found " + returnType);
 379                 }
 380                 final Type[] argTypes = Type.getArgumentTypes(javaDesc);
 381                 if (argTypes.length < 1) {
 382                     error("@Function methods should have at least 1 arg");
 383                 }
 384                 if (!isJavaLangObject(argTypes[0])) {
 385                     error("first argument of a @Function method should be of Object type, found " + argTypes[0]);
 386                 }
 387 
 388                 if (argTypes.length > 1) {
 389                     for (int i = 1; i < argTypes.length - 1; i++) {
 390                         if (!isJavaLangObject(argTypes[i])) {
 391                             error(i + "'th argument of a @Function method should be of Object type, found " + argTypes[i]);
 392                         }
 393                     }
 394 
 395                     final String lastArgTypeDesc = argTypes[argTypes.length - 1].getDescriptor();
 396                     final boolean isVarArg = lastArgTypeDesc.equals(OBJECT_ARRAY_DESC);
 397                     if (!lastArgTypeDesc.equals(OBJECT_DESC) && !isVarArg) {
 398                         error("last argument of a @Function method is neither Object nor Object[] type: " + lastArgTypeDesc);
 399                     }
 400 
 401                     if (isVarArg && argTypes.length > 2) {
 402                         error("vararg @Function method has more than 2 arguments");
 403                     }
 404                 }
 405             }
 406             break;
 407             case SPECIALIZED_FUNCTION: {
 408                 final Type returnType = Type.getReturnType(javaDesc);
 409                 if (!(isValidJSType(returnType) || (isSpecializedConstructor() && Type.VOID_TYPE == returnType))) {
 410                     error("return value of a @SpecializedFunction method should be a valid JS type, found " + returnType);
 411                 }
 412                 final Type[] argTypes = Type.getArgumentTypes(javaDesc);
 413                 for (int i = 0; i < argTypes.length; i++) {
 414                     if (!isValidJSType(argTypes[i])) {
 415                         error(i + "'th argument of a @SpecializedFunction method is not valid JS type, found " + argTypes[i]);
 416                     }
 417                 }
 418             }
 419             break;
 420             case GETTER: {
 421                 final Type[] argTypes = Type.getArgumentTypes(javaDesc);
 422                 if (argTypes.length != 1) {
 423                     error("@Getter methods should have one argument");
 424                 }
 425                 if (!isJavaLangObject(argTypes[0])) {
 426                     error("first argument of a @Getter method should be of Object type, found: " + argTypes[0]);
 427                 }
 428 
 429                 if (Type.getReturnType(javaDesc).equals(Type.VOID_TYPE)) {
 430                     error("return type of getter should not be void");
 431                 }
 432             }
 433             break;
 434             case SETTER: {
 435                 final Type[] argTypes = Type.getArgumentTypes(javaDesc);
 436                 if (argTypes.length != 2) {
 437                     error("@Setter methods should have two arguments");
 438                 }
 439                 if (!isJavaLangObject(argTypes[0])) {
 440                     error("first argument of a @Setter method should be of Object type, found: " + argTypes[0]);
 441                 }
 442                 if (!Type.getReturnType(javaDesc).toString().equals("V")) {
 443                     error("return type of of a @Setter method should be void, found: " + Type.getReturnType(javaDesc));
 444                 }
 445             }
 446             break;
 447             case PROPERTY: {
 448                 if (where == Where.CONSTRUCTOR) {
 449                     if (isStatic()) {
 450                         if (!isFinal()) {
 451                             error("static Where.CONSTRUCTOR @Property should be final");
 452                         }
 453 
 454                         if (!isJSPrimitiveType(Type.getType(javaDesc))) {
 455                             error("static Where.CONSTRUCTOR @Property should be a JS primitive");
 456                         }
 457                     }
 458                 } else if (where == Where.PROTOTYPE) {
 459                     if (isStatic()) {
 460                         if (!isFinal()) {
 461                             error("static Where.PROTOTYPE @Property should be final");
 462                         }
 463 
 464                         if (!isJSPrimitiveType(Type.getType(javaDesc))) {
 465                             error("static Where.PROTOTYPE @Property should be a JS primitive");
 466                         }
 467                     }
 468                 }
 469             }
 470             break;
 471 
 472             default:
 473             break;
 474         }
 475     }
 476 
 477     private static boolean isValidJSType(final Type type) {
 478         return isJSPrimitiveType(type) || isJSObjectType(type);
 479     }
 480 
 481     private static boolean isJSPrimitiveType(final Type type) {
 482         switch (type.getSort()) {
 483             case Type.BOOLEAN:
 484             case Type.INT:
 485             case Type.LONG:
 486             case Type.DOUBLE:
 487                 return true;
 488             default:
 489                 return false;
 490         }
 491     }
 492 
 493     private static boolean isJSObjectType(final Type type) {
 494         return isJavaLangObject(type) || isJavaLangString(type) || isScriptObject(type);
 495     }
 496 
 497     private static boolean isJavaLangObject(final Type type) {
 498         return type.getDescriptor().equals(OBJECT_DESC);
 499     }
 500 
 501     private static boolean isJavaLangString(final Type type) {
 502         return type.getDescriptor().equals(STRING_DESC);
 503     }
 504 
 505     private static boolean isScriptObject(final Type type) {
 506         if (type.getDescriptor().equals(SCRIPTOBJECT_DESC)) {
 507             return true;
 508         }
 509 
 510         if (type.getSort() == Type.OBJECT) {
 511             try {
 512                 final Class<?> clazz = Class.forName(type.getClassName(), false, MY_LOADER);
 513                 return ScriptObject.class.isAssignableFrom(clazz);
 514             } catch (final ClassNotFoundException cnfe) {
 515                 return false;
 516             }
 517         }
 518 
 519         return false;
 520     }
 521 
 522     private void error(final String msg) {
 523         throw new RuntimeException(javaName + " of type " + javaDesc + " : " + msg);
 524     }
 525 
 526     /**
 527      * @return the initClass
 528      */
 529     String getInitClass() {
 530         return initClass;
 531     }
 532 
 533     /**
 534      * @param initClass the initClass to set
 535      */
 536     void setInitClass(final String initClass) {
 537         this.initClass = initClass;
 538     }
 539 
 540     @Override
 541     protected Object clone() {
 542         try {
 543             return super.clone();
 544         } catch (final CloneNotSupportedException e) {
 545             assert false : "clone not supported " + e;
 546             return null;
 547         }
 548     }
 549 
 550     /**
 551      * @return the arity
 552      */
 553     int getArity() {
 554         return arity;
 555     }
 556 
 557     /**
 558      * @param arity the arity to set
 559      */
 560     void setArity(final int arity) {
 561         this.arity = arity;
 562     }
 563 }