import java.net.*;
import java.io.*;
import java.security.*;
import java.util.HashMap;
/** Loads classes into a private sandbox. Useful for making plugins
to your Java applications, for reloading classes dynamically,
and for loading untrusted code.
<p>Each Sandbox loads all
classes fresh (except those specificially designated as
shared). If you create two Sandboxes and each loads the same
classes, those classes will not be ==, and their instances will
not be castable. This allows you to reload classes at runtime, to
run untrusted classes with separate loaders and protection
mechanisms, and to wipe the static data of a dynamically loaded
class.
<p>In order to allow Sandboxes to communicate, all java.*
classes are shared with the system ClassLoader and you may
designate specific classes to be shared and not reloaded.
<p>
@see MaximumSecurityManager
<p>Morgan McGuire
<br>morgan@cs.williams.edu
<br>http://graphics.cs.williams.edu
<br>Revised April 11, 2008
*/
public class Sandbox extends URLClassLoader {
/** Names and classes that should be shared; all other
classes (except for java.* classes) will be loaded fresh. */
protected HashMap<String, Class> shared = new HashMap<String, Class>();
/** <b>Replaces</b> the system classpath with this one. */
private Sandbox(URL[] classpath, Class[] share) {
super(classpath, null);
for (Class c : share) {
addSharedClass(c);
}
}
/** Prepends this directory onto the system classpath */
private Sandbox(String addToClasspath, Class[] share) throws IOException {
this(toClasspath(addToClasspath), share);
}
/** Prepends this directory onto the system classpath */
private Sandbox(String addToClasspath) throws IOException {
this(toClasspath(addToClasspath), new Class[0]);
}
/** Uses default classpath. */
private Sandbox(Class[] share) throws IOException {
this(getSystemClasspath(), share);
}
/** Uses default classpath. */
private Sandbox() throws IOException {
this(getSystemClasspath(), new Class[0]);
}
/** Share class c with this sandbox. */
public void addSharedClass(Class c) {
if ((c.getClassLoader() != this) &&
(findLoadedClass(c.getName()) != null)) {
throw new IllegalArgumentException
("Class " + c.getName() +
" has already been loaded by " + this + " and cannot be shared.");
}
shared.put(c.getName(), c);
}
/** Directory is treated relative to the current dir */
private static URL[] toClasspath(String directory) throws IOException {
try {
URL url = new File(directory).toURL();
return toClasspath(url);
} catch (MalformedURLException e) {
throw new IOException(e.getMessage());
}
}
/** Concatenates two arrays */
private static URL[] append(URL[] a, URL[] b) {
URL[] c = new URL[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
private static URL[] getSystemClasspath() {
ClassLoader sys = ClassLoader.getSystemClassLoader();
if (sys instanceof URLClassLoader) {
return ((URLClassLoader)sys).getURLs();
} else {
return new URL[0];
}
}
private static URL[] toClasspath(URL directory) {
// Custom classpath
return append(new URL[]{directory}, getSystemClasspath());
}
public Class loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class loadClass(String name, boolean b) throws ClassNotFoundException {
Class c = shared.get(name);
if (c != null) {
// Use the existing shared class
return c;
} else if (name.startsWith("java.")) {
// Load this system class
return Class.forName(name, b, ClassLoader.getSystemClassLoader());
} else {
// Load it myself
return super.loadClass(name, b);
}
}
/** Loads the class named name in its own sandbox. The same as:
<code>Class.forName(name, false, new Sandbox(shared));</code>*/
static Class loadIsolated(String name, Class[] shared) throws ClassNotFoundException, IOException {
return new Sandbox(shared).loadClass(name);
}
}