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