package br.com.cafebinario.dynamic;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
public final class DynamicJ {
private static class LoadedClass {
File binFile;
String className;
Class clazz;
long lastModified;
SourceDir srcDir;
File srcFile;
LoadedClass(String className, SourceDir src) {
this.className = className;
this.srcDir = src;
String path = className.replace('.', '/');
this.srcFile = new File(src.srcDir, path + ".java");
this.binFile = new File(src.binDir, path + ".class");
System.out
.println("Diret�rio tempor�rio dos execut�veis (.class): "
+ this.binFile);
compileAndLoadClass();
}
void compileAndLoadClass() {
if (clazz != null) {
return;
}
String error = null;
if (binFile.lastModified() < srcFile.lastModified()) {
error = srcDir.javac.compile(new File[] { srcFile });
}
if (error != null) {
throw new RuntimeException("Failed to compile "
+ srcFile.getAbsolutePath() + ". Error: " + error);
}
try {
clazz = srcDir.classLoader.loadClass(className);
lastModified = srcFile.lastModified();
} catch (ClassNotFoundException e) {
throw new RuntimeException("Failed to load DynaCode class "
+ srcFile.getAbsolutePath());
}
info("Init " + clazz);
}
boolean isChanged() {
return srcFile.lastModified() != lastModified;
}
}
private class MyInvocationHandler implements InvocationHandler {
Object backend;
String backendClassName;
MyInvocationHandler(String className) {
backendClassName = className;
try {
Class clz = loadClass(backendClassName);
backend = newDynaCodeInstance(clz);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Class clz = loadClass(backendClassName);
if (backend.getClass() != clz) {
backend = newDynaCodeInstance(clz);
}
try {
return method.invoke(backend, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
private Object newDynaCodeInstance(Class clz) {
try {
return clz.newInstance();
} catch (Exception e) {
throw new RuntimeException(
"Failed to new instance of DynaCode class "
+ clz.getName(), e);
}
}
}
private class SourceDir {
File binDir;
URLClassLoader classLoader;
Javac javac;
File srcDir;
SourceDir(File srcDir) {
this.srcDir = srcDir;
String subdir = srcDir.getAbsolutePath().replace(':', '_')
.replace('/', '_').replace('\\', '_');
this.binDir = new File(System.getProperty("java.io.tmpdir"),
"dynacode/" + subdir);
this.binDir.mkdirs();
this.javac = new Javac(compileClasspath, binDir.getAbsolutePath());
recreateClassLoader();
}
void recreateClassLoader() {
try {
classLoader = new URLClassLoader(new URL[] { binDir.toURL() },
parentClassLoader);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
private static String extractClasspath(ClassLoader cl) {
StringBuffer buf = new StringBuffer();
while (cl != null) {
if (cl instanceof URLClassLoader) {
URL urls[] = ((URLClassLoader) cl).getURLs();
for (int i = 0; i < urls.length; i++) {
if (buf.length() > 0) {
buf.append(File.pathSeparatorChar);
}
buf.append(urls[i].getFile().toString());
}
}
cl = cl.getParent();
}
return buf.toString();
}
private static void info(String msg) {
System.out.println("[DynaCode] " + msg);
}
private String compileClasspath;
private HashMap loadedClasses = new HashMap();
private ClassLoader parentClassLoader;
private ArrayList sourceDirs = new ArrayList();
public DynamicJ() {
this(Thread.currentThread().getContextClassLoader());
}
public DynamicJ(ClassLoader parentClassLoader) {
this(extractClasspath(parentClassLoader), parentClassLoader);
}
public DynamicJ(String compileClasspath, ClassLoader parentClassLoader) {
this.compileClasspath = compileClasspath;
this.parentClassLoader = parentClassLoader;
}
public boolean addSourceDir(File srcDir) {
try {
srcDir = srcDir.getCanonicalFile();
} catch (IOException e) {
e.printStackTrace();
}
synchronized (sourceDirs) {
for (int i = 0; i < sourceDirs.size(); i++) {
SourceDir src = (SourceDir) sourceDirs.get(i);
if (src.srcDir.equals(srcDir)) {
return false;
}
}
SourceDir src = new SourceDir(srcDir);
sourceDirs.add(src);
info("Add source dir " + srcDir);
}
return true;
}
public URL getResource(String resource) {
try {
SourceDir src = locateResource(resource);
return src == null ? null : new File(src.srcDir, resource).toURL();
} catch (MalformedURLException e) {
return null;
}
}
public InputStream getResourceAsStream(String resource) {
try {
SourceDir src = locateResource(resource);
return src == null ? null : new FileInputStream(new File(
src.srcDir, resource));
} catch (FileNotFoundException e) {
return null;
}
}
public Class loadClass(String className) throws ClassNotFoundException {
LoadedClass loadedClass = null;
synchronized (loadedClasses) {
loadedClass = (LoadedClass) loadedClasses.get(className);
}
if (loadedClass == null) {
String resource = className.replace('.', '/') + ".java";
SourceDir src = locateResource(resource);
if (src == null) {
throw new ClassNotFoundException("DynaCode class not found "
+ className);
}
synchronized (this) {
loadedClass = new LoadedClass(className, src);
synchronized (loadedClasses) {
loadedClasses.put(className, loadedClass);
}
}
return loadedClass.clazz;
}
if (loadedClass.isChanged()) {
unload(loadedClass.srcDir);
return loadClass(className);
}
return loadedClass.clazz;
}
private SourceDir locateResource(String resource) {
for (int i = 0; i < sourceDirs.size(); i++) {
SourceDir src = (SourceDir) sourceDirs.get(i);
if (new File(src.srcDir, resource).exists()) {
return src;
}
}
return null;
}
public Object newProxyInstance(Class interfaceClass, String implClassName)
throws RuntimeException {
MyInvocationHandler handler = new MyInvocationHandler(implClassName);
return Proxy.newProxyInstance(interfaceClass.getClassLoader(),
new Class[] { interfaceClass }, handler);
}
private void unload(SourceDir src) {
synchronized (loadedClasses) {
for (Iterator iter = loadedClasses.values().iterator(); iter
.hasNext();) {
LoadedClass loadedClass = (LoadedClass) iter.next();
if (loadedClass.srcDir == src) {
iter.remove();
}
}
}
src.recreateClassLoader();
}
}