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

Last change on this file was 13055, checked in by Mark Evenson, 14 years ago

Fix comment typo.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 11.3 KB
Line 
1/*
2 * ZipCache.java
3 *
4 * Copyright (C) 2010 Mark Evenson
5 * $Id: ZipCache.java 13055 2010-11-27 11:03:12Z 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.ParsePosition;
45import java.text.SimpleDateFormat;
46import java.util.Date;
47import java.util.HashMap;
48import java.util.Locale;
49import java.util.zip.ZipException;
50import java.util.zip.ZipFile;
51
52/**
53 * A cache for all zip/jar file accesses by URL that uses the last
54 * modified time of the cached resource.
55 *
56 * This implementation is NOT thread safe, although usage without
57 * multiple threads recompiling code that is then re-loaded should be
58 * fine.
59 *
60 * If you run into problems with caching, use
61 * (SYS::DISABLE-ZIP-CACHE).  Once disabled, the caching cannot be
62 * re-enabled.
63 *
64 */ 
65public class ZipCache {
66
67    // To make this thread safe, we should return a proxy for ZipFile
68    // that keeps track of the number of outstanding references handed
69    // out, not allowing ZipFile.close() to succeed until that count
70    // has been reduced to 1 or the finalizer is executing.
71    // Unfortunately the relatively simple strategy of extending
72    // ZipFile via a CachedZipFile does not work because there is not
73    // a null arg constructor for ZipFile.
74    static class Entry {
75        long lastModified;
76        ZipFile file;
77    }
78
79    static boolean cacheEnabled = true;
80
81    private final static Primitive DISABLE_ZIP_CACHE = new disable_zip_cache();
82    final static class disable_zip_cache extends Primitive {
83        disable_zip_cache() {
84            super("disable-zip-cache", PACKAGE_SYS, true, "",
85                  "Disable all caching of ABCL FASLs and ZIPs.");
86        }
87        @Override
88        public LispObject execute() {
89            ZipCache.disable();
90            return T;
91        }
92    }
93
94    static public synchronized void disable() {
95        cacheEnabled = false;
96        zipCache.clear(); 
97    }
98
99    static HashMap<URL, Entry> zipCache = new HashMap<URL, Entry>();
100
101    synchronized public static ZipFile get(Pathname p) {
102        return get(Pathname.makeURL(p));
103    }
104
105    static final SimpleDateFormat ASCTIME
106        = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", Locale.US);
107    static final SimpleDateFormat RFC_1036
108        = new SimpleDateFormat("EEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US);
109    static final SimpleDateFormat RFC_1123
110        = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
111
112
113    synchronized public static ZipFile get(final URL url) {
114        if (!cacheEnabled) {
115            if (url.getProtocol().equals("file")) {
116                File f = new File(url.getPath());
117                try { 
118                    return new ZipFile(f);
119                } catch (ZipException e) {
120                    error(new FileError("Failed to construct ZipFile"
121                                        + " because " + e,
122                                        Pathname.makePathname(f)));
123                } catch (IOException e) {
124                    error(new FileError("Failed to contruct ZipFile"
125                                        + " because " + e,
126                                        Pathname.makePathname(f)));
127                }
128            } else {
129                Entry e = fetchURL(url, false);
130                return e.file;
131            }
132        }               
133
134        Entry entry = zipCache.get(url);
135
136        // Check that the cache entry still accesses a valid ZipFile
137        if (entry != null) {
138            // Simplest way to call private ZipFile.ensureOpen()
139            try {
140                int size = entry.file.size(); 
141            } catch (IllegalStateException e) {
142                zipCache.remove(url);
143                entry = null;
144            }
145        }
146
147        if (entry != null) {
148            if (url.getProtocol().equals("file")) {
149                File f = new File(url.getPath());
150                long current = f.lastModified();
151                if (current > entry.lastModified) {
152                    try {
153                        entry.file = new ZipFile(f);
154                        entry.lastModified = current;
155                    } catch (IOException e) {
156                        Debug.trace(e.toString()); // XXX
157                    }
158                }
159            } else if (url.getProtocol().equals("http")) {
160                // Unfortunately, the Apple JDK under OS X doesn't do
161                // HTTP HEAD requests, instead refetching the entire
162                // resource, and I assume this is the case in all
163                // Sun-derived JVMs.  So, we use a custom HEAD
164                // implementation only looking for Last-Modified
165                // headers, which if we don't find, we give up and
166                // refetch the resource.
167                String dateString = HttpHead.get(url, "Last-Modified");
168                Date date = null;
169                ParsePosition pos = new ParsePosition(0);
170
171                if (dateString != null) {
172                    date = RFC_1123.parse(dateString, pos);
173                    if (date == null) {
174                        date = RFC_1036.parse(dateString, pos);
175                        if (date == null)
176                            date = ASCTIME.parse(dateString, pos);
177                    }
178                }
179
180                if (date == null || date.getTime() > entry.lastModified) {
181                    entry = fetchURL(url, false);
182                    zipCache.put(url, entry);
183                }
184                if (date == null) {
185                    if (dateString == null)
186                        Debug.trace("Failed to retrieve request header: "
187                                    + url.toString());
188                    else
189                        Debug.trace("Failed to parse Last-Modified date: " +
190                                    dateString);
191                }
192
193           } else {
194                entry = fetchURL(url, false);
195                zipCache.put(url, entry);
196            }
197        } else {
198            if (url.getProtocol().equals("file")) {
199                entry = new Entry();
200                String path = url.getPath();
201
202                if (Utilities.isPlatformWindows) {
203                    String authority = url.getAuthority();
204                    if (authority != null) {
205                        path = authority + path;
206                    }
207                }
208                File f = new File(path);
209                entry.lastModified = f.lastModified();
210                try {
211                    entry.file = new ZipFile(f);
212                } catch (ZipException e) {
213                    error(new FileError("Failed to get cached ZipFile"
214                                        + " because " + e,
215                                        Pathname.makePathname(f)));
216                } catch (IOException e) {
217                    error(new FileError("Failed to get cached ZipFile"
218                                        + " because " + e,
219                                        Pathname.makePathname(f)));
220                }
221            } else {
222                entry = fetchURL(url, true);
223            }
224            zipCache.put(url, entry);
225        }
226        return entry.file;
227    }
228     
229    static private Entry fetchURL(URL url, boolean cached) {
230        Entry result = new Entry();
231        URL jarURL = null;
232        try {
233            jarURL = new URL("jar:" + url + "!/");
234        } catch (MalformedURLException e) {
235            error(new LispError("Failed to form a jar: URL from "
236                                + "'" + url + "'" 
237                                + " because " + e));
238        }
239        URLConnection connection = null;
240        try {
241            connection = jarURL.openConnection();
242        } catch (IOException e) {
243            error(new LispError("Failed to open "
244                                + "'" + jarURL + "'"
245                                + " with exception " 
246                                + e));
247        }
248        if (!(connection instanceof JarURLConnection)) {
249            error(new LispError("Could not get a URLConnection from " 
250                                + "'" + jarURL + "'"));
251        }
252        JarURLConnection jarURLConnection = (JarURLConnection) connection;
253        jarURLConnection.setUseCaches(cached);
254        try {
255            result.file = jarURLConnection.getJarFile();
256        } catch (IOException e) {
257            error(new LispError("Failed to fetch URL "
258                                 + "'" + jarURLConnection + "'"
259                                + " because " + e));
260        }
261        result.lastModified = jarURLConnection.getLastModified();
262        return result;
263    }
264
265    // ## remove-zip-cache-entry pathname => boolean
266    private static final Primitive REMOVE_ZIP_CACHE_ENTRY = new remove_zip_cache_entry();
267    private static class remove_zip_cache_entry extends Primitive { 
268        remove_zip_cache_entry() {
269            super("remove-zip-cache-entry", PACKAGE_SYS, true, "pathname");
270        }
271        @Override
272        public LispObject execute(LispObject arg) {
273            Pathname p = coerceToPathname(arg);
274            boolean result = ZipCache.remove(p);
275            return result ? T : NIL;
276        }
277    }
278     
279    synchronized public static boolean remove(URL url) {
280        Entry entry = zipCache.get(url);
281        if (entry != null) {
282            try {
283                entry.file.close();
284            } catch (IOException e) {}
285            zipCache.remove(entry);
286            return true;
287        }
288        return false;
289    }
290
291    synchronized public static boolean remove(Pathname p) {
292        URL url = Pathname.makeURL(p);
293        if (url == null) {
294            return false;
295        }
296        return ZipCache.remove(url);
297    }
298
299    synchronized public static boolean remove(File f) {
300        Pathname p = Pathname.makePathname(f);
301        return ZipCache.remove(p);
302    }
303}
Note: See TracBrowser for help on using the repository browser.