package knorxx.framework.generator.reloading;
import com.google.common.base.Optional;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import knorxx.framework.generator.GenerationRoots;
import knorxx.framework.generator.JavaFile;
import knorxx.framework.generator.JavaFileOnClasspath;
import static knorxx.framework.generator.util.JavaIdentifierUtils.isJavaCoreClass;
/**
*
* @author sj
*/
public class ReloadingClassLoader extends ClassLoader {
private final GenerationRoots generationRoots;
private Map<String, JavaClass> classCache = new HashMap<>();
private final List<AnnotationDescription> annotationDescriptions;
private final ReloadPredicate reloadPredicate;
/**
* @param reloadPredicate If no predicate is supplied only class with available source are reloaded.
*/
public ReloadingClassLoader(GenerationRoots generationRoots, ClassLoader parent,
List<AnnotationDescription> annotationDescriptions, Optional<? extends ReloadPredicate> reloadPredicate) {
super(parent);
this.generationRoots = generationRoots;
this.annotationDescriptions = annotationDescriptions;
this.reloadPredicate = reloadPredicate.isPresent() ? reloadPredicate.get() : new ReloadPredicate.HasSource();
}
@Override
public Class loadClass(String name) throws ClassNotFoundException {
try {
JavaFile javaFile;
JavaClass javaClass = classCache.get(name);
if (javaClass == null) {
Optional<NotYetLoadedJavaFile> javaFileWithSource = NotYetLoadedJavaFile.create(name, generationRoots);
if (javaFileWithSource.isPresent()) {
javaFile = javaFileWithSource.get();
} else {
javaFile = new JavaFileOnClasspath(getParent().loadClass(name));
}
if (isJavaCoreClass(name)) {
javaClass = new JavaClass(javaFile.getJavaClass());
} else {
Class<?> originalClass = (javaFile instanceof NotYetLoadedJavaFile) ? getParent().loadClass(name) : javaFile.getJavaClass();
if (reloadPredicate.apply(originalClass, javaFile instanceof NotYetLoadedJavaFile)) {
byte[] classData = javaFile.readClass();
AddAnnotationClassChangeVisitor addAnnotationClassChangeVisitor = new AddAnnotationClassChangeVisitor(originalClass, annotationDescriptions);
byte[] classDataWithAnnotations = addAnnotationClassChangeVisitor.apply(classData);
javaClass = new JavaClass(defineClass(name, classDataWithAnnotations, 0, classDataWithAnnotations.length), classDataWithAnnotations);
} else {
javaClass = new JavaClass(originalClass);
}
}
classCache.put(name, javaClass);
}
return javaClass.getJavaClass();
} catch (ClassNotFoundException | IOException | ClassFormatError ex) {
throw new ClassNotFoundException("Error while reloading the class '" + name + "'.", ex);
}
}
public Optional<byte[]> getByteCode(String javaClassName) {
JavaClass javaClass = classCache.get(javaClassName);
if(javaClass == null) {
try {
loadClass(javaClassName);
} catch (ClassNotFoundException ex) {
throw new IllegalStateException("Can't load the class '" + javaClassName + "'!", ex);
}
javaClass = classCache.get(javaClassName);
}
if(javaClass == null || !javaClass.hasClassData()) {
return Optional.absent();
} else {
return Optional.of(javaClass.getClassData());
}
}
}