org.armedbear.lisp.ZipCache Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of abcl Show documentation
Show all versions of abcl Show documentation
Common Lisp implementation running on the JVM
/*
* ZipCache.java
*
* Copyright (C) 2010, 2014 Mark Evenson
* $Id$
*
* This program 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 2
* of the License, or (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce an
* executable, regardless of the license terms of these independent
* modules, and to copy and distribute the resulting executable under
* terms of your choice, provided that you also meet, for each linked
* independent module, the terms and conditions of the license of that
* module. An independent module is a module which is not derived from
* or based on this library. If you modify this library, you may extend
* this exception to your version of the library, but you are not
* obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
package org.armedbear.lisp;
import org.armedbear.lisp.util.HttpHead;
import static org.armedbear.lisp.Lisp.*;
import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
/**
* A cache for all zip/jar file accesses by URL that uses the last
* modified time of the cached resource.
*
* This implementation is synchronized on accesses via get().
* Usage without multiple threads recompiling code that is then
* re-loaded should be fine.
*
* If you run into problems with caching, use
* (SYS::DISABLE-ZIP-CACHE). Once disabled, the caching cannot be
* re-enabled.
*
*/
public class ZipCache {
// To make this thread safe, we should return a proxy for ZipFile
// that keeps track of the number of outstanding references handed
// out, not allowing ZipFile.close() to succeed until that count
// has been reduced to 1 or the finalizer is executing.
// Unfortunately the relatively simple strategy of extending
// ZipFile via a CachedZipFile does not work because there is not
// a null arg constructor for ZipFile.
static class Entry {
long lastModified;
ZipFile file;
}
static boolean cacheEnabled = true;
private final static Primitive DISABLE_ZIP_CACHE = new disable_zip_cache();
final static class disable_zip_cache extends Primitive {
disable_zip_cache() {
super("disable-zip-cache", PACKAGE_SYS, true, "",
"Disable all caching of ABCL FASLs and ZIPs.");
}
@Override
public LispObject execute() {
ZipCache.disable();
return T;
}
}
static public synchronized void disable() {
cacheEnabled = false;
zipCache.clear();
}
static HashMap zipCache = new HashMap();
synchronized public static ZipFile get(Pathname p) {
return get(Pathname.makeURL(p));
}
static final SimpleDateFormat ASCTIME
= new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", Locale.US);
static final SimpleDateFormat RFC_1036
= new SimpleDateFormat("EEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US);
static final SimpleDateFormat RFC_1123
= new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
synchronized public static ZipFile get(final URL url) {
if (!cacheEnabled) {
if (url.getProtocol().equals("file")) {
File f = new File(url.getPath());
try {
return new ZipFile(f);
} catch (ZipException e) {
error(new FileError("Failed to construct ZipFile"
+ " because " + e,
Pathname.makePathname(f)));
} catch (IOException e) {
error(new FileError("Failed to contruct ZipFile"
+ " because " + e,
Pathname.makePathname(f)));
}
} else {
Entry e = fetchURL(url, false);
return e.file;
}
}
Entry entry = zipCache.get(url);
// Check that the cache entry still accesses a valid ZipFile
if (entry != null) {
// Simplest way to call private ZipFile.ensureOpen()
try {
int size = entry.file.size();
} catch (IllegalStateException e) {
zipCache.remove(url);
entry = null;
}
}
if (entry != null) {
if (url.getProtocol().equals("file")) {
File f = new File(url.getPath());
long current = f.lastModified();
if (current > entry.lastModified) {
try {
entry.file = new ZipFile(f);
entry.lastModified = current;
} catch (IOException e) {
Debug.trace(e.toString()); // XXX
}
}
} else if (url.getProtocol().equals("http")) {
// Unfortunately, the Apple JDK under OS X doesn't do
// HTTP HEAD requests, instead refetching the entire
// resource, and I assume this is the case in all
// Sun-derived JVMs. So, we use a custom HEAD
// implementation only looking for Last-Modified
// headers, which if we don't find, we give up and
// refetch the resource.
String dateString = null;
try {
dateString = HttpHead.get(url, "Last-Modified");
} catch (IOException ex) {
Debug.trace(ex);
}
Date date = null;
ParsePosition pos = new ParsePosition(0);
if (dateString != null) {
date = RFC_1123.parse(dateString, pos);
if (date == null) {
date = RFC_1036.parse(dateString, pos);
if (date == null)
date = ASCTIME.parse(dateString, pos);
}
}
if (date == null || date.getTime() > entry.lastModified) {
entry = fetchURL(url, false);
zipCache.put(url, entry);
}
if (date == null) {
if (dateString == null)
Debug.trace("Failed to retrieve request header: "
+ url.toString());
else
Debug.trace("Failed to parse Last-Modified date: " +
dateString);
}
} else {
entry = fetchURL(url, false);
zipCache.put(url, entry);
}
} else {
if (url.getProtocol().equals("file")) {
entry = new Entry();
String path = url.getPath();
if (Utilities.isPlatformWindows) {
String authority = url.getAuthority();
if (authority != null) {
path = authority + path;
}
}
File f = new File(path);
entry.lastModified = f.lastModified();
try {
entry.file = new ZipFile(f);
} catch (ZipException e) {
error(new FileError("Failed to get cached ZipFile"
+ " because " + e,
Pathname.makePathname(f)));
} catch (IOException e) {
error(new FileError("Failed to get cached ZipFile"
+ " because " + e,
Pathname.makePathname(f)));
}
} else {
entry = fetchURL(url, true);
}
zipCache.put(url, entry);
}
return entry.file;
}
static private Entry fetchURL(URL url, boolean cached) {
Entry result = new Entry();
URL jarURL = null;
try {
jarURL = new URL("jar:" + url + "!/");
} catch (MalformedURLException e) {
error(new LispError("Failed to form a jar: URL from "
+ "'" + url + "'"
+ " because " + e));
}
URLConnection connection = null;
try {
connection = jarURL.openConnection();
} catch (IOException e) {
error(new LispError("Failed to open "
+ "'" + jarURL + "'"
+ " with exception "
+ e));
}
if (!(connection instanceof JarURLConnection)) {
error(new LispError("Could not get a URLConnection from "
+ "'" + jarURL + "'"));
}
JarURLConnection jarURLConnection = (JarURLConnection) connection;
jarURLConnection.setUseCaches(cached);
try {
result.file = jarURLConnection.getJarFile();
} catch (IOException e) {
error(new LispError("Failed to fetch URL "
+ "'" + jarURLConnection + "'"
+ " because " + e));
}
result.lastModified = jarURLConnection.getLastModified();
return result;
}
// ## remove-zip-cache-entry pathname => boolean
private static final Primitive REMOVE_ZIP_CACHE_ENTRY = new remove_zip_cache_entry();
private static class remove_zip_cache_entry extends Primitive {
remove_zip_cache_entry() {
super("remove-zip-cache-entry", PACKAGE_SYS, true, "pathname");
}
@Override
public LispObject execute(LispObject arg) {
Pathname p = coerceToPathname(arg);
boolean result = ZipCache.remove(p);
return result ? T : NIL;
}
}
synchronized public static boolean remove(URL url) {
Entry entry = zipCache.get(url);
if (entry != null) {
try {
entry.file.close();
} catch (IOException e) {}
zipCache.remove(entry);
return true;
}
return false;
}
synchronized public static boolean remove(Pathname p) {
URL url = Pathname.makeURL(p);
if (url == null) {
return false;
}
return ZipCache.remove(url);
}
synchronized public static boolean remove(File f) {
Pathname p = Pathname.makePathname(f);
return ZipCache.remove(p);
}
}