package sample.evolve; | |
import javassist.*; | |
/** | |
* Evolution provides a set of methods for instrumenting bytecodes. | |
* | |
* For class evolution, updatable class A is renamed to B. Then an abstract | |
* class named A is produced as the super class of B. If the original class A | |
* has a public method m(), then the abstract class A has an abstract method | |
* m(). | |
* | |
* abstract class A abstract m() _makeInstance() | class A --------> class B m() | |
* m() | |
* | |
* Also, all the other classes are translated so that "new A(i)" in the methods | |
* is replaced with "_makeInstance(i)". This makes it possible to change the | |
* behavior of the instantiation of the class A. | |
*/ | |
public class Evolution implements Translator { | |
public final static String handlerMethod = "_makeInstance"; | |
public final static String latestVersionField = VersionManager.latestVersionField; | |
public final static String versionManagerMethod = "initialVersion"; | |
private static CtMethod trapMethod; | |
private static final int initialVersion = 0; | |
private ClassPool pool; | |
private String updatableClassName = null; | |
private CtClass updatableClass = null; | |
public void start(ClassPool _pool) throws NotFoundException { | |
pool = _pool; | |
// Get the definition of Sample.make() and store it into trapMethod | |
// for later use. | |
trapMethod = _pool.getMethod("sample.evolve.Sample", "make"); | |
} | |
public void onLoad(ClassPool _pool, String classname) | |
throws NotFoundException, CannotCompileException { | |
onLoadUpdatable(classname); | |
/* | |
* Replaces all the occurrences of the new operator with a call to | |
* _makeInstance(). | |
*/ | |
CtClass clazz = _pool.get(classname); | |
CtClass absClass = updatableClass; | |
CodeConverter converter = new CodeConverter(); | |
converter.replaceNew(absClass, absClass, handlerMethod); | |
clazz.instrument(converter); | |
} | |
private void onLoadUpdatable(String classname) throws NotFoundException, | |
CannotCompileException { | |
// if the class is a concrete class, | |
// classname is <updatableClassName>$$<version>. | |
int i = classname.lastIndexOf("$$"); | |
if (i <= 0) | |
return; | |
String orgname = classname.substring(0, i); | |
if (!orgname.equals(updatableClassName)) | |
return; | |
int version; | |
try { | |
version = Integer.parseInt(classname.substring(i + 2)); | |
} | |
catch (NumberFormatException e) { | |
throw new NotFoundException(classname, e); | |
} | |
CtClass clazz = pool.getAndRename(orgname, classname); | |
makeConcreteClass(clazz, updatableClass, version); | |
} | |
/* | |
* Register an updatable class. | |
*/ | |
public void makeUpdatable(String classname) throws NotFoundException, | |
CannotCompileException { | |
if (pool == null) | |
throw new RuntimeException( | |
"Evolution has not been linked to ClassPool."); | |
CtClass c = pool.get(classname); | |
updatableClassName = classname; | |
updatableClass = makeAbstractClass(c); | |
} | |
/** | |
* Produces an abstract class. | |
*/ | |
protected CtClass makeAbstractClass(CtClass clazz) | |
throws CannotCompileException, NotFoundException { | |
int i; | |
CtClass absClass = pool.makeClass(clazz.getName()); | |
absClass.setModifiers(Modifier.PUBLIC | Modifier.ABSTRACT); | |
absClass.setSuperclass(clazz.getSuperclass()); | |
absClass.setInterfaces(clazz.getInterfaces()); | |
// absClass.inheritAllConstructors(); | |
CtField fld = new CtField(pool.get("java.lang.Class"), | |
latestVersionField, absClass); | |
fld.setModifiers(Modifier.PUBLIC | Modifier.STATIC); | |
CtField.Initializer finit = CtField.Initializer.byCall(pool | |
.get("sample.evolve.VersionManager"), versionManagerMethod, | |
new String[] { clazz.getName() }); | |
absClass.addField(fld, finit); | |
CtField[] fs = clazz.getDeclaredFields(); | |
for (i = 0; i < fs.length; ++i) { | |
CtField f = fs[i]; | |
if (Modifier.isPublic(f.getModifiers())) | |
absClass.addField(new CtField(f.getType(), f.getName(), | |
absClass)); | |
} | |
CtConstructor[] cs = clazz.getDeclaredConstructors(); | |
for (i = 0; i < cs.length; ++i) { | |
CtConstructor c = cs[i]; | |
int mod = c.getModifiers(); | |
if (Modifier.isPublic(mod)) { | |
CtMethod wm = CtNewMethod.wrapped(absClass, handlerMethod, c | |
.getParameterTypes(), c.getExceptionTypes(), | |
trapMethod, null, absClass); | |
wm.setModifiers(Modifier.PUBLIC | Modifier.STATIC); | |
absClass.addMethod(wm); | |
} | |
} | |
CtMethod[] ms = clazz.getDeclaredMethods(); | |
for (i = 0; i < ms.length; ++i) { | |
CtMethod m = ms[i]; | |
int mod = m.getModifiers(); | |
if (Modifier.isPublic(mod)) | |
if (Modifier.isStatic(mod)) | |
throw new CannotCompileException( | |
"static methods are not supported."); | |
else { | |
CtMethod m2 = CtNewMethod.abstractMethod(m.getReturnType(), | |
m.getName(), m.getParameterTypes(), m | |
.getExceptionTypes(), absClass); | |
absClass.addMethod(m2); | |
} | |
} | |
return absClass; | |
} | |
/** | |
* Modifies the given class file so that it is a subclass of the abstract | |
* class produced by makeAbstractClass(). | |
* | |
* Note: the naming convention must be consistent with | |
* VersionManager.update(). | |
*/ | |
protected void makeConcreteClass(CtClass clazz, CtClass abstractClass, | |
int version) throws CannotCompileException, NotFoundException { | |
int i; | |
clazz.setSuperclass(abstractClass); | |
CodeConverter converter = new CodeConverter(); | |
CtField[] fs = clazz.getDeclaredFields(); | |
for (i = 0; i < fs.length; ++i) { | |
CtField f = fs[i]; | |
if (Modifier.isPublic(f.getModifiers())) | |
converter.redirectFieldAccess(f, abstractClass, f.getName()); | |
} | |
CtConstructor[] cs = clazz.getDeclaredConstructors(); | |
for (i = 0; i < cs.length; ++i) | |
cs[i].instrument(converter); | |
CtMethod[] ms = clazz.getDeclaredMethods(); | |
for (i = 0; i < ms.length; ++i) | |
ms[i].instrument(converter); | |
} | |
} |