MemberInfo.java revision 1626:d99fa86747ee
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.SCRIPTOBJECT_DESC;
30import static jdk.nashorn.internal.tools.nasgen.StringConstants.STRING_DESC;
31import static jdk.nashorn.internal.tools.nasgen.StringConstants.TYPE_SYMBOL;
32
33import jdk.internal.org.objectweb.asm.Opcodes;
34import jdk.internal.org.objectweb.asm.Type;
35import jdk.nashorn.internal.objects.annotations.Where;
36import jdk.nashorn.internal.runtime.ScriptObject;
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    /**
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     * Tag something as specialized constructor or not
143     * @param isSpecializedConstructor boolean, true if specialized constructor
144     */
145    public void setIsSpecializedConstructor(final boolean isSpecializedConstructor) {
146        this.isSpecializedConstructor = isSpecializedConstructor;
147    }
148
149    /**
150     * Check if something is a specialized constructor
151     * @return true if specialized constructor
152     */
153    public boolean isSpecializedConstructor() {
154        return isSpecializedConstructor;
155    }
156
157    /**
158     * Check if this is an optimistic builtin function
159     * @return true if optimistic builtin
160     */
161    public boolean isOptimistic() {
162        return isOptimistic;
163    }
164
165    /**
166     * Tag something as optimistic builtin or not
167     * @param isOptimistic boolean, true if builtin constructor
168     */
169    public void setIsOptimistic(final boolean isOptimistic) {
170        this.isOptimistic = isOptimistic;
171    }
172
173    /**
174     * Get the SpecializedFunction guard for specializations, i.e. optimistic
175     * builtins
176     * @return specialization, null if none
177     */
178    public Type getLinkLogicClass() {
179        return linkLogicClass;
180    }
181
182    /**
183     * Set the SpecializedFunction link logic class for specializations, i.e. optimistic
184     * builtins
185     * @param linkLogicClass link logic class
186     */
187
188    public void setLinkLogicClass(final Type linkLogicClass) {
189        this.linkLogicClass = linkLogicClass;
190    }
191
192    /**
193     * @return the attributes
194     */
195    public int getAttributes() {
196        return attributes;
197    }
198
199    /**
200     * @param attributes the attributes to set
201     */
202    public void setAttributes(final int attributes) {
203        this.attributes = attributes;
204    }
205
206    /**
207     * @return the javaName
208     */
209    public String getJavaName() {
210        return javaName;
211    }
212
213    /**
214     * @param javaName the javaName to set
215     */
216    public void setJavaName(final String javaName) {
217        this.javaName = javaName;
218    }
219
220    /**
221     * @return the javaDesc
222     */
223    public String getJavaDesc() {
224        return javaDesc;
225    }
226
227    void setJavaDesc(final String javaDesc) {
228        this.javaDesc = javaDesc;
229    }
230
231    int getJavaAccess() {
232        return javaAccess;
233    }
234
235    void setJavaAccess(final int access) {
236        this.javaAccess = access;
237    }
238
239    Object getValue() {
240        return value;
241    }
242
243    void setValue(final Object value) {
244        this.value = value;
245    }
246
247    Where getWhere() {
248        return where;
249    }
250
251    void setWhere(final Where where) {
252        this.where = where;
253    }
254
255    boolean isFinal() {
256        return (javaAccess & Opcodes.ACC_FINAL) != 0;
257    }
258
259    boolean isStatic() {
260        return (javaAccess & Opcodes.ACC_STATIC) != 0;
261    }
262
263    boolean isStaticFinal() {
264        return isStatic() && isFinal();
265    }
266
267    boolean isInstanceGetter() {
268        return kind == Kind.GETTER && where == Where.INSTANCE;
269    }
270
271    /**
272     * Check whether this MemberInfo is a getter that resides in the instance
273     *
274     * @return true if instance setter
275     */
276    boolean isInstanceSetter() {
277        return kind == Kind.SETTER && where == Where.INSTANCE;
278    }
279
280    boolean isInstanceProperty() {
281        return kind == Kind.PROPERTY && where == Where.INSTANCE;
282    }
283
284    boolean isInstanceFunction() {
285        return kind == Kind.FUNCTION && where == Where.INSTANCE;
286    }
287
288    boolean isPrototypeGetter() {
289        return kind == Kind.GETTER && where == Where.PROTOTYPE;
290    }
291
292    boolean isPrototypeSetter() {
293        return kind == Kind.SETTER && where == Where.PROTOTYPE;
294    }
295
296    boolean isPrototypeProperty() {
297        return kind == Kind.PROPERTY && where == Where.PROTOTYPE;
298    }
299
300    boolean isPrototypeFunction() {
301        return kind == Kind.FUNCTION && where == Where.PROTOTYPE;
302    }
303
304    boolean isConstructorGetter() {
305        return kind == Kind.GETTER && where == Where.CONSTRUCTOR;
306    }
307
308    boolean isConstructorSetter() {
309        return kind == Kind.SETTER && where == Where.CONSTRUCTOR;
310    }
311
312    boolean isConstructorProperty() {
313        return kind == Kind.PROPERTY && where == Where.CONSTRUCTOR;
314    }
315
316    boolean isConstructorFunction() {
317        return kind == Kind.FUNCTION && where == Where.CONSTRUCTOR;
318    }
319
320    boolean isConstructor() {
321        return kind == Kind.CONSTRUCTOR;
322    }
323
324    void verify() {
325        switch (kind) {
326            case CONSTRUCTOR: {
327                final Type returnType = Type.getReturnType(javaDesc);
328                if (!isJSObjectType(returnType)) {
329                    error("return value of a @Constructor method should be of Object type, found " + returnType);
330                }
331                final Type[] argTypes = Type.getArgumentTypes(javaDesc);
332                if (argTypes.length < 2) {
333                    error("@Constructor methods should have at least 2 args");
334                }
335                if (!argTypes[0].equals(Type.BOOLEAN_TYPE)) {
336                    error("first argument of a @Constructor method should be of boolean type, found " + argTypes[0]);
337                }
338                if (!isJavaLangObject(argTypes[1])) {
339                    error("second argument of a @Constructor method should be of Object type, found " + argTypes[0]);
340                }
341
342                if (argTypes.length > 2) {
343                    for (int i = 2; i < argTypes.length - 1; i++) {
344                        if (!isJavaLangObject(argTypes[i])) {
345                            error(i + "'th argument of a @Constructor method should be of Object type, found " + argTypes[i]);
346                        }
347                    }
348
349                    final String lastArgTypeDesc = argTypes[argTypes.length - 1].getDescriptor();
350                    final boolean isVarArg = lastArgTypeDesc.equals(OBJECT_ARRAY_DESC);
351                    if (!lastArgTypeDesc.equals(OBJECT_DESC) && !isVarArg) {
352                        error("last argument of a @Constructor method is neither Object nor Object[] type: " + lastArgTypeDesc);
353                    }
354
355                    if (isVarArg && argTypes.length > 3) {
356                        error("vararg of a @Constructor method has more than 3 arguments");
357                    }
358                }
359            }
360            break;
361            case FUNCTION: {
362                final Type returnType = Type.getReturnType(javaDesc);
363                if (!(isValidJSType(returnType) || Type.VOID_TYPE == returnType)) {
364                    error("return value of a @Function method should be a valid JS type, found " + returnType);
365                }
366                final Type[] argTypes = Type.getArgumentTypes(javaDesc);
367                if (argTypes.length < 1) {
368                    error("@Function methods should have at least 1 arg");
369                }
370                if (!isJavaLangObject(argTypes[0])) {
371                    error("first argument of a @Function method should be of Object type, found " + argTypes[0]);
372                }
373
374                if (argTypes.length > 1) {
375                    for (int i = 1; i < argTypes.length - 1; i++) {
376                        if (!isJavaLangObject(argTypes[i])) {
377                            error(i + "'th argument of a @Function method should be of Object type, found " + argTypes[i]);
378                        }
379                    }
380
381                    final String lastArgTypeDesc = argTypes[argTypes.length - 1].getDescriptor();
382                    final boolean isVarArg = lastArgTypeDesc.equals(OBJECT_ARRAY_DESC);
383                    if (!lastArgTypeDesc.equals(OBJECT_DESC) && !isVarArg) {
384                        error("last argument of a @Function method is neither Object nor Object[] type: " + lastArgTypeDesc);
385                    }
386
387                    if (isVarArg && argTypes.length > 2) {
388                        error("vararg @Function method has more than 2 arguments");
389                    }
390                }
391            }
392            break;
393            case SPECIALIZED_FUNCTION: {
394                final Type returnType = Type.getReturnType(javaDesc);
395                if (!(isValidJSType(returnType) || (isSpecializedConstructor() && Type.VOID_TYPE == returnType))) {
396                    error("return value of a @SpecializedFunction method should be a valid JS type, found " + returnType);
397                }
398                final Type[] argTypes = Type.getArgumentTypes(javaDesc);
399                for (int i = 0; i < argTypes.length; i++) {
400                    if (!isValidJSType(argTypes[i])) {
401                        error(i + "'th argument of a @SpecializedFunction method is not valid JS type, found " + argTypes[i]);
402                    }
403                }
404            }
405            break;
406            case GETTER: {
407                final Type[] argTypes = Type.getArgumentTypes(javaDesc);
408                if (argTypes.length != 1) {
409                    error("@Getter methods should have one argument");
410                }
411                if (!isJavaLangObject(argTypes[0])) {
412                    error("first argument of a @Getter method should be of Object type, found: " + argTypes[0]);
413                }
414
415                if (Type.getReturnType(javaDesc).equals(Type.VOID_TYPE)) {
416                    error("return type of getter should not be void");
417                }
418            }
419            break;
420            case SETTER: {
421                final Type[] argTypes = Type.getArgumentTypes(javaDesc);
422                if (argTypes.length != 2) {
423                    error("@Setter methods should have two arguments");
424                }
425                if (!isJavaLangObject(argTypes[0])) {
426                    error("first argument of a @Setter method should be of Object type, found: " + argTypes[0]);
427                }
428                if (!Type.getReturnType(javaDesc).toString().equals("V")) {
429                    error("return type of of a @Setter method should be void, found: " + Type.getReturnType(javaDesc));
430                }
431            }
432            break;
433            case PROPERTY: {
434                if (where == Where.CONSTRUCTOR) {
435                    if (isStatic()) {
436                        if (!isFinal()) {
437                            error("static Where.CONSTRUCTOR @Property should be final");
438                        }
439
440                        if (!isJSPrimitiveType(Type.getType(javaDesc))) {
441                            error("static Where.CONSTRUCTOR @Property should be a JS primitive");
442                        }
443                    }
444                } else if (where == Where.PROTOTYPE) {
445                    if (isStatic()) {
446                        if (!isFinal()) {
447                            error("static Where.PROTOTYPE @Property should be final");
448                        }
449
450                        if (!isJSPrimitiveType(Type.getType(javaDesc))) {
451                            error("static Where.PROTOTYPE @Property should be a JS primitive");
452                        }
453                    }
454                }
455            }
456            break;
457
458            default:
459            break;
460        }
461    }
462
463    private static boolean isValidJSType(final Type type) {
464        return isJSPrimitiveType(type) || isJSObjectType(type);
465    }
466
467    private static boolean isJSPrimitiveType(final Type type) {
468        switch (type.getSort()) {
469            case Type.BOOLEAN:
470            case Type.INT:
471            case Type.DOUBLE:
472                return true;
473            default:
474                return type != TYPE_SYMBOL;
475        }
476    }
477
478    private static boolean isJSObjectType(final Type type) {
479        return isJavaLangObject(type) || isJavaLangString(type) || isScriptObject(type);
480    }
481
482    private static boolean isJavaLangObject(final Type type) {
483        return type.getDescriptor().equals(OBJECT_DESC);
484    }
485
486    private static boolean isJavaLangString(final Type type) {
487        return type.getDescriptor().equals(STRING_DESC);
488    }
489
490    private static boolean isScriptObject(final Type type) {
491        if (type.getDescriptor().equals(SCRIPTOBJECT_DESC)) {
492            return true;
493        }
494
495        if (type.getSort() == Type.OBJECT) {
496            try {
497                final Class<?> clazz = Class.forName(type.getClassName(), false, MY_LOADER);
498                return ScriptObject.class.isAssignableFrom(clazz);
499            } catch (final ClassNotFoundException cnfe) {
500                return false;
501            }
502        }
503
504        return false;
505    }
506
507    private void error(final String msg) {
508        throw new RuntimeException(javaName + " of type " + javaDesc + " : " + msg);
509    }
510
511    /**
512     * @return the initClass
513     */
514    String getInitClass() {
515        return initClass;
516    }
517
518    /**
519     * @param initClass the initClass to set
520     */
521    void setInitClass(final String initClass) {
522        this.initClass = initClass;
523    }
524
525    @Override
526    protected Object clone() {
527        try {
528            return super.clone();
529        } catch (final CloneNotSupportedException e) {
530            assert false : "clone not supported " + e;
531            return null;
532        }
533    }
534
535    /**
536     * @return the arity
537     */
538    int getArity() {
539        return arity;
540    }
541
542    /**
543     * @param arity the arity to set
544     */
545    void setArity(final int arity) {
546        this.arity = arity;
547    }
548
549    String getDocumentationKey(final String objName) {
550        if (kind == Kind.FUNCTION) {
551            StringBuilder buf = new StringBuilder(objName);
552            switch (where) {
553                case CONSTRUCTOR:
554                    break;
555                case PROTOTYPE:
556                    buf.append(".prototype");
557                    break;
558                case INSTANCE:
559                    buf.append(".this");
560                    break;
561            }
562            buf.append('.');
563            buf.append(name);
564            return buf.toString();
565        }
566
567        return null;
568    }
569}
570