com.feilong.lib.javassist.Loader Maven / Gradle / Ivy
Show all versions of feilong Show documentation
/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. Alternatively, the contents of this file may be used under
* the terms of the GNU Lesser General Public License Version 2.1 or later,
* or the Apache License Version 2.0.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*/
package com.feilong.lib.javassist;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Vector;
/**
* The class loader for Javassist.
*
*
* This is a sample class loader using ClassPool
.
* Unlike a regular class loader, this class loader obtains bytecode
* from a ClassPool
.
*
*
* Note that Javassist can be used without this class loader; programmers
* can define their own versions of class loader. They can run
* a program even without any user-defined class loader if that program
* is statically translated with Javassist.
* This class loader is just provided as a utility class.
*
*
* Suppose that an instance of MyTranslator
implementing
* the interface Translator
is responsible for modifying
* class files.
* The startup program of an application using MyTranslator
* should be something like this:
*
*
* import javassist.*;
*
* public class Main{
*
* public static void main(String[] args) throws Throwable{
* MyTranslator myTrans = new MyTranslator();
* ClassPool cp = ClassPool.getDefault();
* Loader cl = new Loader(cp);
* cl.addTranslator(cp, myTrans);
* cl.run("MyApp", args);
* }
* }
*
*
*
* Class MyApp
is the main program of the application.
*
*
* This program should be executed as follows:
*
*
* % java Main arg1 arg2...
*
*
*
* It modifies the class MyApp
with a MyTranslator
* object before the JVM loads it.
* Then it calls main()
in MyApp
with arguments
* arg1, arg2, ...
*
*
* This program execution is equivalent to:
*
*
* % java MyApp arg1 arg2...
*
*
*
* except that classes are translated by MyTranslator
* at load time.
*
*
* If only a particular class must be modified when it is loaded,
* the startup program can be simpler; MyTranslator
is
* unnecessary. For example, if only a class test.Rectangle
* is modified, the main()
method above will be the following:
*
*
* ClassPool cp = ClassPool.getDefault();
* Loader cl = new Loader(cp);
* CtClass ct = cp.get("test.Rectangle");
* ct.setSuperclass(cp.get("test.Point"));
* cl.run("MyApp", args);
*
*
*
* This program changes the super class of the test.Rectangle
* class.
*
*
* Note 1:
*
*
* This class loader does not allow the users to intercept the loading
* of java.*
and javax.*
classes (and
* sun.*
, org.xml.*
, ...) unless
* Loader.doDelegation
is false
. This is because
* the JVM prohibits a user class loader from loading a system class.
* Also see Note 2.
* If this behavior is not appropriate, a subclass of Loader
* must be defined and loadClassByDelegation()
must be overridden.
*
*
* Note 2:
*
*
* If classes are loaded with different class loaders, they belong to
* separate name spaces. If class C
is loaded by a class
* loader CL
, all classes that the class C
* refers to are also loaded by CL
. However, if CL
* delegates the loading of the class C
to CL'
,
* then those classes that the class C
refers to
* are loaded by a parent class loader CL'
* instead of CL
.
*
*
* If an object of class C
is assigned
* to a variable of class C
belonging to a different name
* space, then a ClassCastException
is thrown.
*
*
* Because of the fact above, this loader delegates only the loading of
* javassist.Loader
* and classes included in package java.*
and
* javax.*
to the parent class
* loader. Other classes are directly loaded by this loader.
*
*
* For example, suppose that java.lang.String
would be loaded
* by this loader while java.io.File
is loaded by the parent
* class loader. If the constructor of java.io.File
is called
* with an instance of java.lang.String
, then it may throw
* an exception since it accepts an instance of only the
* java.lang.String
loaded by the parent class loader.
*
* @see com.feilong.lib.javassist.ClassPool
* @see com.feilong.lib.javassist.Translator
*/
public class Loader extends ClassLoader{
/**
* A simpler class loader.
* This is a class loader that exposes the protected {@code defineClass()} method
* declared in {@code java.lang.ClassLoader}. It provides a method similar to
* {@code CtClass#toClass()}.
*
*
* When loading a class, this class loader delegates the work to the
* parent class loader unless the loaded classes are explicitly given
* by {@link #invokeDefineClass(CtClass)}.
* Note that a class {@code Foo} loaded by this class loader is
* different from the class with the same name {@code Foo} but loaded by
* another class loader. This is Java's naming rule.
*
*
* @since 3.24
*/
public static class Simple extends ClassLoader{
/**
* Constructs a class loader.
*/
public Simple(){
}
/**
* Constructs a class loader.
*
* @param parent
* the parent class loader.
*/
public Simple(ClassLoader parent){
super(parent);
}
/**
* Invokes the protected {@code defineClass()} in {@code ClassLoader}.
* It converts the given {@link CtClass} object into a {@code java.lang.Class} object.
*/
public Class> invokeDefineClass(CtClass cc) throws IOException,CannotCompileException{
byte[] code = cc.toBytecode();
return defineClass(cc.getName(), code, 0, code.length);
}
}
private HashMap notDefinedHere; // must be atomic.
private Vector notDefinedPackages; // must be atomic.
private ClassPool source;
private Translator translator;
private ProtectionDomain domain;
/**
* Specifies the algorithm of class loading.
*
*
* This class loader uses the parent class loader for
* java.*
and javax.*
classes.
* If this variable doDelegation
* is false
, this class loader does not delegate those
* classes to the parent class loader.
*
*
* The default value is true
.
*/
public boolean doDelegation = true;
/**
* Creates a new class loader.
*/
public Loader(){
this(null);
}
/**
* Creates a new class loader.
*
* @param cp
* the source of class files.
*/
public Loader(ClassPool cp){
init(cp);
}
/**
* Creates a new class loader
* using the specified parent class loader for delegation.
*
* @param parent
* the parent class loader.
* @param cp
* the source of class files.
*/
public Loader(ClassLoader parent, ClassPool cp){
super(parent);
init(cp);
}
private void init(ClassPool cp){
notDefinedHere = new HashMap<>();
notDefinedPackages = new Vector<>();
source = cp;
translator = null;
domain = null;
delegateLoadingOf(com.feilong.lib.javassist.Loader.class.getName());
}
/**
* Records a class so that the loading of that class is delegated
* to the parent class loader.
*
*
* If the given class name ends with .
(dot), then
* that name is interpreted as a package name. All the classes
* in that package and the sub packages are delegated.
*/
public void delegateLoadingOf(String classname){
if (classname.endsWith(".")){
notDefinedPackages.addElement(classname);
}else{
notDefinedHere.put(classname, this);
}
}
/**
* Sets the protection domain for the classes handled by this class
* loader. Without registering an appropriate protection domain,
* the program loaded by this loader will not work with a security
* manager or a signed jar file.
*/
public void setDomain(ProtectionDomain d){
domain = d;
}
/**
* Sets the soruce ClassPool
.
*/
public void setClassPool(ClassPool cp){
source = cp;
}
/**
* Adds a translator, which is called whenever a class is loaded.
*
* @param cp
* the ClassPool
object for obtaining
* a class file.
* @param t
* a translator.
* @throws NotFoundException
* if t.start()
throws an exception.
* @throws CannotCompileException
* if t.start()
throws an exception.
*/
public void addTranslator(ClassPool cp,Translator t) throws NotFoundException,CannotCompileException{
source = cp;
translator = t;
t.start(cp);
}
/**
* Loads a class and calls main()
in that class.
*
* @param args
* command line parameters.
*
*
* {@code args[0]} is the class name to be loaded.
*
* {@code args[1..n]} are parameters passed
* to the target {@code main()}.
*/
public void run(String[] args) throws Throwable{
if (args.length >= 1){
run(args[0], Arrays.copyOfRange(args, 1, args.length));
}
}
/**
* Loads a class and calls main()
in that class.
*
* @param classname
* the loaded class.
* @param args
* parameters passed to main()
.
*/
public void run(String classname,String[] args) throws Throwable{
Class> c = loadClass(classname);
try{
c.getDeclaredMethod("main", new Class>[] { String[].class }).invoke(null, new Object[] { args });
}catch (InvocationTargetException e){
throw e.getTargetException();
}
}
/**
* Requests the class loader to load a class.
*/
@Override
protected Class> loadClass(String name,boolean resolve) throws ClassFormatError,ClassNotFoundException{
name = name.intern();
synchronized (name){
Class> c = findLoadedClass(name);
if (c == null){
c = loadClassByDelegation(name);
}
if (c == null){
c = findClass(name);
}
if (c == null){
c = delegateToParent(name);
}
if (resolve){
resolveClass(c);
}
return c;
}
}
/**
* Finds the specified class using ClassPath
.
* If the source throws an exception, this returns null.
*
*
* This method can be overridden by a subclass of
* Loader
. Note that the overridden method must not throw
* an exception when it just fails to find a class file.
*
* @return null if the specified class could not be found.
* @throws ClassNotFoundException
* if an exception is thrown while
* obtaining a class file.
*/
@Override
protected Class> findClass(String name) throws ClassNotFoundException{
byte[] classfile;
try{
if (source != null){
if (translator != null){
translator.onLoad(source, name);
}
try{
classfile = source.get(name).toBytecode();
}catch (NotFoundException e){
return null;
}
}else{
String jarname = "/" + name.replace('.', '/') + ".class";
InputStream in = this.getClass().getResourceAsStream(jarname);
if (in == null){
return null;
}
classfile = ClassPoolTail.readStream(in);
}
}catch (Exception e){
throw new ClassNotFoundException("caught an exception while obtaining a class file for " + name, e);
}
int i = name.lastIndexOf('.');
if (i != -1){
String pname = name.substring(0, i);
if (isDefinedPackage(pname)){
try{
definePackage(pname, null, null, null, null, null, null, null);
}catch (IllegalArgumentException e){
// ignore. maybe the package object for the same
// name has been created just right away.
}
}
}
if (domain == null){
return defineClass(name, classfile, 0, classfile.length);
}
return defineClass(name, classfile, 0, classfile.length, domain);
}
private boolean isDefinedPackage(String name){
return getPackage(name) == null;
}
protected Class> loadClassByDelegation(String name) throws ClassNotFoundException{
/*
* The swing components must be loaded by a system
* class loader.
* javax.swing.UIManager loads a (concrete) subclass
* of LookAndFeel by a system class loader and cast
* an instance of the class to LookAndFeel for
* (maybe) a security reason. To avoid failure of
* type conversion, LookAndFeel must not be loaded
* by this class loader.
*/
Class> c = null;
if (doDelegation){
if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("sun.") || name.startsWith("com.sun.")
|| name.startsWith("org.w3c.") || name.startsWith("org.xml.") || notDelegated(name)){
c = delegateToParent(name);
}
}
return c;
}
private boolean notDelegated(String name){
if (notDefinedHere.containsKey(name)){
return true;
}
for (String pack : notDefinedPackages){
if (name.startsWith(pack)){
return true;
}
}
return false;
}
protected Class> delegateToParent(String classname) throws ClassNotFoundException{
ClassLoader cl = getParent();
if (cl != null){
return cl.loadClass(classname);
}
return findSystemClass(classname);
}
}