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