
org.netbeans.html.boot.impl.FnUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of net.java.html.boot Show documentation
Show all versions of net.java.html.boot Show documentation
Builder to launch your Java/HTML based application.
/**
* 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-2014 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.InputStreamReader;
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 args;
if (fia.javacall) {
body = callback(fia.body);
args = new ArrayList(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:
super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/Boolean", "booleanValue", "()Z"
);
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", "", "(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 args = new ArrayList();
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 l = res(name, true);
return l.isEmpty() ? null : l.get(0);
}
@Override
protected Enumeration findResources(String name) {
return Collections.enumeration(res(name, false));
}
private List res(String name, boolean oneIsEnough) {
List l = new ArrayList();
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);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy