TemplateClassDefinition<T> templateDefinition, //
String entireClass, //
String materializedClassName) throws ClassTransformationException {
try {
Stopwatch t1 = new Stopwatch().start();
final byte[] implementationClass = classLoader.getClassByteCode(materializedClassName, entireClass);
// Get Template Class
final String templateClassName = templateDefinition.getTemplateClassName().replace('.', FileUtils.separatorChar);
final String templateClassPath = FileUtils.separator + templateClassName + ".class";
t1.stop();
Stopwatch t2 = new Stopwatch().start();
final byte[] templateClass = getClassByteCodeFromPath(templateClassPath);
// int fileNum = new Random().nextInt(100);
//Files.write(templateClass, new File(String.format("/tmp/%d-template.class", fileNum)));
// Generate Merge Class
// Setup adapters for merging, remapping class names and class writing. This is done in reverse order of how they
// will be evaluated.
String oldTemplateSlashName = templateDefinition.getTemplateClassName().replace('.', FileUtils.separatorChar);
String materializedSlashName = materializedClassName.replace('.', FileUtils.separatorChar);
RemapClasses remapper = new RemapClasses(oldTemplateSlashName, materializedSlashName);
Stopwatch t3;
{
ClassNode impl = getClassNodeFromByteCode(implementationClass);
t2.stop();
t3 = new Stopwatch().start();
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
ClassVisitor remappingAdapter = new RemappingClassAdapter(cw, remapper);
MergeAdapter mergingAdapter = new MergeAdapter(oldTemplateSlashName, materializedSlashName, remappingAdapter,
impl);
ClassReader tReader = new ClassReader(templateClass);
tReader.accept(mergingAdapter, ClassReader.EXPAND_FRAMES);
byte[] outputClass = cw.toByteArray();
// Files.write(outputClass, new File(String.format("/tmp/%d-output.class", fileNum)));
outputClass = cw.toByteArray();
// Load the class
classLoader.injectByteCode(materializedClassName, outputClass);
}
t3.stop();
Stopwatch t4 = new Stopwatch().start();
int i = 0;
for (String s : remapper.getSubclasses()) {
logger.debug("Setting up sub class {}", s);
// for each sub class, remap them into the new class.
String subclassPath = FileUtils.separator + s + ".class";
final byte[] bytecode = getClassByteCodeFromPath(subclassPath);
RemapClasses localRemapper = new RemapClasses(oldTemplateSlashName, materializedSlashName);
Preconditions.checkArgument(localRemapper.getSubclasses().isEmpty(), "Class transformations are only supported for classes that have a single level of inner classes.");
ClassWriter subcw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
ClassVisitor remap = new RemappingClassAdapter(subcw, localRemapper);
ClassReader reader = new ClassReader(bytecode);
reader.accept(remap, ClassReader.EXPAND_FRAMES);
byte[] newByteCode = subcw.toByteArray();
classLoader.injectByteCode(s.replace(oldTemplateSlashName, materializedSlashName).replace(FileUtils.separatorChar, '.'), newByteCode);
// Files.write(subcw.toByteArray(), new File(String.format("/tmp/%d-sub-%d.class", fileNum, i)));
i++;
}
t4.stop();
logger.debug(String.format("[Compile Time] Janino: %dms, Bytecode load and parse: %dms, Class Merge: %dms, Subclass remap and load: %dms.", t1.elapsed(TimeUnit.MILLISECONDS), t2.elapsed(TimeUnit.MILLISECONDS), t3.elapsed(TimeUnit.MILLISECONDS), t4.elapsed(TimeUnit.MILLISECONDS)));
Class<?> c = classLoader.findClass(materializedClassName);
if (templateDefinition.getExternalInterface().isAssignableFrom(c)) {
return (T) c.newInstance();
} else {
throw new ClassTransformationException("The requested class did not implement the expected interface.");