ZipClassLoader.java Source code

Java tutorial

Introduction

Here is the source code for ZipClassLoader.java

Source

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