package org.hotswap.agent.plugin.proxy.hscglib;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.hotswap.agent.javassist.CtClass;
import org.hotswap.agent.javassist.CtMethod;
import org.hotswap.agent.javassist.Modifier;
import org.hotswap.agent.javassist.bytecode.MethodInfo;
import org.hotswap.agent.logging.AgentLogger;
/**
* Inits plugin and adds bytecode generation call parameter recording
*
* @author Erki Ehtla
*
*/
public class GeneratorParametersTransformer {
private static AgentLogger LOGGER = AgentLogger.getLogger(GeneratorParametersTransformer.class);
private static Map<ClassLoader, WeakReference<Map<String, Object>>> classLoaderMaps = new WeakHashMap<ClassLoader, WeakReference<Map<String, Object>>>();
/**
* Adds bytecode generation call parameter recording
*
* @param cc
* @return
* @throws Exception
*/
public static CtClass transform(CtClass cc) throws Exception {
if (isGeneratorStrategy(cc)) {
for (CtMethod method : cc.getDeclaredMethods()) {
if (!Modifier.isAbstract(method.getModifiers()) && method.getName().equals("generate")
&& method.getMethodInfo().getDescriptor().endsWith(";)[B")) {
cc.defrost();
method.insertAfter("org.hotswap.agent.plugin.proxy.hscglib.GeneratorParametersRecorder.register($0, $1, $_);");
}
}
}
return cc;
}
/**
* Determines if a Class is a Cglib GeneratorStrategy subclass
*
* @param cc
* @return
*/
private static boolean isGeneratorStrategy(CtClass cc) {
String[] interfaces = cc.getClassFile2().getInterfaces();
for (String interfaceName : interfaces) {
// We use class name strings because some libraries repackage cglib to a different namespace to avoid
// conflicts.
if (interfaceName.endsWith(".GeneratorStrategy")) {
@SuppressWarnings("unchecked")
List<MethodInfo> methodInfos = cc.getClassFile2().getMethods();
for (MethodInfo method : methodInfos) {
if (method.getName().equals("generate") && method.getDescriptor().endsWith("[B")) {
return true;
}
}
}
}
return false;
}
/**
* Retrieves GeneratorParams Map from within a ClassLoader
*
* @param loader
* @return Map of Class names and parameters used for Proxy creation
*/
@SuppressWarnings("unchecked")
private static Map<String, Object> getGeneratorParamsMap(ClassLoader loader) {
try {
WeakReference<Map<String, Object>> mapRef;
synchronized (classLoaderMaps) {
mapRef = classLoaderMaps.get(loader);
if (mapRef == null) {
Map<String, Object> map = (Map<String, Object>) loader
.loadClass(GeneratorParametersRecorder.class.getName()).getField("generatorParams")
.get(null);
mapRef = new WeakReference<Map<String, Object>>(map);
classLoaderMaps.put(loader, mapRef);
}
}
Map<String, Object> map = mapRef.get();
if (map == null) {
return new HashMap<>();
}
return map;
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException
| ClassNotFoundException e) {
LOGGER.error("Unable to access field with proxy generation parameters. Proxy redefinition failed.");
throw new RuntimeException(e);
}
}
/**
* Retrieves GeneratorParams from within a ClassLoader
*
* @param loader
* @param name
* Class name
* @return GeneratorParams instance in this ClassLoader
*/
public static GeneratorParams getGeneratorParams(ClassLoader loader, String name) {
Object generatorParams = getGeneratorParamsMap(loader).get(name);
if (generatorParams != null) {
try {
return GeneratorParams.valueOf(generatorParams);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return null;
}
}