ScriptClassInfoCollector.java revision 973:d564abed1e6a
1139823Simp/*
21541Srgrimes * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3166841Srwatson * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4166841Srwatson *
51541Srgrimes * This code is free software; you can redistribute it and/or modify it
61541Srgrimes * under the terms of the GNU General Public License version 2 only, as
71541Srgrimes * published by the Free Software Foundation.  Oracle designates this
81541Srgrimes * particular file as subject to the "Classpath" exception as provided
91541Srgrimes * by Oracle in the LICENSE file that accompanied this code.
101541Srgrimes *
111541Srgrimes * This code is distributed in the hope that it will be useful, but WITHOUT
121541Srgrimes * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
131541Srgrimes * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
141541Srgrimes * version 2 for more details (a copy is included in the LICENSE file that
151541Srgrimes * accompanied this code).
161541Srgrimes *
171541Srgrimes * You should have received a copy of the GNU General Public License version
181541Srgrimes * 2 along with this work; if not, write to the Free Software Foundation,
191541Srgrimes * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
201541Srgrimes *
211541Srgrimes * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
221541Srgrimes * or visit www.oracle.com if you need additional information or have any
231541Srgrimes * questions.
241541Srgrimes */
251541Srgrimes
261541Srgrimespackage jdk.nashorn.internal.tools.nasgen;
271541Srgrimes
281541Srgrimesimport static jdk.nashorn.internal.tools.nasgen.ScriptClassInfo.SCRIPT_CLASS_ANNO_DESC;
291541Srgrimesimport static jdk.nashorn.internal.tools.nasgen.ScriptClassInfo.WHERE_ENUM_DESC;
301541Srgrimes
3150477Speterimport java.io.BufferedInputStream;
321541Srgrimesimport java.io.FileInputStream;
331541Srgrimesimport java.io.IOException;
342169Spaulimport java.io.PrintStream;
35166841Srwatsonimport java.util.ArrayList;
362169Spaulimport java.util.Collections;
371541Srgrimesimport java.util.List;
381541Srgrimesimport jdk.internal.org.objectweb.asm.AnnotationVisitor;
391541Srgrimesimport jdk.internal.org.objectweb.asm.ClassReader;
40166841Srwatsonimport jdk.internal.org.objectweb.asm.ClassVisitor;
41166841Srwatsonimport jdk.internal.org.objectweb.asm.FieldVisitor;
42166841Srwatsonimport jdk.internal.org.objectweb.asm.MethodVisitor;
431541Srgrimesimport jdk.internal.org.objectweb.asm.Opcodes;
441541Srgrimesimport jdk.nashorn.internal.objects.annotations.Where;
451541Srgrimesimport jdk.nashorn.internal.tools.nasgen.MemberInfo.Kind;
461541Srgrimes
471541Srgrimes/**
481541Srgrimes * This class collects all @ScriptClass and other annotation information from a
491541Srgrimes * compiled .class file. Enforces that @Function/@Getter/@Setter/@Constructor
501541Srgrimes * methods are declared to be 'static'.
511541Srgrimes */
521541Srgrimespublic class ScriptClassInfoCollector extends ClassVisitor {
531541Srgrimes    private String scriptClassName;
54166841Srwatson    private List<MemberInfo> scriptMembers;
551541Srgrimes    private String javaClassName;
561541Srgrimes
571541Srgrimes    ScriptClassInfoCollector(final ClassVisitor visitor) {
581541Srgrimes        super(Main.ASM_VERSION, visitor);
5952904Sshin    }
601541Srgrimes
611541Srgrimes    ScriptClassInfoCollector() {
621541Srgrimes        this(new NullVisitor());
631541Srgrimes    }
641541Srgrimes
6516143Swollman    private void addScriptMember(final MemberInfo memInfo) {
661541Srgrimes        if (scriptMembers == null) {
671541Srgrimes            scriptMembers = new ArrayList<>();
6828270Swollman        }
6952904Sshin        scriptMembers.add(memInfo);
7052904Sshin    }
711541Srgrimes
721541Srgrimes    @Override
731541Srgrimes    public void visit(final int version, final int access, final String name, final String signature,
74166841Srwatson           final String superName, final String[] interfaces) {
751541Srgrimes        super.visit(version, access, name, signature, superName, interfaces);
761541Srgrimes        javaClassName = name;
77166841Srwatson    }
786472Swollman
796472Swollman    @Override
8036079Swollman    public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
81166841Srwatson        final AnnotationVisitor delegateAV = super.visitAnnotation(desc, visible);
821541Srgrimes        if (SCRIPT_CLASS_ANNO_DESC.equals(desc)) {
83166841Srwatson            return new AnnotationVisitor(Main.ASM_VERSION, delegateAV) {
84166841Srwatson                @Override
85166841Srwatson                public void visit(final String name, final Object value) {
86166841Srwatson                    if ("value".equals(name)) {
87166841Srwatson                        scriptClassName = (String) value;
88166841Srwatson                    }
89166841Srwatson                    super.visit(name, value);
901541Srgrimes                }
911541Srgrimes            };
9255205Speter        }
9344078Sdfr
9444078Sdfr        return delegateAV;
95166841Srwatson    }
96166841Srwatson
97166841Srwatson    @Override
98166841Srwatson    public FieldVisitor visitField(final int fieldAccess, final String fieldName, final String fieldDesc, final String signature, final Object value) {
99166841Srwatson        final FieldVisitor delegateFV = super.visitField(fieldAccess, fieldName, fieldDesc, signature, value);
100166841Srwatson
101166842Srwatson        return new FieldVisitor(Main.ASM_VERSION, delegateFV) {
1021541Srgrimes            @Override
103166841Srwatson            public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
104166841Srwatson                final AnnotationVisitor delegateAV = super.visitAnnotation(descriptor, visible);
105166841Srwatson
106166841Srwatson                if (ScriptClassInfo.PROPERTY_ANNO_DESC.equals(descriptor)) {
107166841Srwatson                    final MemberInfo memInfo = new MemberInfo();
1081541Srgrimes
1092169Spaul                    memInfo.setKind(Kind.PROPERTY);
1102169Spaul                    memInfo.setJavaName(fieldName);
111                    memInfo.setJavaDesc(fieldDesc);
112                    memInfo.setJavaAccess(fieldAccess);
113
114                    if ((fieldAccess & Opcodes.ACC_STATIC) != 0) {
115                        memInfo.setValue(value);
116                    }
117
118                    addScriptMember(memInfo);
119
120                    return new AnnotationVisitor(Main.ASM_VERSION, delegateAV) {
121                        // These could be "null" if values are not suppiled,
122                        // in which case we have to use the default values.
123                        private String  name;
124                        private Integer attributes;
125                        private String  clazz = "";
126                        private Where   where;
127
128                        @Override
129                        public void visit(final String annotationName, final Object annotationValue) {
130                            switch (annotationName) {
131                            case "name":
132                                this.name = (String) annotationValue;
133                                break;
134                            case "attributes":
135                                this.attributes = (Integer) annotationValue;
136                                break;
137                            case "clazz":
138                                this.clazz = (annotationValue == null) ? "" : annotationValue.toString();
139                                break;
140                            default:
141                                break;
142                            }
143                            super.visit(annotationName, annotationValue);
144                        }
145
146                        @Override
147                        public void visitEnum(final String enumName, final String desc, final String enumValue) {
148                            if ("where".equals(enumName) && WHERE_ENUM_DESC.equals(desc)) {
149                                this.where = Where.valueOf(enumValue);
150                            }
151                            super.visitEnum(enumName, desc, enumValue);
152                        }
153
154                        @Override
155                        public void visitEnd() {
156                            super.visitEnd();
157                            memInfo.setName(name == null ? fieldName : name);
158                            memInfo.setAttributes(attributes == null
159                                    ? MemberInfo.DEFAULT_ATTRIBUTES : attributes);
160                            clazz = clazz.replace('.', '/');
161                            memInfo.setInitClass(clazz);
162                            memInfo.setWhere(where == null? Where.INSTANCE : where);
163                        }
164                    };
165                }
166
167                return delegateAV;
168            }
169        };
170    }
171
172    private void error(final String javaName, final String javaDesc, final String msg) {
173        throw new RuntimeException(scriptClassName + "." + javaName + javaDesc + " : " + msg);
174    }
175
176    @Override
177    public MethodVisitor visitMethod(final int methodAccess, final String methodName,
178            final String methodDesc, final String signature, final String[] exceptions) {
179
180        final MethodVisitor delegateMV = super.visitMethod(methodAccess, methodName, methodDesc,
181                signature, exceptions);
182
183        return new MethodVisitor(Main.ASM_VERSION, delegateMV) {
184
185            @Override
186            public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
187                final AnnotationVisitor delegateAV = super.visitAnnotation(descriptor, visible);
188                final Kind annoKind = ScriptClassInfo.annotations.get(descriptor);
189
190                if (annoKind != null) {
191                    if ((methodAccess & Opcodes.ACC_STATIC) == 0) {
192                        error(methodName, methodDesc, "nasgen method annotations cannot be on instance methods");
193                    }
194
195                    final MemberInfo memInfo = new MemberInfo();
196
197                    memInfo.setKind(annoKind);
198                    memInfo.setJavaName(methodName);
199                    memInfo.setJavaDesc(methodDesc);
200                    memInfo.setJavaAccess(methodAccess);
201
202                    addScriptMember(memInfo);
203
204                    return new AnnotationVisitor(Main.ASM_VERSION, delegateAV) {
205                        // These could be "null" if values are not suppiled,
206                        // in which case we have to use the default values.
207                        private String  name;
208                        private Integer attributes;
209                        private Integer arity;
210                        private Where   where;
211
212                        @Override
213                        public void visit(final String annotationName, final Object annotationValue) {
214                            switch (annotationName) {
215                            case "name":
216                                this.name = (String)annotationValue;
217                                break;
218                            case "attributes":
219                                this.attributes = (Integer)annotationValue;
220                                break;
221                            case "arity":
222                                this.arity = (Integer)annotationValue;
223                                break;
224                            default:
225                                break;
226                            }
227
228                            super.visit(annotationName, annotationValue);
229                        }
230
231                        @Override
232                        public void visitEnum(final String enumName, final String desc, final String enumValue) {
233                            if ("where".equals(enumName) && WHERE_ENUM_DESC.equals(desc)) {
234                                this.where = Where.valueOf(enumValue);
235                            }
236                            super.visitEnum(enumName, desc, enumValue);
237                        }
238
239                        @Override
240                        public void visitEnd() {
241                            super.visitEnd();
242
243                            if (memInfo.getKind() == Kind.CONSTRUCTOR) {
244                                memInfo.setName(name == null ? scriptClassName : name);
245                            } else {
246                                memInfo.setName(name == null ? methodName : name);
247                            }
248                            memInfo.setAttributes(attributes == null ? MemberInfo.DEFAULT_ATTRIBUTES : attributes);
249
250                            memInfo.setArity((arity == null)? MemberInfo.DEFAULT_ARITY : arity);
251                            if (where == null) {
252                                // by default @Getter/@Setter belongs to INSTANCE
253                                // @Function belong to PROTOTYPE.
254                                switch (memInfo.getKind()) {
255                                    case GETTER:
256                                    case SETTER:
257                                        where = Where.INSTANCE;
258                                        break;
259                                    case SPECIALIZED_CONSTRUCTOR:
260                                    case CONSTRUCTOR:
261                                        where = Where.CONSTRUCTOR;
262                                        break;
263                                    case FUNCTION:
264                                        where = Where.PROTOTYPE;
265                                        break;
266                                    case SPECIALIZED_FUNCTION:
267                                        //TODO is this correct
268                                    default:
269                                        break;
270                                }
271                            }
272                            memInfo.setWhere(where);
273                        }
274                    };
275                }
276
277                return delegateAV;
278            }
279        };
280    }
281
282    ScriptClassInfo getScriptClassInfo() {
283        ScriptClassInfo sci = null;
284        if (scriptClassName != null) {
285            sci = new ScriptClassInfo();
286            sci.setName(scriptClassName);
287            if (scriptMembers == null) {
288                scriptMembers = Collections.emptyList();
289            }
290            sci.setMembers(scriptMembers);
291            sci.setJavaName(javaClassName);
292        }
293        return sci;
294    }
295
296    /**
297     * External entry point for ScriptClassInfoCollector if invoked from the command line
298     * @param args argument vector, args contains a class for which to collect info
299     * @throws IOException if there were problems parsing args or class
300     */
301    public static void main(final String[] args) throws IOException {
302        if (args.length != 1) {
303            System.err.println("Usage: " + ScriptClassInfoCollector.class.getName() + " <class>");
304            System.exit(1);
305        }
306
307        args[0] = args[0].replace('.', '/');
308        final ScriptClassInfoCollector scic = new ScriptClassInfoCollector();
309        try (final BufferedInputStream bis = new BufferedInputStream(new FileInputStream(args[0] + ".class"))) {
310            final ClassReader reader = new ClassReader(bis);
311            reader.accept(scic, 0);
312        }
313        final ScriptClassInfo sci = scic.getScriptClassInfo();
314        final PrintStream out = System.out;
315        if (sci != null) {
316            out.println("script class: " + sci.getName());
317            out.println("===================================");
318            for (final MemberInfo memInfo : sci.getMembers()) {
319                out.println("kind : " + memInfo.getKind());
320                out.println("name : " + memInfo.getName());
321                out.println("attributes: " + memInfo.getAttributes());
322                out.println("javaName: " + memInfo.getJavaName());
323                out.println("javaDesc: " + memInfo.getJavaDesc());
324                out.println("where: " + memInfo.getWhere());
325                out.println("=====================================");
326            }
327        } else {
328            out.println(args[0] + " is not a @ScriptClass");
329        }
330    }
331}
332