source: branches/0.19.x/abcl/src/org/armedbear/lisp/ZipCache.java

Last change on this file was 12533, checked in by Mark Evenson, 15 years ago

Backport r12531 for :ABSOLUTE directory components for jar pathnames.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 9.7 KB
Line 
1/*
2 * ZipCache.java
3 *
4 * Copyright (C) 2010 Mark Evenson
5 * $Id: ZipCache.java 12533 2010-03-14 19:09:04Z mevenson $
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 *
21 * As a special exception, the copyright holders of this library give you
22 * permission to link this library with independent modules to produce an
23 * executable, regardless of the license terms of these independent
24 * modules, and to copy and distribute the resulting executable under
25 * terms of your choice, provided that you also meet, for each linked
26 * independent module, the terms and conditions of the license of that
27 * module.  An independent module is a module which is not derived from
28 * or based on this library.  If you modify this library, you may extend
29 * this exception to your version of the library, but you are not
30 * obligated to do so.  If you do not wish to do so, delete this
31 * exception statement from your version.
32 */
33package org.armedbear.lisp;
34
35import org.armedbear.lisp.util.HttpHead;
36import static org.armedbear.lisp.Lisp.*;
37
38import java.io.File;
39import java.io.IOException;
40import java.net.JarURLConnection;
41import java.net.MalformedURLException;
42import java.net.URL;
43import java.net.URLConnection;
44import java.text.ParseException;
45import java.text.SimpleDateFormat;
46import java.util.Date;
47import java.util.HashMap;
48import java.util.zip.ZipException;
49import java.util.zip.ZipFile;
50
51/**
52 * A cache for all zip/jar file accesses by URL that uses the last
53 * modified time of the cached resource.
54 *
55 * This implementation is NOT thread safe, although usage without
56 * multiple threads recompiling code that is then re-loaded should be
57 * fine.
58 *
59 * If you run into problems with caching, use
60 * (SYS::DISABLE-ZIP-CACHE).  Once disabled, the caching cannot be
61 * re-enabled.
62 *
63 */ 
64public class ZipCache {
65
66    // To make this thread safe, we should return a proxy for ZipFile
67    // that keeps track of the number of outstanding references handed
68    // out, not allowing ZipFile.close() to succeed until that count
69    // has been reduced to 1 or the finalizer is executing.
70    // Unfortunately the relatively simple strategy of extending
71    // ZipFile via a CachedZipFile does not work because there is not
72    // a null arg constructor for ZipFile.
73    static class Entry {
74        long lastModified;
75        ZipFile file;
76    }
77
78    static boolean cacheEnabled = true;
79
80    private final static Primitive DISABLE_ZIP_CACHE = new disable_zip_cache();
81    final static class disable_zip_cache extends Primitive {
82        disable_zip_cache() {
83            super("disable-zip-cache", PACKAGE_SYS, true, "",
84                  "Disable all caching of ABCL FASLs and ZIPs.");
85        }
86        @Override
87        public LispObject execute() {
88            ZipCache.disable();
89            return T;
90        }
91    }
92
93    static public synchronized void disable() {
94        cacheEnabled = false;
95        zipCache.clear(); 
96    }
97
98    static HashMap<URL, Entry> zipCache = new HashMap<URL, Entry>();
99
100    synchronized public static ZipFile get(LispObject arg) {
101        return get(Pathname.makeURL(arg));
102    }
103
104    static final SimpleDateFormat RFC_1123
105        = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
106
107    synchronized public static ZipFile get(final URL url) {
108        if (!cacheEnabled) {
109            if (url.getProtocol().equals("file")) {
110                File f = new File(url.getPath());
111                try { 
112                    return new ZipFile(f);
113                } catch (ZipException e) {
114                    Debug.trace(e); // XXX
115                    return null;
116                } catch (IOException e) {
117                    Debug.trace(e); // XXX
118                    return null;
119                }
120            } else {
121                Entry e = fetchURL(url, false);
122                return e.file;
123            }
124        }               
125
126        Entry entry = zipCache.get(url);
127
128        // Check that the cache entry still accesses a valid ZipFile
129        if (entry != null) {
130            // Simplest way to call private ZipFile.ensureOpen()
131            try {
132                int size = entry.file.size(); 
133            } catch (IllegalStateException e) {
134                zipCache.remove(url);
135                entry = null;
136            }
137        }
138
139        if (entry != null) {
140            if (url.getProtocol().equals("file")) {
141                File f = new File(url.getPath());
142                long current = f.lastModified();
143                if (current > entry.lastModified) {
144                    try {
145                        entry.file = new ZipFile(f);
146                        entry.lastModified = current;
147                    } catch (IOException e) {
148                        Debug.trace(e.toString()); // XXX
149                    }
150                }
151            } else if (url.getProtocol().equals("http")) {
152                // Unfortunately, the Apple JDK under OS X doesn't do
153                // HTTP HEAD requests, instead refetching the entire
154                // resource, and I assume this is the case in all
155                // Sun-derived JVMs.  So, we use a custom HEAD
156                // implementation only looking for Last-Modified
157                // headers, which if we don't find, we give up and
158                // refetch the resource.n
159                String dateString = HttpHead.get(url, "Last-Modified");
160                Date date = null;
161                try {
162                    if (dateString == null) {
163                        throw new ParseException("Failed to get HEAD for " + url, 0);
164                    }
165                    date = RFC_1123.parse(dateString);
166                    long current = date.getTime();
167                    if (current > entry.lastModified) {
168                        entry = fetchURL(url, false);
169                        zipCache.put(url, entry);
170                    }
171                } catch (ParseException e) {
172                   Debug.trace("Failed to parse HTTP Last-Modified field: " + e);
173                   entry = fetchURL(url, false);
174                   zipCache.put(url, entry);
175                }
176           } else { 
177                entry = fetchURL(url, false);
178                zipCache.put(url, entry);
179            }
180        } else {
181            if (url.getProtocol().equals("file")) {
182                entry = new Entry();
183                File f = new File(url.getPath());
184                entry.lastModified = f.lastModified();
185                try {
186                    entry.file = new ZipFile(f);
187                } catch (ZipException e) {
188                    Debug.trace(e); // XXX
189                    return null;
190                } catch (IOException e) {
191                    Debug.trace(e); // XXX
192                    return null;
193                }
194            } else {
195                entry = fetchURL(url, true);
196            }
197            zipCache.put(url, entry);
198        }
199        return entry.file;
200    }
201     
202    static private Entry fetchURL(URL url, boolean cached) {
203        Entry result = new Entry();
204        URL jarURL = null;
205        try {
206            jarURL = new URL("jar:" + url + "!/");
207        } catch (MalformedURLException e) {
208            Debug.trace(e);
209            Debug.assertTrue(false); // XXX
210        }
211        URLConnection connection;
212        try {
213            connection = jarURL.openConnection();
214        } catch (IOException ex) {
215            Debug.trace("Failed to open "
216                        + "'" + jarURL + "'");
217            return null;
218        }
219        if (!(connection instanceof JarURLConnection)) {
220            // XXX
221            Debug.trace("Could not get a URLConnection from " + jarURL);
222            return null;
223        }
224        JarURLConnection jarURLConnection = (JarURLConnection) connection;
225        jarURLConnection.setUseCaches(cached);
226        try {
227            result.file = jarURLConnection.getJarFile();
228        } catch (IOException e) {
229            Debug.trace(e);
230            Debug.assertTrue(false); // XXX
231        }
232        result.lastModified = jarURLConnection.getLastModified();
233        return result;
234    }
235
236    // ## remove-zip-cache-entry pathname => boolean
237    private static final Primitive REMOVE_ZIP_CACHE_ENTRY = new remove_zip_cache_entry();
238    private static class remove_zip_cache_entry extends Primitive { 
239        remove_zip_cache_entry() {
240            super("remove-zip-cache-entry", PACKAGE_SYS, true, "pathname");
241        }
242        @Override
243        public LispObject execute(LispObject arg) {
244            Pathname p = coerceToPathname(arg);
245            boolean result = ZipCache.remove(p);
246            return result ? T : NIL;
247        }
248    }
249     
250    synchronized public static boolean remove(URL url) {
251        Entry entry = zipCache.get(url);
252        if (entry != null) {
253            try {
254                entry.file.close();
255            } catch (IOException e) {}
256            zipCache.remove(entry);
257            return true;
258        }
259        return false;
260    }
261
262    synchronized public static boolean remove(Pathname p) {
263        URL url = Pathname.makeURL(p);
264        if (url == null) {
265            return false;
266        }
267        return ZipCache.remove(url);
268    }
269
270    synchronized public static boolean remove(File f) {
271        Pathname p = Pathname.makePathname(f);
272        return ZipCache.remove(p);
273    }
274}
Note: See TracBrowser for help on using the repository browser.