http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java ---------------------------------------------------------------------- diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java b/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java new file mode 100644 index 0000000..7abae31 --- /dev/null +++ b/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java @@ -0,0 +1,717 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.boot.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import net.java.html.js.JavaScriptBody; +import net.java.html.js.JavaScriptResource; +import org.netbeans.html.boot.spi.Fn; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; +import org.objectweb.asm.signature.SignatureWriter; + +/** Utilities related to bytecode transformations. Depend on asm.jar which + * needs to be added to be provided to classpath to make methods in this + * class useful. + * + * @author Jaroslav Tulach + */ +public final class FnUtils { + + private FnUtils() { + } + + /** Seeks for {@link JavaScriptBody} and {@link JavaScriptResource} annotations + * in the bytecode and converts them into real code. Used by Maven plugin + * postprocessing classes. + * + * @param bytecode the original bytecode with javascript specific annotations + * @param loader the loader to load resources (scripts and classes) when needed + * @return the transformed bytecode + * @since 0.7 + */ + public static byte[] transform(byte[] bytecode, ClassLoader loader) { + ClassReader cr = new ClassReader(bytecode) { + // to allow us to compile with -profile compact1 on + // JDK8 while processing the class as JDK7, the highest + // class format asm 4.1 understands to + @Override + public short readShort(int index) { + short s = super.readShort(index); + if (index == 6 && s > Opcodes.V1_7) { + return Opcodes.V1_7; + } + return s; + } + }; + FindInClass tst = new FindInClass(loader, null); + cr.accept(tst, 0); + if (tst.found > 0) { + ClassWriter w = new ClassWriterEx(loader, cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + FindInClass fic = new FindInClass(loader, w); + cr.accept(fic, 0); + bytecode = w.toByteArray(); + } + return bytecode; + } + + public static ClassLoader newLoader(final FindResources f, final Fn.Presenter d, ClassLoader parent) { + return new JsClassLoaderImpl(parent, f, d); + } + + static String callback(final String body) { + return new JsCallback() { + @Override + protected CharSequence callMethod( + String ident, String fqn, String method, String params + ) { + StringBuilder sb = new StringBuilder(); + if (ident != null) { + sb.append("vm.raw$"); + } else { + sb.append("vm."); + } + sb.append(mangle(fqn, method, params)); + sb.append("("); + if (ident != null) { + sb.append(ident); + } + return sb; + } + + }.parse(body); + } + + private static final class FindInClass extends ClassVisitor { + private String name; + private int found; + private String resource; + + public FindInClass(ClassLoader l, ClassVisitor cv) { + super(Opcodes.ASM4, cv); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + this.name = name; + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + final AnnotationVisitor del = super.visitAnnotation(desc, visible); + if ("Lnet/java/html/js/JavaScriptResource;".equals(desc)) { + return new LoadResource(del); + } + return del; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + return new FindInMethod(access, name, desc, + super.visitMethod(access & (~Opcodes.ACC_NATIVE), name, desc, signature, exceptions) + ); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + if (name.startsWith("$$fn$$")) { + return null; + } + return superField(access, name, desc, signature, value); + } + + final FieldVisitor superField(int access, String name, String desc, String signature, Object value) { + return super.visitField(access, name, desc, signature, value); + } + + private final class FindInMethod extends MethodVisitor { + + private final String name; + private final String desc; + private final int access; + private FindInAnno fia; + private boolean bodyGenerated; + + public FindInMethod(int access, String name, String desc, MethodVisitor mv) { + super(Opcodes.ASM4, mv); + this.access = access; + this.name = name; + this.desc = desc; + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if ("Lnet/java/html/js/JavaScriptBody;".equals(desc)) { // NOI18N + found++; + return new FindInAnno(); + } + return super.visitAnnotation(desc, visible); + } + + private void generateJSBody(FindInAnno fia) { + this.fia = fia; + } + + @Override + public void visitCode() { + if (fia == null) { + return; + } + generateBody(true); + } + + private boolean generateBody(boolean hasCode) { + if (bodyGenerated) { + return false; + } + bodyGenerated = true; + if (mv != null) { + AnnotationVisitor va = super.visitAnnotation("Lnet/java/html/js/JavaScriptBody;", false); + AnnotationVisitor varr = va.visitArray("args"); + for (String argName : fia.args) { + varr.visit(null, argName); + } + varr.visitEnd(); + va.visit("javacall", fia.javacall); + va.visit("body", fia.body); + va.visitEnd(); + } + + String body; + List<String> args; + if (fia.javacall) { + body = callback(fia.body); + args = new ArrayList<String>(fia.args); + args.add("vm"); + } else { + body = fia.body; + args = fia.args; + } + + super.visitFieldInsn( + Opcodes.GETSTATIC, FindInClass.this.name, + "$$fn$$" + name + "_" + found, + "Lorg/netbeans/html/boot/spi/Fn;" + ); + super.visitInsn(Opcodes.DUP); + super.visitMethodInsn( + Opcodes.INVOKESTATIC, + "org/netbeans/html/boot/spi/Fn", "isValid", + "(Lorg/netbeans/html/boot/spi/Fn;)Z" + ); + Label ifNotNull = new Label(); + super.visitJumpInsn(Opcodes.IFNE, ifNotNull); + + // init Fn + super.visitInsn(Opcodes.POP); + super.visitLdcInsn(Type.getObjectType(FindInClass.this.name)); + super.visitInsn(fia.keepAlive ? Opcodes.ICONST_1 : Opcodes.ICONST_0); + super.visitLdcInsn(body); + super.visitIntInsn(Opcodes.SIPUSH, args.size()); + super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String"); + boolean needsVM = false; + for (int i = 0; i < args.size(); i++) { + assert !needsVM; + String argName = args.get(i); + needsVM = "vm".equals(argName); + super.visitInsn(Opcodes.DUP); + super.visitIntInsn(Opcodes.BIPUSH, i); + super.visitLdcInsn(argName); + super.visitInsn(Opcodes.AASTORE); + } + super.visitMethodInsn(Opcodes.INVOKESTATIC, + "org/netbeans/html/boot/spi/Fn", "define", + "(Ljava/lang/Class;ZLjava/lang/String;[Ljava/lang/String;)Lorg/netbeans/html/boot/spi/Fn;" + ); + Label noPresenter = new Label(); + super.visitInsn(Opcodes.DUP); + super.visitJumpInsn(Opcodes.IFNULL, noPresenter); + if (resource != null) { + super.visitLdcInsn(Type.getObjectType(FindInClass.this.name)); + super.visitLdcInsn(resource); + super.visitMethodInsn(Opcodes.INVOKESTATIC, + "org/netbeans/html/boot/spi/Fn", "preload", + "(Lorg/netbeans/html/boot/spi/Fn;Ljava/lang/Class;Ljava/lang/String;)Lorg/netbeans/html/boot/spi/Fn;" + ); + } + super.visitInsn(Opcodes.DUP); + super.visitFieldInsn( + Opcodes.PUTSTATIC, FindInClass.this.name, + "$$fn$$" + name + "_" + found, + "Lorg/netbeans/html/boot/spi/Fn;" + ); + // end of Fn init + + super.visitLabel(ifNotNull); + + final int offset; + if ((access & Opcodes.ACC_STATIC) == 0) { + offset = 1; + super.visitIntInsn(Opcodes.ALOAD, 0); + } else { + offset = 0; + super.visitInsn(Opcodes.ACONST_NULL); + } + + super.visitIntInsn(Opcodes.SIPUSH, args.size()); + super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + + class SV extends SignatureVisitor { + + private boolean nowReturn; + private Type returnType; + private int index; + private int loadIndex = offset; + + public SV() { + super(Opcodes.ASM4); + } + + @Override + public void visitBaseType(char descriptor) { + final Type t = Type.getType("" + descriptor); + if (nowReturn) { + returnType = t; + return; + } + FindInMethod.super.visitInsn(Opcodes.DUP); + FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++); + FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), loadIndex++); + String factory; + switch (descriptor) { + case 'I': + factory = "java/lang/Integer"; + break; + case 'J': + factory = "java/lang/Long"; + loadIndex++; + break; + case 'S': + factory = "java/lang/Short"; + break; + case 'F': + factory = "java/lang/Float"; + break; + case 'D': + factory = "java/lang/Double"; + loadIndex++; + break; + case 'Z': + factory = "java/lang/Boolean"; + break; + case 'C': + factory = "java/lang/Character"; + break; + case 'B': + factory = "java/lang/Byte"; + break; + default: + throw new IllegalStateException(t.toString()); + } + FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC, + factory, "valueOf", "(" + descriptor + ")L" + factory + ";" + ); + FindInMethod.super.visitInsn(Opcodes.AASTORE); + } + + @Override + public SignatureVisitor visitArrayType() { + if (nowReturn) { + return new SignatureVisitor(Opcodes.ASM4) { + @Override + public void visitClassType(String name) { + returnType = Type.getType("[" + Type.getObjectType(name).getDescriptor()); + } + + @Override + public void visitBaseType(char descriptor) { + returnType = Type.getType("[" + descriptor); + } + }; + } + loadObject(); + return new SignatureWriter(); + } + + @Override + public void visitClassType(String name) { + if (nowReturn) { + returnType = Type.getObjectType(name); + return; + } + loadObject(); + } + + @Override + public SignatureVisitor visitReturnType() { + nowReturn = true; + return this; + } + + private void loadObject() { + FindInMethod.super.visitInsn(Opcodes.DUP); + FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++); + FindInMethod.super.visitVarInsn(Opcodes.ALOAD, loadIndex++); + FindInMethod.super.visitInsn(Opcodes.AASTORE); + } + + } + SV sv = new SV(); + SignatureReader sr = new SignatureReader(desc); + sr.accept(sv); + + if (needsVM) { + FindInMethod.super.visitInsn(Opcodes.DUP); + FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, sv.index); + int lastSlash = FindInClass.this.name.lastIndexOf('/'); + String jsCallbacks = FindInClass.this.name.substring(0, lastSlash + 1) + "$JsCallbacks$"; + FindInMethod.super.visitFieldInsn(Opcodes.GETSTATIC, jsCallbacks, "VM", "L" + jsCallbacks + ";"); + FindInMethod.super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, jsCallbacks, "current", "()L" + jsCallbacks + ";"); + FindInMethod.super.visitInsn(Opcodes.AASTORE); + } + + if (fia.wait4js) { + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "org/netbeans/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;" + ); + switch (sv.returnType.getSort()) { + case Type.VOID: + super.visitInsn(Opcodes.RETURN); + break; + case Type.ARRAY: + case Type.OBJECT: + super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName()); + super.visitInsn(Opcodes.ARETURN); + break; + case Type.BOOLEAN: { + Label handleNullValue = new Label(); + super.visitInsn(Opcodes.DUP); + super.visitJumpInsn(Opcodes.IFNULL, handleNullValue); + super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean"); + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "java/lang/Boolean", "booleanValue", "()Z" + ); + super.visitInsn(Opcodes.IRETURN); + super.visitLabel(handleNullValue); + super.visitInsn(Opcodes.ICONST_0); + super.visitInsn(Opcodes.IRETURN); + break; + } + default: + super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number"); + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor() + ); + super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN)); + } + } else { + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "org/netbeans/html/boot/spi/Fn", "invokeLater", "(Ljava/lang/Object;[Ljava/lang/Object;)V" + ); + super.visitInsn(Opcodes.RETURN); + } + super.visitLabel(noPresenter); + if (hasCode) { + super.visitCode(); + } else { + super.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException"); + super.visitInsn(Opcodes.DUP); + super.visitLdcInsn("No presenter active. Use BrwsrCtx.execute!"); + super.visitMethodInsn(Opcodes.INVOKESPECIAL, + "java/lang/IllegalStateException", "<init>", "(Ljava/lang/String;)V" + ); + this.visitInsn(Opcodes.ATHROW); + } + return true; + } + + @Override + public void visitEnd() { + super.visitEnd(); + if (fia != null) { + if (generateBody(false)) { + // native method + super.visitMaxs(1, 0); + } + FindInClass.this.superField( + Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, + "$$fn$$" + name + "_" + found, + "Lorg/netbeans/html/boot/spi/Fn;", + null, null + ); + } + } + + private final class FindInAnno extends AnnotationVisitor { + + List<String> args = new ArrayList<String>(); + String body; + boolean javacall = false; + boolean wait4js = true; + boolean keepAlive = true; + + public FindInAnno() { + super(Opcodes.ASM4); + } + + @Override + public void visit(String name, Object value) { + if (name == null) { + args.add((String) value); + return; + } + if (name.equals("javacall")) { // NOI18N + javacall = (Boolean) value; + return; + } + if (name.equals("wait4js")) { // NOI18N + wait4js = (Boolean) value; + return; + } + if (name.equals("keepAlive")) { // NOI18N + keepAlive = (Boolean) value; + return; + } + assert name.equals("body"); // NOI18N + body = (String) value; + } + + @Override + public AnnotationVisitor visitArray(String name) { + return this; + } + + @Override + public void visitEnd() { + if (body != null) { + generateJSBody(this); + } + } + } + } + + private final class LoadResource extends AnnotationVisitor { + public LoadResource(AnnotationVisitor av) { + super(Opcodes.ASM4, av); + } + + @Override + public void visit(String attrName, Object value) { + super.visit(attrName, value); + String relPath = (String) value; + if (relPath.startsWith("/")) { + resource = relPath; + } else { + int last = name.lastIndexOf('/'); + String fullPath = name.substring(0, last + 1) + relPath; + resource = fullPath; + } + } + } + } + + private static class ClassWriterEx extends ClassWriter { + + private final ClassLoader loader; + + public ClassWriterEx(ClassLoader l, ClassReader classReader, int flags) { + super(classReader, flags); + this.loader = l; + } + + @Override + protected String getCommonSuperClass(final String type1, final String type2) { + Class<?> c, d; + try { + c = Class.forName(type1.replace('/', '.'), false, loader); + d = Class.forName(type2.replace('/', '.'), false, loader); + } catch (Exception e) { + throw new RuntimeException(e.toString()); + } + if (c.isAssignableFrom(d)) { + return type1; + } + if (d.isAssignableFrom(c)) { + return type2; + } + if (c.isInterface() || d.isInterface()) { + return "java/lang/Object"; + } else { + do { + c = c.getSuperclass(); + } while (!c.isAssignableFrom(d)); + return c.getName().replace('.', '/'); + } + } + } + + static class JsClassLoaderImpl extends JsClassLoader { + + private final FindResources f; + private final Fn.Presenter d; + + public JsClassLoaderImpl(ClassLoader parent, FindResources f, Fn.Presenter d) { + super(parent); + setDefaultAssertionStatus(JsClassLoader.class.desiredAssertionStatus()); + this.f = f; + this.d = d; + } + + @Override + protected URL findResource(String name) { + List<URL> l = res(name, true); + return l.isEmpty() ? null : l.get(0); + } + + @Override + protected Enumeration<URL> findResources(String name) { + return Collections.enumeration(res(name, false)); + } + + private List<URL> res(String name, boolean oneIsEnough) { + List<URL> l = new ArrayList<URL>(); + f.findResources(name, l, oneIsEnough); + return l; + } + + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + if (name.startsWith("javafx")) { + return Class.forName(name); + } + if (name.startsWith("netscape")) { + return Class.forName(name); + } + if (name.startsWith("com.sun")) { + return Class.forName(name); + } + if (name.startsWith("org.netbeans.html.context.spi")) { + return Class.forName(name); + } + if (name.startsWith("net.java.html.BrwsrCtx")) { + return Class.forName(name); + } + if (name.equals(JsClassLoader.class.getName())) { + return JsClassLoader.class; + } + if (name.equals(Fn.class.getName())) { + return Fn.class; + } + if (name.equals(Fn.Presenter.class.getName())) { + return Fn.Presenter.class; + } + if (name.equals(Fn.ToJavaScript.class.getName())) { + return Fn.ToJavaScript.class; + } + if (name.equals(Fn.FromJavaScript.class.getName())) { + return Fn.FromJavaScript.class; + } + if (name.equals(FnUtils.class.getName())) { + return FnUtils.class; + } + if ( + name.equals("org.netbeans.html.boot.spi.Fn") || + name.equals("org.netbeans.html.boot.impl.FnUtils") || + name.equals("org.netbeans.html.boot.impl.FnContext") + ) { + return Class.forName(name); + } + URL u = findResource(name.replace('.', '/') + ".class"); + if (u != null) { + InputStream is = null; + try { + is = u.openStream(); + byte[] arr = new byte[is.available()]; + int len = 0; + while (len < arr.length) { + int read = is.read(arr, len, arr.length - len); + if (read == -1) { + throw new IOException("Can't read " + u); + } + len += read; + } + is.close(); + is = null; + if (JsPkgCache.process(this, name)) { + arr = FnUtils.transform(arr, this); + } + return defineClass(name, arr, 0, arr.length); + } catch (IOException ex) { + throw new ClassNotFoundException("Can't load " + name, ex); + } finally { + try { + if (is != null) is.close(); + } catch (IOException ex) { + throw new ClassNotFoundException(null, ex); + } + } + } + return super.findClass(name); + } + + protected Fn defineFn(String code, String... names) { + return d.defineFn(code, names); + } + + protected void loadScript(Reader code) throws Exception { + d.loadScript(code); + } + } +}
http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/main/java/org/netbeans/html/boot/impl/JavaScriptProcesor.java ---------------------------------------------------------------------- diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/JavaScriptProcesor.java b/boot/src/main/java/org/netbeans/html/boot/impl/JavaScriptProcesor.java new file mode 100644 index 0000000..ce9c4bb --- /dev/null +++ b/boot/src/main/java/org/netbeans/html/boot/impl/JavaScriptProcesor.java @@ -0,0 +1,528 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.boot.impl; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Completion; +import javax.annotation.processing.Completions; +import javax.annotation.processing.Messager; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; +import javax.tools.FileObject; +import javax.tools.StandardLocation; +import net.java.html.js.JavaScriptBody; +import net.java.html.js.JavaScriptResource; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author Jaroslav Tulach + */ +@ServiceProvider(service = Processor.class) +public final class JavaScriptProcesor extends AbstractProcessor { + private final Map<String,Map<String,ExecutableElement>> javacalls = + new HashMap<String,Map<String,ExecutableElement>>(); + private final Map<String,Set<TypeElement>> bodies = + new HashMap<String, Set<TypeElement>>(); + + @Override + public Set<String> getSupportedAnnotationTypes() { + Set<String> set = new HashSet<String>(); + set.add(JavaScriptBody.class.getName()); + set.add(JavaScriptResource.class.getName()); + return set; + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + final Messager msg = processingEnv.getMessager(); + for (Element e : roundEnv.getElementsAnnotatedWith(JavaScriptBody.class)) { + if (e.getKind() != ElementKind.METHOD && e.getKind() != ElementKind.CONSTRUCTOR) { + continue; + } + ExecutableElement ee = (ExecutableElement)e; + List<? extends VariableElement> params = ee.getParameters(); + + JavaScriptBody jsb = e.getAnnotation(JavaScriptBody.class); + if (jsb == null) { + continue; + } else { + Set<TypeElement> classes = this.bodies.get(findPkg(e)); + if (classes == null) { + classes = new HashSet<TypeElement>(); + bodies.put(findPkg(e), classes); + } + Element t = e.getEnclosingElement(); + while (!t.getKind().isClass() && !t.getKind().isInterface()) { + t = t.getEnclosingElement(); + } + classes.add((TypeElement)t); + } + String[] arr = jsb.args(); + if (params.size() != arr.length) { + msg.printMessage(Diagnostic.Kind.ERROR, "Number of args arguments does not match real arguments!", e); + } + for (int i = 0; i < arr.length; i++) { + if (!params.get(i).getSimpleName().toString().equals(arr[i])) { + msg.printMessage(Diagnostic.Kind.WARNING, "Actual method parameter names and args ones " + Arrays.toString(arr) + " differ", e); + } + } + if (!jsb.wait4js() && ee.getReturnType().getKind() != TypeKind.VOID) { + msg.printMessage(Diagnostic.Kind.ERROR, "Methods that don't wait for JavaScript to finish must return void!", e); + } + if (!jsb.javacall() && jsb.body().contains(".@")) { + msg.printMessage(Diagnostic.Kind.WARNING, "Usage of .@ usually requires javacall=true", e); + } + if (jsb.javacall()) { + JsCallback verify = new VerifyCallback(e); + try { + verify.parse(jsb.body()); + } catch (IllegalStateException ex) { + msg.printMessage(Diagnostic.Kind.ERROR, ex.getLocalizedMessage(), e); + } + } + } + for (Element e : roundEnv.getElementsAnnotatedWith(JavaScriptResource.class)) { + JavaScriptResource r = e.getAnnotation(JavaScriptResource.class); + if (r == null) { + continue; + } + final String res; + if (r.value().startsWith("/")) { + res = r.value().substring(1); + } else { + res = findPkg(e).replace('.', '/') + "/" + r.value(); + } + + try { + FileObject os = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", res); + os.openInputStream().close(); + } catch (IOException ex1) { + try { + FileObject os2 = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", res); + os2.openInputStream().close(); + } catch (IOException ex2) { + try { + FileObject os3 = processingEnv.getFiler().getResource(StandardLocation.CLASS_PATH, "", res); + os3.openInputStream().close(); + } catch (IOException ex3) { + msg.printMessage(Diagnostic.Kind.ERROR, "Cannot find resource " + res, e); + } + } + } + + boolean found = false; + for (Element mthod : e.getEnclosedElements()) { + if (mthod.getKind() != ElementKind.METHOD) { + continue; + } + if (mthod.getAnnotation(JavaScriptBody.class) != null) { + found = true; + break; + } + } + if (!found) { + msg.printMessage(Diagnostic.Kind.ERROR, "At least one method needs @JavaScriptBody annotation. " + + "Otherwise it is not guaranteed the resource will ever be loaded,", e + ); + } + } + + if (roundEnv.processingOver()) { + generateCallbackClass(javacalls); + generateJavaScriptBodyList(bodies); + javacalls.clear(); + } + return true; + } + + @Override + public Iterable<? extends Completion> getCompletions(Element e, + AnnotationMirror annotation, ExecutableElement member, String userText + ) { + StringBuilder sb = new StringBuilder(); + if (e.getKind() == ElementKind.METHOD && member.getSimpleName().contentEquals("args")) { + ExecutableElement ee = (ExecutableElement) e; + String sep = ""; + sb.append("{ "); + for (VariableElement ve : ee.getParameters()) { + sb.append(sep).append('"').append(ve.getSimpleName()) + .append('"'); + sep = ", "; + } + sb.append(" }"); + return Collections.nCopies(1, Completions.of(sb.toString())); + } + return null; + } + + private class VerifyCallback extends JsCallback { + private final Element e; + public VerifyCallback(Element e) { + this.e = e; + } + + @Override + protected CharSequence callMethod(String ident, String fqn, String method, String params) { + final TypeElement type = processingEnv.getElementUtils().getTypeElement(fqn); + if (type == null) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "Callback to non-existing class " + fqn, e + ); + return ""; + } + ExecutableElement found = null; + StringBuilder foundParams = new StringBuilder(); + for (Element m : type.getEnclosedElements()) { + if (m.getKind() != ElementKind.METHOD) { + continue; + } + if (m.getSimpleName().contentEquals(method)) { + String paramTypes = findParamTypes((ExecutableElement)m); + if (paramTypes.equals(params)) { + found = (ExecutableElement) m; + break; + } + foundParams.append(paramTypes).append("\n"); + } + } + if (found == null) { + if (foundParams.length() == 0) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "Callback to class " + fqn + " with unknown method " + method, e + ); + } else { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "Callback to " + fqn + "." + method + " with wrong parameters: " + + params + ". Only known parameters are " + foundParams, e + ); + } + } else { + Map<String,ExecutableElement> mangledOnes = javacalls.get(findPkg(e)); + if (mangledOnes == null) { + mangledOnes = new TreeMap<String, ExecutableElement>(); + javacalls.put(findPkg(e), mangledOnes); + } + String mangled = JsCallback.mangle(fqn, method, findParamTypes(found)); + mangledOnes.put(mangled, found); + } + return ""; + } + + private String findParamTypes(ExecutableElement method) { + ExecutableType t = (ExecutableType) method.asType(); + StringBuilder sb = new StringBuilder(); + sb.append('('); + for (TypeMirror tm : t.getParameterTypes()) { + if (tm.getKind().isPrimitive()) { + switch (tm.getKind()) { + case INT: sb.append('I'); break; + case BOOLEAN: sb.append('Z'); break; + case BYTE: sb.append('B'); break; + case CHAR: sb.append('C'); break; + case SHORT: sb.append('S'); break; + case DOUBLE: sb.append('D'); break; + case FLOAT: sb.append('F'); break; + case LONG: sb.append('J'); break; + default: + throw new IllegalStateException("Uknown " + tm.getKind()); + } + } else { + while (tm.getKind() == TypeKind.ARRAY) { + sb.append('['); + tm = ((ArrayType)tm).getComponentType(); + } + sb.append('L'); + Types tu = processingEnv.getTypeUtils(); + Element elm = tu.asElement(tu.erasure(tm)); + dumpElems(sb, elm, ';'); + } + } + sb.append(')'); + return sb.toString(); + } + } + + private static void dumpElems(StringBuilder sb, Element e, char after) { + if (e == null) { + return; + } + if (e.getKind() == ElementKind.PACKAGE) { + PackageElement pe = (PackageElement) e; + sb.append(pe.getQualifiedName().toString().replace('.', '/')).append('/'); + return; + } + Element p = e.getEnclosingElement(); + dumpElems(sb, p, '$'); + sb.append(e.getSimpleName()); + sb.append(after); + } + + private void generateJavaScriptBodyList(Map<String,Set<TypeElement>> bodies) { + if (bodies.isEmpty()) { + return; + } + try { + FileObject all = processingEnv.getFiler().createResource( + StandardLocation.CLASS_OUTPUT, "", "META-INF/net.java.html.js.classes" + ); + PrintWriter wAll = new PrintWriter(new OutputStreamWriter( + all.openOutputStream(), "UTF-8" + )); + for (Map.Entry<String, Set<TypeElement>> entry : bodies.entrySet()) { + String pkg = entry.getKey(); + Set<TypeElement> classes = entry.getValue(); + + FileObject out = processingEnv.getFiler().createResource( + StandardLocation.CLASS_OUTPUT, pkg, "net.java.html.js.classes", + classes.iterator().next() + ); + OutputStream os = out.openOutputStream(); + try { + PrintWriter w = new PrintWriter(new OutputStreamWriter(os, "UTF-8")); + for (TypeElement type : classes) { + final Name bn = processingEnv.getElementUtils().getBinaryName(type); + w.println(bn); + wAll.println(bn); + } + w.flush(); + w.close(); + } catch (IOException x) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to write to " + entry.getKey() + ": " + x.toString()); + } finally { + os.close(); + } + } + wAll.close(); + } catch (IOException x) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to write to " + "META-INF/net.java.html.js.classes: " + x.toString()); + } + } + + private void generateCallbackClass(Map<String,Map<String, ExecutableElement>> process) { + for (Map.Entry<String, Map<String, ExecutableElement>> pkgEn : process.entrySet()) { + String pkgName = pkgEn.getKey(); + Map<String, ExecutableElement> map = pkgEn.getValue(); + StringBuilder source = new StringBuilder(); + source.append("package ").append(pkgName).append(";\n"); + source.append("public final class $JsCallbacks$ {\n"); + source.append(" static final $JsCallbacks$ VM = new $JsCallbacks$(null);\n"); + source.append(" private final org.netbeans.html.boot.spi.Fn.Presenter p;\n"); + source.append(" private $JsCallbacks$ last;\n"); + source.append(" private $JsCallbacks$(org.netbeans.html.boot.spi.Fn.Presenter p) {\n"); + source.append(" this.p = p;\n"); + source.append(" }\n"); + source.append(" final $JsCallbacks$ current() {\n"); + source.append(" org.netbeans.html.boot.spi.Fn.Presenter now = org.netbeans.html.boot.spi.Fn.activePresenter();\n"); + source.append(" if (now == p) return this;\n"); + source.append(" if (last != null && now == last.p) return last;\n"); + source.append(" return last = new $JsCallbacks$(now);\n"); + source.append(" }\n"); + for (Map.Entry<String, ExecutableElement> entry : map.entrySet()) { + final String mangled = entry.getKey(); + final ExecutableElement m = entry.getValue(); + generateMethod(false, m, source, mangled); + generateMethod(true, m, source, "raw$" + mangled); + } + source.append("}\n"); + final String srcName = pkgName + ".$JsCallbacks$"; + try { + Writer w = processingEnv.getFiler().createSourceFile(srcName, + map.values().toArray(new Element[map.size()]) + ).openWriter(); + w.write(source.toString()); + w.close(); + } catch (IOException ex) { + processingEnv.getMessager().printMessage( + Diagnostic.Kind.ERROR, "Can't write " + srcName + ": " + ex.getMessage() + ); + } + } + } + + private void generateMethod(boolean selfObj, final ExecutableElement m, StringBuilder source, final String mangled) { + final boolean isStatic = m.getModifiers().contains(Modifier.STATIC); + if (isStatic && selfObj) { + return; + } + final TypeElement selfType = (TypeElement)m.getEnclosingElement(); + Types tu = processingEnv.getTypeUtils(); + + source.append("\n public java.lang.Object ") + .append(mangled) + .append("("); + + String sep = ""; + StringBuilder convert = new StringBuilder(); + if (!isStatic) { + if (selfObj) { + source.append("java.lang.Object self"); + convert.append(" if (p instanceof org.netbeans.html.boot.spi.Fn.FromJavaScript) {\n"); + convert.append(" self"). + append(" = ((org.netbeans.html.boot.spi.Fn.FromJavaScript)p).toJava(self"). + append(");\n"); + convert.append(" }\n"); + } else { + source.append(selfType.getQualifiedName()); + source.append(" self"); + } + sep = ", "; + } + + int cnt = 0; + for (VariableElement ve : m.getParameters()) { + source.append(sep); + ++cnt; + final TypeMirror t = ve.asType(); + if (!t.getKind().isPrimitive() && !"java.lang.String".equals(t.toString())) { // NOI18N + source.append("java.lang.Object"); + convert.append(" if (p instanceof org.netbeans.html.boot.spi.Fn.FromJavaScript) {\n"); + convert.append(" arg").append(cnt). + append(" = ((org.netbeans.html.boot.spi.Fn.FromJavaScript)p).toJava(arg").append(cnt). + append(");\n"); + convert.append(" }\n"); + } else { + source.append(t); + } + source.append(" arg").append(cnt); + sep = ", "; + } + source.append(") throws Throwable {\n"); + source.append(convert); + if (useTryResources()) { + source.append(" try (java.io.Closeable a = org.netbeans.html.boot.spi.Fn.activate(p)) { \n"); + } else { + source.append(" java.io.Closeable a = org.netbeans.html.boot.spi.Fn.activate(p); try {\n"); + } + source.append(" "); + if (m.getReturnType().getKind() != TypeKind.VOID) { + source.append("java.lang.Object $ret = "); + } + if (isStatic) { + source.append(((TypeElement)m.getEnclosingElement()).getQualifiedName()); + source.append('.'); + } else { + if (selfObj) { + source.append("(("); + source.append(selfType.getQualifiedName()); + source.append(")self)."); + } else { + source.append("self."); + } + } + source.append(m.getSimpleName()); + source.append("("); + cnt = 0; + sep = ""; + for (VariableElement ve : m.getParameters()) { + source.append(sep); + source.append("(").append(tu.erasure(ve.asType())); + source.append(")arg").append(++cnt); + sep = ", "; + } + source.append(");\n"); + if (m.getReturnType().getKind() == TypeKind.VOID) { + source.append(" return null;\n"); + } else { + source.append(" if (p instanceof org.netbeans.html.boot.spi.Fn.ToJavaScript) {\n"); + source.append(" $ret = ((org.netbeans.html.boot.spi.Fn.ToJavaScript)p).toJavaScript($ret);\n"); + source.append(" }\n"); + source.append(" return $ret;\n"); + } + if (useTryResources()) { + source.append(" }\n"); + } else { + + source.append(" } finally {\n"); + source.append(" a.close();\n"); + source.append(" }\n"); + } + source.append(" }\n"); + } + + private boolean useTryResources() { + try { + return processingEnv.getSourceVersion().compareTo(SourceVersion.RELEASE_7) >= 0; + } catch (LinkageError err) { + // can happen when running on JDK6 + return false; + } + } + + private static String findPkg(Element e) { + while (e.getKind() != ElementKind.PACKAGE) { + e = e.getEnclosingElement(); + } + return ((PackageElement)e).getQualifiedName().toString(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/main/java/org/netbeans/html/boot/impl/JsAgent.java ---------------------------------------------------------------------- diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/JsAgent.java b/boot/src/main/java/org/netbeans/html/boot/impl/JsAgent.java new file mode 100644 index 0000000..5d72fa2 --- /dev/null +++ b/boot/src/main/java/org/netbeans/html/boot/impl/JsAgent.java @@ -0,0 +1,75 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.boot.impl; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.lang.instrument.Instrumentation; +import java.security.ProtectionDomain; + +/** + * + * @author Jaroslav Tulach + */ +public final class JsAgent implements ClassFileTransformer { + public static void premain(String args, Instrumentation instr) { + instr.addTransformer(new JsAgent()); + } + + public static void agentmain(String args, Instrumentation instr) { + instr.addTransformer(new JsAgent()); + } + + @Override + public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { + try { + if (JsPkgCache.process(loader, className)) { + return FnUtils.transform(classfileBuffer, loader); + } else { + return classfileBuffer; + } + } catch (Exception ex) { + return classfileBuffer; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/main/java/org/netbeans/html/boot/impl/JsCallback.java ---------------------------------------------------------------------- diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/JsCallback.java b/boot/src/main/java/org/netbeans/html/boot/impl/JsCallback.java new file mode 100644 index 0000000..45bf53b --- /dev/null +++ b/boot/src/main/java/org/netbeans/html/boot/impl/JsCallback.java @@ -0,0 +1,160 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.boot.impl; + + +/** + * + * @author Jaroslav Tulach + */ +abstract class JsCallback { + final String parse(String body) { + StringBuilder sb = new StringBuilder(); + int pos = 0; + for (;;) { + int next = body.indexOf(".@", pos); + if (next == -1) { + sb.append(body.substring(pos)); + body = sb.toString(); + break; + } + int ident = next; + while (ident > 0) { + if (!Character.isJavaIdentifierPart(body.charAt(--ident))) { + ident++; + break; + } + } + String refId = body.substring(ident, next); + + sb.append(body.substring(pos, ident)); + + int sigBeg = body.indexOf('(', next); + int sigEnd = body.indexOf(')', sigBeg); + int colon4 = body.indexOf("::", next); + if (sigBeg == -1 || sigEnd == -1 || colon4 == -1) { + throw new IllegalStateException( + "Wrong format of instance callback. " + + "Should be: '[email protected]::method(Ljava/lang/Object;)(param)':\n" + + body + ); + } + String fqn = body.substring(next + 2, colon4); + String method = body.substring(colon4 + 2, sigBeg); + String params = body.substring(sigBeg, sigEnd + 1); + + int paramBeg = body.indexOf('(', sigEnd + 1); + if (paramBeg == -1) { + throw new IllegalStateException( + "Wrong format of instance callback. " + + "Should be: '[email protected]::method(Ljava/lang/Object;)(param)':\n" + + body + ); + } + + sb.append(callMethod(refId, fqn, method, params)); + if (body.charAt(paramBeg + 1) != (')')) { + sb.append(","); + } + pos = paramBeg + 1; + } + pos = 0; + sb = null; + for (;;) { + int next = body.indexOf("@", pos); + if (next == -1) { + if (sb == null) { + return body; + } + sb.append(body.substring(pos)); + return sb.toString(); + } + if (sb == null) { + sb = new StringBuilder(); + } + + sb.append(body.substring(pos, next)); + + int sigBeg = body.indexOf('(', next); + int sigEnd = body.indexOf(')', sigBeg); + int colon4 = body.indexOf("::", next); + int paramBeg = body.indexOf('(', sigEnd + 1); + if (sigBeg == -1 || sigEnd == -1 || colon4 == -1 || paramBeg == -1) { + throw new IllegalStateException( + "Wrong format of static callback. " + + "Should be: '@pkg.Class::staticMethod(Ljava/lang/Object;)(param)':\n" + + body + ); + } + String fqn = body.substring(next + 1, colon4); + String method = body.substring(colon4 + 2, sigBeg); + String params = body.substring(sigBeg, sigEnd + 1); + + + sb.append(callMethod(null, fqn, method, params)); + pos = paramBeg + 1; + } + } + + protected abstract CharSequence callMethod( + String ident, String fqn, String method, String params + ); + + static String mangle(String fqn, String method, String params) { + if (params.startsWith("(")) { + params = params.substring(1); + } + if (params.endsWith(")")) { + params = params.substring(0, params.length() - 1); + } + return + replace(fqn) + "$" + replace(method) + "$" + replace(params); + } + + private static String replace(String orig) { + return orig.replace("_", "_1"). + replace(";", "_2"). + replace("[", "_3"). + replace('.', '_').replace('/', '_'); + } +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/main/java/org/netbeans/html/boot/impl/JsClassLoader.java ---------------------------------------------------------------------- diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/JsClassLoader.java b/boot/src/main/java/org/netbeans/html/boot/impl/JsClassLoader.java new file mode 100644 index 0000000..58b0a62 --- /dev/null +++ b/boot/src/main/java/org/netbeans/html/boot/impl/JsClassLoader.java @@ -0,0 +1,57 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.boot.impl; + +import net.java.html.js.JavaScriptBody; + +/** Marker class to help us recognize we assigned classloader is + * capable to handle {@link JavaScriptBody} annotated methods. + * + * @author Jaroslav Tulach + */ +abstract class JsClassLoader extends ClassLoader { + JsClassLoader(ClassLoader parent) { + super(parent); + setDefaultAssertionStatus(JsClassLoader.class.desiredAssertionStatus()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/main/java/org/netbeans/html/boot/impl/JsPkgCache.java ---------------------------------------------------------------------- diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/JsPkgCache.java b/boot/src/main/java/org/netbeans/html/boot/impl/JsPkgCache.java new file mode 100644 index 0000000..7064d57 --- /dev/null +++ b/boot/src/main/java/org/netbeans/html/boot/impl/JsPkgCache.java @@ -0,0 +1,132 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.boot.impl; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author Jaroslav Tulach + */ +final class JsPkgCache { + private final Map<String,Set<String>> props = new WeakHashMap<String, Set<String>>(); + private static final Map<ClassLoader, JsPkgCache> CACHE = new WeakHashMap<ClassLoader, JsPkgCache>(); + private static final Set<String> NONE = Collections.emptySet(); + + public static boolean process(ClassLoader l, String className) { + if (className.equals("org.netbeans.html.boot.impl.Test")) { // NOI18N + return true; + } + Set<String> p; + JsPkgCache c; + String pkgName; + synchronized (CACHE) { + c = CACHE.get(l); + if (c == null) { + c = new JsPkgCache(); + CACHE.put(l, c); + } + int lastDot = className.lastIndexOf('.'); + pkgName = className.substring(0, lastDot + 1).replace('.', '/'); + p = c.props.get(pkgName); + if (p == NONE) { + return false; + } else if (p != null) { + return p.contains(className); + } + } + final String res = pkgName + "net.java.html.js.classes"; + + Enumeration<URL> en; + try { + en = l.getResources(res); + } catch (IOException ex) { + en = null; + } + if (en == null || !en.hasMoreElements()) synchronized (CACHE) { + c.props.put(pkgName, NONE); + return false; + } + + try { + Set<String> arr = new TreeSet<String>(); + while (en.hasMoreElements()) { + URL u = en.nextElement(); + BufferedReader r = new BufferedReader( + new InputStreamReader(u.openStream(), "UTF-8") + ); + for (;;) { + String line = r.readLine(); + if (line == null) { + break; + } + arr.add(line); + } + r.close(); + } + p = arr; + } catch (IOException ex) { + LOG.log(Level.WARNING, "Can't read " + res, ex); + p = NONE; + } + + synchronized (CACHE) { + c.props.put(pkgName, p); + return p.contains(className); + } + + } + private static final Logger LOG = Logger.getLogger(JsPkgCache.class.getName()); +}
