package net.sourceforge.javautil.bytecode;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.util.ASMifierClassVisitor;
import net.sourceforge.javautil.bytecode.api.BytecodeResolutionPool;
import net.sourceforge.javautil.bytecode.api.IBytecodeResolvable;
import net.sourceforge.javautil.bytecode.api.BytecodeResolutionPool.Linkable;
import net.sourceforge.javautil.bytecode.api.TypeMemberAccess.Scope;
import net.sourceforge.javautil.bytecode.api.type.AbstractType;
import net.sourceforge.javautil.bytecode.api.type.BytecodeContextType;
import net.sourceforge.javautil.bytecode.api.type.IBytecodeWriterType;
import net.sourceforge.javautil.bytecode.api.type.JavaClassAbstract;
import net.sourceforge.javautil.bytecode.api.type.JavaClassConcrete;
import net.sourceforge.javautil.bytecode.api.type.JavaInterface;
import net.sourceforge.javautil.common.logging.ILogger;
import net.sourceforge.javautil.common.logging.LoggerLevelStandard;
import net.sourceforge.javautil.common.logging.LoggingContext;
import net.sourceforge.javautil.common.version.IVersion;
import net.sourceforge.javautil.common.version.Version;
/**
* This will allow creation of {@link Class}'s from dynamically generated bytecode.
*
* @author elponderador
* @author $Author$
* @version $Id$
*/
public class BytecodeCompiler {
private static final ILogger log = LoggingContext.getLogger(BytecodeCompiler.class);
/**
* The different versions of the bytecode/class file specification.
*
* @author elponderador
* @author $Author$
* @version $Id$
*/
public enum Version implements IVersion<Version> {
Version1_1("1.1"),
Version1_2("1.2"),
Version1_3("1.3"),
Version1_4("1.4"),
Version1_5("1.5"),
Version6("6.0"),
Version7("7.0");
public static Version getJVMVersion () {
String version = System.getProperty("java.class.version");
if ("196653".equals(version)) return Version.Version1_1;
if ("46.0".equals(version)) return Version.Version1_2;
if ("47.0".equals(version)) return Version.Version1_3;
if ("48.0".equals(version)) return Version.Version1_4;
if ("49.0".equals(version)) return Version.Version1_5;
if ("50.0".equals(version)) return Version.Version6;
if ("51.0".equals(version)) return Version.Version7;
throw new RuntimeException("Could not determine current JVM class version");
}
private final IVersion version;
/**
* @param version The string form of the version
*/
private Version(String version) {
this.version = net.sourceforge.javautil.common.version.Version.decode(version);
}
public int getMajorVersion() {
return version.getMajorVersion();
}
public int getMicroVersion() {
return version.getMicroVersion();
}
public int getMinorVersion() {
return version.getMinorVersion();
}
public String getSuffix() {
return version.getSuffix();
}
}
protected final IBytecodeFactory factory;
protected final BytecodeResolutionPool pool;
protected final Loader loader;
protected final Version defaultVersion;
public BytecodeCompiler(IBytecodeFactory factory) {
this(Thread.currentThread().getContextClassLoader(), factory);
}
public BytecodeCompiler(ClassLoader parent, IBytecodeFactory factory) {
this(parent, factory, Version.getJVMVersion());
}
public BytecodeCompiler(ClassLoader parent, IBytecodeFactory factory, Version defaultVersion) {
this.factory = factory;
this.defaultVersion = defaultVersion;
this.loader = new Loader(parent);
this.pool = new BytecodeResolutionPool(loader);
}
public JavaClassAbstract createAbstractClass (String name, Scope scope, boolean isStatic, boolean isFinal) {
return new JavaClassAbstract(this, name, scope, isStatic, isFinal);
}
public JavaClassConcrete createConcreteClass (String name, Scope scope, boolean isStatic, boolean isFinal) {
return new JavaClassConcrete(this, name, scope, isStatic, isFinal);
}
public JavaInterface createJavaInterface (String name, Scope scope, boolean isStatic, boolean isFinal) {
return new JavaInterface(this, name, scope, isStatic, isFinal);
}
/**
* @return The factory being used by this compiler
*/
public IBytecodeFactory getFactory() { return factory; }
/**
* @return The pool being used by this compiler
*/
public BytecodeResolutionPool getPool() { return pool; }
/**
* @return The default version that will be used at compile time
*/
public Version getDefaultVersion() { return defaultVersion; }
/**
* This assumes the use of the {@link #getDefaultVersion()}.
*
* @see #compile(AbstractType, Version)
*/
public Class compile (AbstractType type) { return compile(type, defaultVersion); }
/**
* @param type The type to compile
* @param version The version of bytecode/class file format to generate
* @return The compiled class
*/
public Class compile (AbstractType type, Version version) {
BytecodeContextType ctx = this.factory.createTypeContext(type, pool, version);
type.write(ctx);
byte[] bytecode = ctx.getGeneratedBytecode();
if (log.isLogging(LoggerLevelStandard.DEBUG)) {
ClassReader cr = new ClassReader(bytecode);
ASMifierClassVisitor asm = new ASMifierClassVisitor(
new PrintWriter(log.getLoggingWriter(LoggerLevelStandard.DEBUG), true) // log.getLoggingWriter(LoggerLevelStandard.INFO), true)
);
cr.accept(asm, 0);
}
return loader.define(type.getName(), bytecode);
}
/**
* @param handler The handler for the proxy
* @param interfaces The interfaces (generated/visible by this compiler)
* @return The proxy
*/
public Object createProxy (InvocationHandler handler, Class... interfaces) {
return Proxy.newProxyInstance(loader, interfaces, handler);
}
/**
* @return The class loader used by this compiler
*/
protected Loader getLoader () {
return this.loader;
}
/**
* Internal loader for class creation.
*
* @author elponderador
* @author $Author$
* @version $Id$
*/
private class Loader extends ClassLoader {
public Loader(ClassLoader parent) {
super(parent);
}
/**
* @param name The name of the class
* @param bytecode The bytecode for the class
* @return The created class
*/
public Class define (String name, byte[] bytecode) {
Class defined = this.defineClass(name, bytecode, 0, bytecode.length);
pool.clear(name);
return defined;
}
@Override protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
return super.findClass(name);
} catch (ClassNotFoundException e) {
IBytecodeResolvable resolved = pool.resolve(name);
if (resolved != null) {
if (resolved instanceof AbstractType) {
return compile( (AbstractType) resolved );
} else {
return ((Linkable)resolved).getWrapped();
}
} else
throw e;
}
}
}
}