Don't copy the HTML/Java synthetic fields - they will be generated at method visitEnd time. Eliminates class format errors after incremental compilations.
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
6 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7 * Other names may be trademarks of their respective owners.
9 * The contents of this file are subject to the terms of either the GNU
10 * General Public License Version 2 only ("GPL") or the Common
11 * Development and Distribution License("CDDL") (collectively, the
12 * "License"). You may not use this file except in compliance with the
13 * License. You can obtain a copy of the License at
14 * http://www.netbeans.org/cddl-gplv2.html
15 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16 * specific language governing permissions and limitations under the
17 * License. When distributing the software, include this License Header
18 * Notice in each file and include the License file at
19 * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
20 * particular file as subject to the "Classpath" exception as provided
21 * by Oracle in the GPL Version 2 section of the License file that
22 * accompanied this code. If applicable, add the following below the
23 * License Header, with the fields enclosed by brackets [] replaced by
24 * your own identifying information:
25 * "Portions Copyrighted [year] [name of copyright owner]"
29 * The Original Software is NetBeans. The Initial Developer of the Original
30 * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
32 * If you wish your version of this file to be governed by only the CDDL
33 * or only the GPL Version 2, indicate your decision by adding
34 * "[Contributor] elects to include this software in this distribution
35 * under the [CDDL or GPL Version 2] license." If you do not indicate a
36 * single choice of license, a recipient has the option to distribute
37 * your version of this file under either the CDDL, the GPL Version 2 or
38 * to extend the choice of license to its licensees as provided above.
39 * However, if you add GPL Version 2 code and therefore, elected the GPL
40 * Version 2 license, then the option applies only if the new code is
41 * made subject to such option by the copyright holder.
43 package org.netbeans.html.boot.impl;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.io.InputStreamReader;
48 import java.io.Reader;
50 import java.util.ArrayList;
51 import java.util.Collections;
52 import java.util.Enumeration;
53 import java.util.List;
54 import net.java.html.js.JavaScriptBody;
55 import net.java.html.js.JavaScriptResource;
56 import org.netbeans.html.boot.spi.Fn;
57 import org.objectweb.asm.AnnotationVisitor;
58 import org.objectweb.asm.ClassReader;
59 import org.objectweb.asm.ClassVisitor;
60 import org.objectweb.asm.ClassWriter;
61 import org.objectweb.asm.FieldVisitor;
62 import org.objectweb.asm.Label;
63 import org.objectweb.asm.MethodVisitor;
64 import org.objectweb.asm.Opcodes;
65 import org.objectweb.asm.Type;
66 import org.objectweb.asm.signature.SignatureReader;
67 import org.objectweb.asm.signature.SignatureVisitor;
68 import org.objectweb.asm.signature.SignatureWriter;
70 /** Utilities related to bytecode transformations. Depend on asm.jar which
71 * needs to be added to be provided to classpath to make methods in this
74 * @author Jaroslav Tulach
76 public final class FnUtils {
81 /** Seeks for {@link JavaScriptBody} and {@link JavaScriptResource} annotations
82 * in the bytecode and converts them into real code. Used by Maven plugin
83 * postprocessing classes.
85 * @param bytecode the original bytecode with javascript specific annotations
86 * @param loader the loader to load resources (scripts and classes) when needed
87 * @return the transformed bytecode
90 public static byte[] transform(byte[] bytecode, ClassLoader loader) {
91 ClassReader cr = new ClassReader(bytecode) {
92 // to allow us to compile with -profile compact1 on
93 // JDK8 while processing the class as JDK7, the highest
94 // class format asm 4.1 understands to
96 public short readShort(int index) {
97 short s = super.readShort(index);
98 if (index == 6 && s > Opcodes.V1_7) {
104 FindInClass tst = new FindInClass(loader, null);
107 ClassWriter w = new ClassWriterEx(loader, cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
108 FindInClass fic = new FindInClass(loader, w);
110 bytecode = w.toByteArray();
115 public static ClassLoader newLoader(final FindResources f, final Fn.Presenter d, ClassLoader parent) {
116 return new JsClassLoaderImpl(parent, f, d);
119 static String callback(final String body) {
120 return new JsCallback() {
122 protected CharSequence callMethod(
123 String ident, String fqn, String method, String params
125 StringBuilder sb = new StringBuilder();
127 sb.append("vm.raw$");
131 sb.append(mangle(fqn, method, params));
142 private static final class FindInClass extends ClassVisitor {
145 private String resource;
147 public FindInClass(ClassLoader l, ClassVisitor cv) {
148 super(Opcodes.ASM4, cv);
152 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
154 super.visit(version, access, name, signature, superName, interfaces);
158 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
159 final AnnotationVisitor del = super.visitAnnotation(desc, visible);
160 if ("Lnet/java/html/js/JavaScriptResource;".equals(desc)) {
161 return new LoadResource(del);
167 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
168 return new FindInMethod(access, name, desc,
169 super.visitMethod(access & (~Opcodes.ACC_NATIVE), name, desc, signature, exceptions)
174 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
175 if (name.startsWith("$$fn$$")) {
178 return superField(access, name, desc, signature, value);
181 final FieldVisitor superField(int access, String name, String desc, String signature, Object value) {
182 return super.visitField(access, name, desc, signature, value);
185 private final class FindInMethod extends MethodVisitor {
187 private final String name;
188 private final String desc;
189 private final int access;
190 private FindInAnno fia;
191 private boolean bodyGenerated;
193 public FindInMethod(int access, String name, String desc, MethodVisitor mv) {
194 super(Opcodes.ASM4, mv);
195 this.access = access;
201 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
202 if ("Lnet/java/html/js/JavaScriptBody;".equals(desc)) { // NOI18N
204 return new FindInAnno();
206 return super.visitAnnotation(desc, visible);
209 private void generateJSBody(FindInAnno fia) {
214 public void visitCode() {
221 private boolean generateBody(boolean hasCode) {
225 bodyGenerated = true;
227 AnnotationVisitor va = super.visitAnnotation("Lnet/java/html/js/JavaScriptBody;", false);
228 AnnotationVisitor varr = va.visitArray("args");
229 for (String argName : fia.args) {
230 varr.visit(null, argName);
233 va.visit("javacall", fia.javacall);
234 va.visit("body", fia.body);
241 body = callback(fia.body);
242 args = new ArrayList<String>(fia.args);
249 super.visitFieldInsn(
250 Opcodes.GETSTATIC, FindInClass.this.name,
251 "$$fn$$" + name + "_" + found,
252 "Lorg/netbeans/html/boot/spi/Fn;"
254 super.visitInsn(Opcodes.DUP);
255 super.visitMethodInsn(
256 Opcodes.INVOKESTATIC,
257 "org/netbeans/html/boot/spi/Fn", "isValid",
258 "(Lorg/netbeans/html/boot/spi/Fn;)Z"
260 Label ifNotNull = new Label();
261 super.visitJumpInsn(Opcodes.IFNE, ifNotNull);
264 super.visitInsn(Opcodes.POP);
265 super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
266 super.visitInsn(fia.keepAlive ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
267 super.visitLdcInsn(body);
268 super.visitIntInsn(Opcodes.SIPUSH, args.size());
269 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
270 boolean needsVM = false;
271 for (int i = 0; i < args.size(); i++) {
273 String argName = args.get(i);
274 needsVM = "vm".equals(argName);
275 super.visitInsn(Opcodes.DUP);
276 super.visitIntInsn(Opcodes.BIPUSH, i);
277 super.visitLdcInsn(argName);
278 super.visitInsn(Opcodes.AASTORE);
280 super.visitMethodInsn(Opcodes.INVOKESTATIC,
281 "org/netbeans/html/boot/spi/Fn", "define",
282 "(Ljava/lang/Class;ZLjava/lang/String;[Ljava/lang/String;)Lorg/netbeans/html/boot/spi/Fn;"
284 Label noPresenter = new Label();
285 super.visitInsn(Opcodes.DUP);
286 super.visitJumpInsn(Opcodes.IFNULL, noPresenter);
287 if (resource != null) {
288 super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
289 super.visitLdcInsn(resource);
290 super.visitMethodInsn(Opcodes.INVOKESTATIC,
291 "org/netbeans/html/boot/spi/Fn", "preload",
292 "(Lorg/netbeans/html/boot/spi/Fn;Ljava/lang/Class;Ljava/lang/String;)Lorg/netbeans/html/boot/spi/Fn;"
295 super.visitInsn(Opcodes.DUP);
296 super.visitFieldInsn(
297 Opcodes.PUTSTATIC, FindInClass.this.name,
298 "$$fn$$" + name + "_" + found,
299 "Lorg/netbeans/html/boot/spi/Fn;"
303 super.visitLabel(ifNotNull);
306 if ((access & Opcodes.ACC_STATIC) == 0) {
308 super.visitIntInsn(Opcodes.ALOAD, 0);
311 super.visitInsn(Opcodes.ACONST_NULL);
314 super.visitIntInsn(Opcodes.SIPUSH, args.size());
315 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
317 class SV extends SignatureVisitor {
319 private boolean nowReturn;
320 private Type returnType;
322 private int loadIndex = offset;
329 public void visitBaseType(char descriptor) {
330 final Type t = Type.getType("" + descriptor);
335 FindInMethod.super.visitInsn(Opcodes.DUP);
336 FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
337 FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), loadIndex++);
339 switch (descriptor) {
341 factory = "java/lang/Integer";
344 factory = "java/lang/Long";
348 factory = "java/lang/Short";
351 factory = "java/lang/Float";
354 factory = "java/lang/Double";
358 factory = "java/lang/Boolean";
361 factory = "java/lang/Character";
364 factory = "java/lang/Byte";
367 throw new IllegalStateException(t.toString());
369 FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
370 factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
372 FindInMethod.super.visitInsn(Opcodes.AASTORE);
376 public SignatureVisitor visitArrayType() {
378 return new SignatureVisitor(Opcodes.ASM4) {
380 public void visitClassType(String name) {
381 returnType = Type.getType("[" + Type.getObjectType(name).getDescriptor());
386 return new SignatureWriter();
390 public void visitClassType(String name) {
392 returnType = Type.getObjectType(name);
399 public SignatureVisitor visitReturnType() {
404 private void loadObject() {
405 FindInMethod.super.visitInsn(Opcodes.DUP);
406 FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
407 FindInMethod.super.visitVarInsn(Opcodes.ALOAD, loadIndex++);
408 FindInMethod.super.visitInsn(Opcodes.AASTORE);
413 SignatureReader sr = new SignatureReader(desc);
417 FindInMethod.super.visitInsn(Opcodes.DUP);
418 FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, sv.index);
419 int lastSlash = FindInClass.this.name.lastIndexOf('/');
420 String jsCallbacks = FindInClass.this.name.substring(0, lastSlash + 1) + "$JsCallbacks$";
421 FindInMethod.super.visitFieldInsn(Opcodes.GETSTATIC, jsCallbacks, "VM", "L" + jsCallbacks + ";");
422 FindInMethod.super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, jsCallbacks, "current", "()L" + jsCallbacks + ";");
423 FindInMethod.super.visitInsn(Opcodes.AASTORE);
427 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
428 "org/netbeans/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
430 switch (sv.returnType.getSort()) {
432 super.visitInsn(Opcodes.RETURN);
436 super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
437 super.visitInsn(Opcodes.ARETURN);
440 super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
441 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
442 "java/lang/Boolean", "booleanValue", "()Z"
444 super.visitInsn(Opcodes.IRETURN);
447 super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
448 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
449 "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
451 super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
454 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
455 "org/netbeans/html/boot/spi/Fn", "invokeLater", "(Ljava/lang/Object;[Ljava/lang/Object;)V"
457 super.visitInsn(Opcodes.RETURN);
459 super.visitLabel(noPresenter);
463 super.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException");
464 super.visitInsn(Opcodes.DUP);
465 super.visitLdcInsn("No presenter active. Use BrwsrCtx.execute!");
466 super.visitMethodInsn(Opcodes.INVOKESPECIAL,
467 "java/lang/IllegalStateException", "<init>", "(Ljava/lang/String;)V"
469 this.visitInsn(Opcodes.ATHROW);
475 public void visitEnd() {
478 if (generateBody(false)) {
480 super.visitMaxs(1, 0);
482 FindInClass.this.superField(
483 Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
484 "$$fn$$" + name + "_" + found,
485 "Lorg/netbeans/html/boot/spi/Fn;",
491 private final class FindInAnno extends AnnotationVisitor {
493 List<String> args = new ArrayList<String>();
495 boolean javacall = false;
496 boolean wait4js = true;
497 boolean keepAlive = true;
499 public FindInAnno() {
504 public void visit(String name, Object value) {
506 args.add((String) value);
509 if (name.equals("javacall")) { // NOI18N
510 javacall = (Boolean) value;
513 if (name.equals("wait4js")) { // NOI18N
514 wait4js = (Boolean) value;
517 if (name.equals("keepAlive")) { // NOI18N
518 keepAlive = (Boolean) value;
521 assert name.equals("body"); // NOI18N
522 body = (String) value;
526 public AnnotationVisitor visitArray(String name) {
531 public void visitEnd() {
533 generateJSBody(this);
539 private final class LoadResource extends AnnotationVisitor {
540 public LoadResource(AnnotationVisitor av) {
541 super(Opcodes.ASM4, av);
545 public void visit(String attrName, Object value) {
546 super.visit(attrName, value);
547 String relPath = (String) value;
548 if (relPath.startsWith("/")) {
551 int last = name.lastIndexOf('/');
552 String fullPath = name.substring(0, last + 1) + relPath;
559 private static class ClassWriterEx extends ClassWriter {
561 private final ClassLoader loader;
563 public ClassWriterEx(ClassLoader l, ClassReader classReader, int flags) {
564 super(classReader, flags);
569 protected String getCommonSuperClass(final String type1, final String type2) {
572 c = Class.forName(type1.replace('/', '.'), false, loader);
573 d = Class.forName(type2.replace('/', '.'), false, loader);
574 } catch (Exception e) {
575 throw new RuntimeException(e.toString());
577 if (c.isAssignableFrom(d)) {
580 if (d.isAssignableFrom(c)) {
583 if (c.isInterface() || d.isInterface()) {
584 return "java/lang/Object";
587 c = c.getSuperclass();
588 } while (!c.isAssignableFrom(d));
589 return c.getName().replace('.', '/');
594 static class JsClassLoaderImpl extends JsClassLoader {
596 private final FindResources f;
597 private final Fn.Presenter d;
599 public JsClassLoaderImpl(ClassLoader parent, FindResources f, Fn.Presenter d) {
601 setDefaultAssertionStatus(JsClassLoader.class.desiredAssertionStatus());
607 protected URL findResource(String name) {
608 List<URL> l = res(name, true);
609 return l.isEmpty() ? null : l.get(0);
613 protected Enumeration<URL> findResources(String name) {
614 return Collections.enumeration(res(name, false));
617 private List<URL> res(String name, boolean oneIsEnough) {
618 List<URL> l = new ArrayList<URL>();
619 f.findResources(name, l, oneIsEnough);
624 protected Class<?> findClass(String name) throws ClassNotFoundException {
625 if (name.startsWith("javafx")) {
626 return Class.forName(name);
628 if (name.startsWith("netscape")) {
629 return Class.forName(name);
631 if (name.startsWith("com.sun")) {
632 return Class.forName(name);
634 if (name.startsWith("org.netbeans.html.context.spi")) {
635 return Class.forName(name);
637 if (name.startsWith("net.java.html.BrwsrCtx")) {
638 return Class.forName(name);
640 if (name.equals(JsClassLoader.class.getName())) {
641 return JsClassLoader.class;
643 if (name.equals(Fn.class.getName())) {
646 if (name.equals(Fn.Presenter.class.getName())) {
647 return Fn.Presenter.class;
649 if (name.equals(Fn.ToJavaScript.class.getName())) {
650 return Fn.ToJavaScript.class;
652 if (name.equals(Fn.FromJavaScript.class.getName())) {
653 return Fn.FromJavaScript.class;
655 if (name.equals(FnUtils.class.getName())) {
656 return FnUtils.class;
659 name.equals("org.netbeans.html.boot.spi.Fn") ||
660 name.equals("org.netbeans.html.boot.impl.FnUtils") ||
661 name.equals("org.netbeans.html.boot.impl.FnContext")
663 return Class.forName(name);
665 URL u = findResource(name.replace('.', '/') + ".class");
667 InputStream is = null;
670 byte[] arr = new byte[is.available()];
672 while (len < arr.length) {
673 int read = is.read(arr, len, arr.length - len);
675 throw new IOException("Can't read " + u);
681 if (JsPkgCache.process(this, name)) {
682 arr = FnUtils.transform(arr, this);
684 return defineClass(name, arr, 0, arr.length);
685 } catch (IOException ex) {
686 throw new ClassNotFoundException("Can't load " + name, ex);
689 if (is != null) is.close();
690 } catch (IOException ex) {
691 throw new ClassNotFoundException(null, ex);
695 return super.findClass(name);
698 protected Fn defineFn(String code, String... names) {
699 return d.defineFn(code, names);
702 protected void loadScript(Reader code) throws Exception {