/* Copyright (c) 2006, 2009, Carl Burch. License information is located in the
* com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class ZipClassLoader extends ClassLoader {
// This code was posted on a forum by "leukbr" on March 30, 2001.
// http://forums.sun.com/thread.jspa?threadID=360060&forumID=31
// I've modified it substantially to include a thread that keeps the file
// open for OPEN_TIME milliseconds so time isn't wasted continually
// opening and closing the file.
private static final int OPEN_TIME = 5000;
private static final int DEBUG = 0;
// 0 = no debug messages
// 1 = open/close ZIP file only
// 2 = also each resource request
// 3 = all messages while retrieving resource
private static final int REQUEST_FIND = 0;
private static final int REQUEST_LOAD = 1;
private static class Request {
int action;
String resource;
boolean responseSent;
Object response;
Request(int action, String resource) {
this.action = action;
this.resource = resource;
this.responseSent = false;
}
public String toString() {
String act = action == REQUEST_LOAD ? "load"
: action == REQUEST_FIND ? "find" : "act" + action;
return act + ":" + resource;
}
void setResponse(Object value) {
synchronized(this) {
response = value;
responseSent = true;
notifyAll();
}
}
void ensureDone() {
boolean aborted = false;
synchronized(this) {
if(!responseSent) {
aborted = true;
responseSent = true;
response = null;
notifyAll();
}
}
if(aborted && DEBUG >= 1) {
System.err.println("request not handled successfully"); //OK
}
}
Object getResponse() {
synchronized(this) {
while(!responseSent) {
try { this.wait(1000); } catch(InterruptedException e) { }
}
return response;
}
}
}
private class WorkThread extends Thread {
private LinkedList requests = new LinkedList();
private ZipFile zipFile = null;
public void run() {
try {
while(true) {
Request request = waitForNextRequest();
if(request == null) return;
if(DEBUG >= 2) System.err.println("processing " + request); //OK
try {
switch(request.action) {
case REQUEST_LOAD: performLoad(request); break;
case REQUEST_FIND: performFind(request); break;
}
} finally {
request.ensureDone();
}
if(DEBUG >= 2) System.err.println("processed: " + request.getResponse()); //OK
}
} catch(Throwable t) {
if(DEBUG >= 3) { System.err.print("uncaught: "); t.printStackTrace(); } //OK
} finally {
if(zipFile != null) {
try {
zipFile.close();
zipFile = null;
if(DEBUG >= 1) System.err.println(" ZIP closed"); //OK
} catch(IOException e) {
if(DEBUG >= 1) System.err.println("Error closing ZIP file"); //OK
}
}
}
}
private Request waitForNextRequest() {
synchronized(bgLock) {
long start = System.currentTimeMillis();
while(requests.isEmpty()) {
long elapse = System.currentTimeMillis() - start;
if(elapse >= OPEN_TIME) {
bgThread = null;
return null;
}
try {
bgLock.wait(OPEN_TIME);
} catch(InterruptedException e) { }
}
return (Request) requests.removeFirst();
}
}
private void performFind(Request req) {
ensureZipOpen();
Object ret = null;
try {
if(zipFile != null) {
if(DEBUG >= 3) System.err.println(" retrieve ZIP entry"); //OK
String res = req.resource;
ZipEntry zipEntry = zipFile.getEntry(res);
if(zipEntry != null) {
String url = "jar:" + zipPath.toURI() + "!/" + res;
ret = new URL(url);
if(DEBUG >= 3) System.err.println(" found: " + url); //OK
}
}
} catch(Throwable ex) {
if(DEBUG >= 3) System.err.println(" error retrieving data"); //OK
ex.printStackTrace();
}
req.setResponse(ret);
}
private void performLoad(Request req) {
BufferedInputStream bis = null;
ensureZipOpen();
Object ret = null;
try {
if(zipFile != null) {
if(DEBUG >= 3) System.err.println(" retrieve ZIP entry"); //OK
ZipEntry zipEntry = zipFile.getEntry(req.resource);
if(zipEntry != null) {
if(DEBUG >= 3) System.err.println(" load file"); //OK
byte[] result = new byte[(int) zipEntry.getSize()];
bis = new BufferedInputStream(zipFile.getInputStream(zipEntry));
try {
bis.read(result, 0, result.length);
ret = result;
} catch(IOException e) {
if(DEBUG >= 3) System.err.println(" error loading file"); //OK
}
}
}
} catch(Throwable ex) {
if(DEBUG >= 3) System.err.println(" error retrieving data"); //OK
ex.printStackTrace();
} finally {
if (bis!=null) {
try {
if(DEBUG >= 3) System.err.println(" close file"); //OK
bis.close();
} catch (IOException ioex) {
if(DEBUG >= 3) System.err.println(" error closing data"); //OK
}
}
}
req.setResponse(ret);
}
private void ensureZipOpen() {
if(zipFile == null) {
try {
if(DEBUG >= 3) System.err.println(" open ZIP file"); //OK
zipFile = new ZipFile(zipPath);
if(DEBUG >= 1) System.err.println(" ZIP opened"); //OK
} catch(IOException e) {
if(DEBUG >= 1) System.err.println(" error opening ZIP file"); //OK
}
}
}
}
private File zipPath;
private HashMap classes = new HashMap();
private Object bgLock = new Object();
private WorkThread bgThread = null;
public ZipClassLoader(String zipFileName) {
this(new File(zipFileName));
}
public ZipClassLoader(File zipFile) {
zipPath = zipFile;
}
public URL findResource(String resourceName) {
if(DEBUG >= 3) System.err.println("findResource " + resourceName); //OK
Object ret = request(REQUEST_FIND, resourceName);
if(ret instanceof URL) {
return (URL) ret;
} else {
return super.findResource(resourceName);
}
}
public Class findClass(String className) throws ClassNotFoundException {
boolean found = false;
Object result = null;
// check whether we have loaded this class before
synchronized(classes) {
found = classes.containsKey(className);
if(found) result = classes.get(className);
}
// try loading it from the ZIP file if we haven't
if(!found) {
String resourceName = className.replace('.', '/') + ".class";
result = request(REQUEST_LOAD, resourceName);
if(result instanceof byte[]) {
if(DEBUG >= 3) System.err.println(" define class"); //OK
byte[] data = (byte[]) result;
result = defineClass(className, data, 0, data.length);
if(result != null) {
if(DEBUG >= 3) System.err.println(" class defined"); //OK
} else {
if(DEBUG >= 3) System.err.println(" format error"); //OK
result = new ClassFormatError(className);
}
}
synchronized(classes) { classes.put(className, result); }
}
if(result instanceof Class) {
return (Class) result;
} else if(result instanceof ClassNotFoundException) {
throw (ClassNotFoundException) result;
} else if(result instanceof Error) {
throw (Error) result;
} else {
return super.findClass(className);
}
}
private Object request(int action, String resourceName) {
Request request;
synchronized(bgLock) {
if(bgThread == null) { // start the thread if it isn't working
bgThread = new WorkThread();
bgThread.start();
}
request = new Request(action, resourceName);
bgThread.requests.addLast(request);
bgLock.notifyAll();
}
return request.getResponse();
}
}