/******************************************************************************* * This file is part of logisim-evolution. * * logisim-evolution 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. * * logisim-evolution 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 logisim-evolution. If not, see <http://www.gnu.org/licenses/>. * * Original code by Carl Burch (http://www.cburch.com), 2011. * Subsequent modifications by : * + Haute École Spécialisée Bernoise * http://www.bfh.ch * + Haute École du paysage, d'ingénierie et d'architecture de Genève * http://hepia.hesge.ch/ * + Haute École d'Ingénierie et de Gestion du Canton de Vaud * http://www.heig-vd.ch/ * The project is currently maintained by : * + REDS Institute - HEIG-VD * Yverdon-les-Bains, Switzerland * http://reds.heig-vd.ch *******************************************************************************/ package com.cburch.logisim.util; 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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ZipClassLoader extends ClassLoader { 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; } @SuppressWarnings("unused") void ensureDone() { boolean aborted = false; synchronized (this) { if (!responseSent) { aborted = true; responseSent = true; response = null; notifyAll(); } } if (aborted && DEBUG >= 1) { logger.error("request not handled successfully"); // OK } } Object getResponse() { synchronized (this) { while (!responseSent) { try { this.wait(1000); } catch (InterruptedException e) { } } return response; } } void setResponse(Object value) { synchronized (this) { response = value; responseSent = true; notifyAll(); } } @Override public String toString() { String act = action == REQUEST_LOAD ? "load" : action == REQUEST_FIND ? "find" : "act" + action; return act + ":" + resource; } } private class WorkThread extends Thread { private LinkedList<Request> requests = new LinkedList<Request>(); private ZipFile zipFile = null; @SuppressWarnings("unused") private void ensureZipOpen() { if (zipFile == null) { try { if (DEBUG >= 3) logger.debug(" open ZIP file"); // OK zipFile = new ZipFile(zipPath); if (DEBUG >= 1) logger.debug(" ZIP opened"); // OK } catch (IOException e) { if (DEBUG >= 1) logger.error(" error opening ZIP file"); // OK } } } @SuppressWarnings("unused") private void performFind(Request req) { ensureZipOpen(); Object ret = null; try { if (zipFile != null) { if (DEBUG >= 3) logger.debug(" 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) logger.debug(" found: " + url); // OK } } } catch (Exception ex) { if (DEBUG >= 3) logger.error(" error retrieving data"); // OK ex.printStackTrace(); } req.setResponse(ret); } @SuppressWarnings("unused") private void performLoad(Request req) { BufferedInputStream bis = null; ensureZipOpen(); Object ret = null; try { if (zipFile != null) { if (DEBUG >= 3) logger.debug(" retrieve ZIP entry"); // OK ZipEntry zipEntry = zipFile.getEntry(req.resource); if (zipEntry != null) { if (DEBUG >= 3) logger.debug(" 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) logger.error(" error loading file"); // OK } } } } catch (Exception ex) { if (DEBUG >= 3) logger.error(" error retrieving data"); // OK ex.printStackTrace(); } finally { if (bis != null) { try { if (DEBUG >= 3) logger.debug(" close file"); // OK bis.close(); } catch (IOException ioex) { if (DEBUG >= 3) logger.error(" error closing data"); // OK } } } req.setResponse(ret); } @SuppressWarnings("unused") @Override public void run() { try { while (true) { Request request = waitForNextRequest(); if (request == null) return; if (DEBUG >= 2) logger.debug("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) logger.debug("processed: " + request.getResponse()); // OK } } catch (Exception t) { if (DEBUG >= 3) { logger.error("uncaught: "); t.printStackTrace(); } // OK } finally { if (zipFile != null) { try { zipFile.close(); zipFile = null; if (DEBUG >= 1) logger.debug(" ZIP closed"); // OK } catch (IOException e) { if (DEBUG >= 1) logger.error("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 requests.removeFirst(); } } } final static Logger logger = LoggerFactory.getLogger(ZipClassLoader.class); // 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 File zipPath; private HashMap<String, Object> classes = new HashMap<String, Object>(); private Object bgLock = new Object(); private WorkThread bgThread = null; public ZipClassLoader(File zipFile) { zipPath = zipFile; } public ZipClassLoader(String zipFileName) { this(new File(zipFileName)); } @SuppressWarnings("unused") @Override 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) logger.debug(" define class"); // OK byte[] data = (byte[]) result; result = defineClass(className, data, 0, data.length); if (result != null) { if (DEBUG >= 3) logger.debug(" class defined"); // OK } else { if (DEBUG >= 3) logger.error(" 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); } } @SuppressWarnings("unused") @Override public URL findResource(String resourceName) { if (DEBUG >= 3) logger.debug("findResource " + resourceName); // OK Object ret = request(REQUEST_FIND, resourceName); if (ret instanceof URL) { return (URL) ret; } else { return super.findResource(resourceName); } } 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(); } }