package org.hotswap.agent.plugin.proxy; import java.io.ByteArrayInputStream; import java.util.UUID; import org.hotswap.agent.javassist.ClassPool; import org.hotswap.agent.javassist.CtClass; import org.hotswap.agent.javassist.CtField; import org.hotswap.agent.javassist.CtMethod; import org.hotswap.agent.javassist.Modifier; /** * Transforms the bytecode of a new proxy definition so it is initialized on the first access of one of its methods. * * @author Erki Ehtla * */ public abstract class AbstractProxyBytecodeTransformer implements ProxyBytecodeTransformer { private ClassPool classPool; /** * * @param classPool * Classpool used to make a CtClass */ public AbstractProxyBytecodeTransformer(ClassPool classPool) { this.classPool = classPool; } public byte[] transform(byte[] byteCode) throws Exception { CtClass cc = classPool.makeClass(new ByteArrayInputStream(byteCode), false); try { String initFieldName = INIT_FIELD_PREFIX + generateRandomString(); addStaticInitStateField(cc, initFieldName); String initCode = getInitCall(cc, initFieldName); addInitCallToMethods(cc, initFieldName, initCode); return cc.toBytecode(); } finally { cc.detach(); } } /** * Builds the Java code String which should be executed to initialize the proxy * * @param cc * CtClass from new definition * @param random * randomly generated String * @return Java code to call init the proxy */ protected abstract String getInitCall(CtClass cc, String random) throws Exception; protected String generateRandomString() { return UUID.randomUUID().toString().replace("-", ""); } /** * Adds the initCall as Java code to all the non static methods of the class. The initialization is only done if * clinitFieldName is false. Responsibility to set the clinitFieldName is on the initCall. * * @param cc * CtClass to be modified * @param clinitFieldName * field name in CtClass * @param initCall * Java code to initialize the Proxy * @throws Exception */ protected void addInitCallToMethods(CtClass cc, String clinitFieldName, String initCall) throws Exception { CtMethod[] methods = cc.getDeclaredMethods(); for (CtMethod ctMethod : methods) { if (!ctMethod.isEmpty() && !Modifier.isStatic(ctMethod.getModifiers())) { ctMethod.insertBefore("if(!" + clinitFieldName + "){synchronized(" + cc.getName() + ".class){if(!" + clinitFieldName + "){" + initCall + "}}}"); } } } /** * Adds a static boolean field to the class indicating the state of initialization * * @param cc * CtClass to be modified * @param clinitFieldName * field name in CtClass * @throws Exception */ protected void addStaticInitStateField(CtClass cc, String clinitFieldName) throws Exception { CtField f = new CtField(CtClass.booleanType, clinitFieldName, cc); f.setModifiers(Modifier.PRIVATE | Modifier.STATIC); // init value "true" will be inside clinit, so the field wont actually be initialized on redefinition cc.addField(f, "true"); } }