jarファイルからクラスをロードする その2

カレントスレッドのクラスローダに定義するように修正。すこしはマシかな…


package jarjar;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.jar.*;

public class JarJar {

public static void main(String args) {
try {
ClassLoader cloader = Thread.currentThread().getContextClassLoader();
JarJar jarjar = new JarJar("ooweb-0.5.jar", cloader);
jarjar.load();
println(Class.forName("net.sf.ooweb.util.StringUtils"));
} catch (Throwable e) {
e.printStackTrace();
}
}

private static void println(Object x) {
System.out.println(x);
}

/////////////////////////////////////////////////////////////////
private JarFile jar = null;

private ClassLoader cloader = null;

private Method define = null;

private static final Integer ZERO = new Integer(0);

public JarJar(String filename, ClassLoader cloader) throws IOException {
this.jar = new JarFile(filename);
this.cloader = cloader;
Class args = { String.class, byte.class, int.class, int.class };

try {
define = ClassLoader.class.getDeclaredMethod("defineClass", args);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}

define.setAccessible(true);
}

public void load() throws IOException {
Map ctx = new HashMap();

for (Enumeration enum = jar.entries(); enum.hasMoreElements();) {
JarEntry entry = (JarEntry) enum.nextElement();
String name = entry.getName();

if (entry.isDirectory() || !name.endsWith(".class"))
continue;

byte bs = getBytes(jar, entry);
ctx.put(getClassName(name), bs);
}

Set classes = new HashSet();

for (Iterator names = ctx.keySet().iterator(); names.hasNext();) {
String name = (String) names.next();
loadClasses(name, ctx, classes);
}
}

private void loadClasses(String name, Map ctx, Set classes) {
if (classes.contains(name))
return;

byte bs = (byte) ctx.get(name);

try {
defineClass(name, bs);
} catch (NoClassDefFoundError e) {
loadClasses(getClassName(e.getMessage()), ctx, classes);
defineClass(name, bs);
}

classes.add(name);
}

private byte getBytes(JarFile jar, JarEntry entry) throws IOException {
InputStream in = jar.getInputStream(entry);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte
buf = new byte[1024];

while (in.available() > 0) {
int len = in.read(buf);
out.write(buf, 0, len);
}

out.flush();
in.close();
return out.toByteArray();
}

private String getClassName(String name) {
name = name.replace('/', '.');

if (name.endsWith(".class"))
name = name.substring(0, name.length() - 6);

return name;
}

private void defineClass(String name, byte bs) {
try {
if (define == null) {
}

Object objs = { name, bs, ZERO, new Integer(bs.length) };
define.invoke(cloader, objs);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();

if (cause instanceof NoClassDefFoundError)
throw (NoClassDefFoundError) cause;

throw new RuntimeException(e);
}
}

}