package net.datacrow.core.services.plugin;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import net.datacrow.core.DataCrow;
import net.datacrow.util.Directory;
import org.apache.log4j.Logger;
/**
* The class loader used to find custom and standard online services.
* @author Robert Jan van der Waals
*/
public class ServiceClassLoader extends ClassLoader {
private static Logger logger = Logger.getLogger(ServiceClassLoader.class.getName());
protected final Collection<File> jarFiles = new ArrayList<File>();
protected final Map<String, Class<?>> cache = new HashMap<String, Class<?>>();
/**
* Creates a new PluginClassLoader that searches in the directory path
* passed as a parameter. The constructor automatically finds all JAR and ZIP
* files in the path and first level of sub directories. The JAR and ZIP files
* are stored in a Vector for future searches.
* @param path the path to the services directory.
*/
public ServiceClassLoader(String path) {
init(path);
for (File jf : jarFiles) {
try {
ZipFile zf = new ZipFile(jf);
Enumeration<? extends ZipEntry> entries = zf.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (name.endsWith(".class")) {
name = name.replaceAll("/", ".");
name = name.substring(0, name.lastIndexOf("."));
loadClass(name);
}
}
} catch (Exception e) {
logger.error(e, e);
}
}
}
public Collection<Class<?>> getClasses() {
return cache.values();
}
/** This version of the constructor is used when ImageJ is launched using Java WebStart. */
public ServiceClassLoader(String path, boolean callSuper) {
super(Thread.currentThread().getContextClassLoader());
init(path);
}
private void init(String path) {
Directory dir = new Directory(DataCrow.servicesDir, true, new String[] {"jar"});
for (String filename : dir.read())
jarFiles.add(new File(filename));
}
/**
* Returns a Class from the path or JAR files. Classes are automatically resolved.
* @param className a class name without the .class extension.
*/
@Override
public Class<?> loadClass(String className) throws ClassNotFoundException {
return loadClass(className, true);
}
/**
* Returns a Class from the path or JAR files. Classes are resolved if resolveIt is true.
* @param className a String class name without the .class extension.
* resolveIt a boolean (should almost always be true)
*/
@Override
public synchronized Class<?> loadClass(String className, boolean resolveIt) throws ClassNotFoundException {
// try the local cache of classes
Class<?> result = cache.get(className);
if (result != null) return result;
// try the system class loader
try {
result = super.findSystemClass(className);
return result;
} catch (Exception e) {
}
// Try to load it from one of the jar files
byte[] classBytes = loadClassBytes(className);
if (classBytes == null) {
result = getParent().loadClass(className);
if (result != null)
return result;
}
if (classBytes == null)
throw new ClassNotFoundException(className);
// Define it (parse the class file)
result = defineClass(className, classBytes, 0, classBytes.length);
if (result == null)
throw new ClassFormatError();
// Resolve if necessary
if (resolveIt) resolveClass(result);
cache.put(className, result);
return result;
}
protected byte[] loadClassBytes(String name) {
for (File jf : jarFiles) {
try {
byte[] classBytes = loadClassFromJar(jf.getPath(), name);
if (classBytes != null) return classBytes;
} catch (Exception e) {}
}
return null;
}
private byte[] loadClassFromJar(String jar, String className) {
String name = className.replace('.', '/');
name += ".class";
return loadFromJar(jar, name);
}
// Load class or resource from a JAR file
private byte[] loadFromJar(String jar, String name) {
BufferedInputStream bis = null;
ZipFile zf = null;
try {
zf = new ZipFile(jar);
Enumeration<? extends ZipEntry> entries = zf.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.getName().equals(name)) {
bis = new BufferedInputStream(zf.getInputStream(entry));
int size = (int) entry.getSize();
byte[] data = new byte[size];
int b = 0, eofFlag = 0;
while ((size - b) > 0) {
eofFlag = bis.read(data, b, size - b);
if (eofFlag == -1)
break;
b += eofFlag;
}
return data;
}
}
} catch (Exception e) {
logger.debug(e, e);
} finally {
try {
if (zf != null)
zf.close();
if (bis != null)
bis.close();
} catch (IOException e) {
logger.error(e, e);
}
}
return null;
}
}