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

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

Under Windows, properly reference jars on other drive letters.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 10.7 KB
Line 
1/*
2 * ZipCache.java
3 *
4 * Copyright (C) 2010 Mark Evenson
5 * $Id: ZipCache.java 12643 2010-05-01 14:24:57Z 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(Pathname p) {
101        return get(Pathname.makeURL(p));
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                    error(new FileError("Failed to construct ZipFile"
115                                        + " because " + e,
116                                        Pathname.makePathname(f)));
117                } catch (IOException e) {
118                    error(new FileError("Failed to contruct ZipFile"
119                                        + " because " + e,
120                                        Pathname.makePathname(f)));
121                }
122            } else {
123                Entry e = fetchURL(url, false);
124                return e.file;
125            }
126        }               
127
128        Entry entry = zipCache.get(url);
129
130        // Check that the cache entry still accesses a valid ZipFile
131        if (entry != null) {
132            // Simplest way to call private ZipFile.ensureOpen()
133            try {
134                int size = entry.file.size(); 
135            } catch (IllegalStateException e) {
136                zipCache.remove(url);
137                entry = null;
138            }
139        }
140
141        if (entry != null) {
142            if (url.getProtocol().equals("file")) {
143                File f = new File(url.getPath());
144                long current = f.lastModified();
145                if (current > entry.lastModified) {
146                    try {
147                        entry.file = new ZipFile(f);
148                        entry.lastModified = current;
149                    } catch (IOException e) {
150                        Debug.trace(e.toString()); // XXX
151                    }
152                }
153            } else if (url.getProtocol().equals("http")) {
154                // Unfortunately, the Apple JDK under OS X doesn't do
155                // HTTP HEAD requests, instead refetching the entire
156                // resource, and I assume this is the case in all
157                // Sun-derived JVMs.  So, we use a custom HEAD
158                // implementation only looking for Last-Modified
159                // headers, which if we don't find, we give up and
160                // refetch the resource.n
161                String dateString = HttpHead.get(url, "Last-Modified");
162                Date date = null;
163                try {
164                    if (dateString == null) {
165                        throw new ParseException("Failed to get HEAD for " + url, 0);
166                    }
167                    date = RFC_1123.parse(dateString);
168                    long current = date.getTime();
169                    if (current > entry.lastModified) {
170                        entry = fetchURL(url, false);
171                        zipCache.put(url, entry);
172                    }
173                } catch (ParseException e) {
174                   Debug.trace("Failed to parse HTTP Last-Modified field: " + e);
175                   entry = fetchURL(url, false);
176                   zipCache.put(url, entry);
177                }
178           } else { 
179                entry = fetchURL(url, false);
180                zipCache.put(url, entry);
181            }
182        } else {
183            if (url.getProtocol().equals("file")) {
184                entry = new Entry();
185                String path = url.getPath();
186
187                if (Utilities.isPlatformWindows) {
188                    String authority = url.getAuthority();
189                    if (authority != null) {
190                        path = authority + path;
191                    }
192                }
193                File f = new File(path);
194                entry.lastModified = f.lastModified();
195                try {
196                    entry.file = new ZipFile(f);
197                } catch (ZipException e) {
198                    error(new FileError("Failed to get cached ZipFile"
199                                        + " because " + e,
200                                        Pathname.makePathname(f)));
201                } catch (IOException e) {
202                    error(new FileError("Failed to get cached ZipFile"
203                                        + " because " + e,
204                                        Pathname.makePathname(f)));
205                }
206            } else {
207                entry = fetchURL(url, true);
208            }
209            zipCache.put(url, entry);
210        }
211        return entry.file;
212    }
213     
214    static private Entry fetchURL(URL url, boolean cached) {
215        Entry result = new Entry();
216        URL jarURL = null;
217        try {
218            jarURL = new URL("jar:" + url + "!/");
219        } catch (MalformedURLException e) {
220            error(new LispError("Failed to form a jar: URL from "
221                                + "'" + url + "'" 
222                                + " because " + e));
223        }
224        URLConnection connection = null;
225        try {
226            connection = jarURL.openConnection();
227        } catch (IOException e) {
228            error(new LispError("Failed to open "
229                                + "'" + jarURL + "'"
230                                + " with exception " 
231                                + e));
232        }
233        if (!(connection instanceof JarURLConnection)) {
234            error(new LispError("Could not get a URLConnection from " 
235                                + "'" + jarURL + "'"));
236        }
237        JarURLConnection jarURLConnection = (JarURLConnection) connection;
238        jarURLConnection.setUseCaches(cached);
239        try {
240            result.file = jarURLConnection.getJarFile();
241        } catch (IOException e) {
242            error(new LispError("Failed to fetch URL "
243                                 + "'" + jarURLConnection + "'"
244                                + " because " + e));
245        }
246        result.lastModified = jarURLConnection.getLastModified();
247        return result;
248    }
249
250    // ## remove-zip-cache-entry pathname => boolean
251    private static final Primitive REMOVE_ZIP_CACHE_ENTRY = new remove_zip_cache_entry();
252    private static class remove_zip_cache_entry extends Primitive { 
253        remove_zip_cache_entry() {
254            super("remove-zip-cache-entry", PACKAGE_SYS, true, "pathname");
255        }
256        @Override
257        public LispObject execute(LispObject arg) {
258            Pathname p = coerceToPathname(arg);
259            boolean result = ZipCache.remove(p);
260            return result ? T : NIL;
261        }
262    }
263     
264    synchronized public static boolean remove(URL url) {
265        Entry entry = zipCache.get(url);
266        if (entry != null) {
267            try {
268                entry.file.close();
269            } catch (IOException e) {}
270            zipCache.remove(entry);
271            return true;
272        }
273        return false;
274    }
275
276    synchronized public static boolean remove(Pathname p) {
277        URL url = Pathname.makeURL(p);
278        if (url == null) {
279            return false;
280        }
281        return ZipCache.remove(url);
282    }
283
284    synchronized public static boolean remove(File f) {
285        Pathname p = Pathname.makePathname(f);
286        return ZipCache.remove(p);
287    }
288}
Note: See TracBrowser for help on using the repository browser.