/*
* Vitry, copyright (C) Hans Hoglund 2011
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING.txt for details.
*/
package vitry.runtime.util;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Loads classes from the java class path and/or any specified path.
*
* Delegates base packages by default and other classes if it can not find them.
* The unload and reload methods return a new class loader. For classes to be
* reclaimed, the old classloader must go out of scope.
*
* @author Hans Hoglund
*/
public class PersistentClassLoader extends ClassLoader
{
public PersistentClassLoader() {
this.classes = new HashMap<String, Class<?>>();
}
public PersistentClassLoader(Map<String, Class<?>> classes) {
this.classes = classes;
}
private final List<URL> paths;
{
paths = new LinkedList<URL>();
paths.addAll(JAVA_CLASS_PATH);
}
private final Map<String, Class<?>> classes;
public synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
boolean delegateEagerly = moduleLoaderShouldDelegate(name);
ClassNotFoundException ex = null;
Class<?> cl = findLoadedClass(name);
if (cl == null)
{
cl = classes.get(name);
}
if (cl == null && delegateEagerly)
{
try
{
cl = this.getParent().loadClass(name);
} catch (ClassNotFoundException e)
{
ex = e;
}
}
if (cl == null)
{
try
{
cl = new DefiningClassLoader(getPathsArray()).loadClass(name, this);
} catch (ClassNotFoundException e)
{
}
}
if (cl == null)
{
if (delegateEagerly)
{
// Only reached if delegation failed
assert (ex != null);
throw ex;
}
else
{
cl = this.getParent().loadClass(name);
}
}
if (resolve)
resolveClass(cl);
return cl;
}
public synchronized PersistentClassLoader unloadClass(String name)
{
// TODO use persistent collection
Map<String, Class<?>> removed = new HashMap<String, Class<?>>(classes);
removed.remove(name);
return new PersistentClassLoader(removed);
}
public synchronized PersistentClassLoader reloadClass(String name)
throws ClassNotFoundException
{
PersistentClassLoader mcl = this.unloadClass(name);
mcl.loadClass(name);
return mcl;
}
public void addPath(String path)
{
addPath(new File(path));
}
public void addPath(File path)
{
try
{
addPath(path.toURI().toURL());
} catch (MalformedURLException e)
{
throw new RuntimeException("Could not translate file name to URL.", e);
}
}
public void addPath(URL path)
{
assert paths.add(path);
}
static class DefiningClassLoader extends URLClassLoader
{
public DefiningClassLoader(URL[] paths) {
super(paths, null);
}
private PersistentClassLoader tempParent;
public Class<?> loadClass(String name) throws ClassNotFoundException
{
Class<?> c = findLoadedClass(name);
if (c == null)
{
if (definingLoaderShouldDelegate(name))
c = tempParent.loadClass(name);
else
c = super.findClass(name);
}
return c;
}
public Class<?> loadClass(String name, PersistentClassLoader parent)
throws ClassNotFoundException
{
// Store parent temporarily for recursive invocations by the JVM
tempParent = parent;
Class<?> c = loadClass(name);
tempParent.classes.put(name, c);
// Set it to null before return, to allow outdated instances
// of ModuleClassLoader to be reclaimed.
tempParent = null;
return c;
}
}
URL[] getPathsArray()
{
return paths.toArray(new URL[paths.size()]);
}
static boolean moduleLoaderShouldDelegate(String name)
{
for (String prefix : DEFINING_DELEGATES)
{
if (name.startsWith(prefix))
return true;
}
return false;
}
static boolean definingLoaderShouldDelegate(String name)
{
for (String prefix : DEFINING_DELEGATES)
{
if (name.startsWith(prefix))
return true;
}
return false;
}
static final String[] MODULE_DELEGATES = { "java.", "javax." };
static final String[] DEFINING_DELEGATES = { "java.", "javax.", "vitry." };
static final List<URL> JAVA_CLASS_PATH = new LinkedList<URL>();
static
{
String cpStr = System.getProperty("java.class.path");
assert (cpStr != null);
for (String pathStr : cpStr.split(File.pathSeparator))
{
try
{
JAVA_CLASS_PATH.add(new File(pathStr).toURI().toURL());
} catch (MalformedURLException e)
{
// Ignore invalid paths
}
}
}
}