Zip Class Loader
/* 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(); } }