blob: af62c83c6906b35f5d54f9c520b066addcfe6dac [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.dev.resource.impl;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.dev.util.collect.IdentityHashMap;
import com.google.gwt.dev.util.collect.IdentityHashSet;
import com.google.gwt.dev.util.collect.IdentityMaps;
import com.google.gwt.dev.util.collect.Sets;
import com.google.gwt.dev.util.msg.Message1String;
import org.apache.commons.collections.map.AbstractReferenceMap;
import org.apache.commons.collections.map.ReferenceIdentityMap;
import org.apache.commons.collections.map.ReferenceMap;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* A classpath entry that is a jar or zip file.
*/
public class ZipFileClassPathEntry extends ClassPathEntry {
/**
* Logger messages related to this class.
*/
private static class Messages {
static final Message1String BUILDING_INDEX = new Message1String(
TreeLogger.TRACE, "Indexing zip file: $0");
static final Message1String EXCLUDING_RESOURCE = new Message1String(
TreeLogger.DEBUG, "Excluding $0");
static final Message1String FINDING_INCLUDED_RESOURCES = new Message1String(
TreeLogger.DEBUG, "Searching for included resources in $0");
static final Message1String INCLUDING_RESOURCE = new Message1String(
TreeLogger.DEBUG, "Including $0");
static final Message1String READ_ZIP_ENTRY = new Message1String(
TreeLogger.DEBUG, "$0");
}
private static class ZipFileSnapshot {
private final Map<AbstractResource, PathPrefix> cachedAnswers;
private final int prefixSetSize;
ZipFileSnapshot(int prefixSetSize,
Map<AbstractResource, PathPrefix> cachedAnswers) {
this.prefixSetSize = prefixSetSize;
this.cachedAnswers = cachedAnswers;
}
}
/**
* Memory-sensitive cache of indexed {@link ZipFileClassPathEntry}s. URI of file is most probably
* not referenced anywhere else, so we use hard reference, and soft reference on
* {@link ZipFileClassPathEntry} allows its clearing in response to memory demand.
*/
@SuppressWarnings("unchecked")
private static final Map<String, ZipFileClassPathEntry> entryCache = new ReferenceMap(
AbstractReferenceMap.HARD, AbstractReferenceMap.SOFT);
/**
* @return the {@link ZipFileClassPathEntry} instance for given jar or zip
* file, may be shared with other users.
*/
public static synchronized ZipFileClassPathEntry get(File zipFile) throws IOException {
String location = zipFile.toURI().toString();
ZipFileClassPathEntry entry = entryCache.get(location);
if (entry == null) {
entry = new ZipFileClassPathEntry(zipFile);
entryCache.put(location, entry);
}
return entry;
}
private Set<ZipFileResource> allZipFileResources;
/**
* The lifetime of the {@link PathPrefixSet} pins the life time of the associated
* {@link ZipFileSnapshot}; this is because the {@link PathPrefixSet} is referenced from module,
* and {@link ZipFileSnapshot} is not referenced anywhere outside of {@link ZipFileClassPathEntry}
* . When the module dies, the {@link ZipFileSnapshot} needs to die also.
*/
@SuppressWarnings("unchecked")
private final Map<PathPrefixSet, ZipFileSnapshot> cachedSnapshots = new ReferenceIdentityMap(
AbstractReferenceMap.WEAK, AbstractReferenceMap.HARD, true);
private final long lastModified;
private final String location;
private final ZipFile zipFile;
private ZipFileClassPathEntry(File zipFile) throws IOException {
assert zipFile.isAbsolute();
this.lastModified = zipFile.lastModified();
this.zipFile = new ZipFile(zipFile);
this.location = zipFile.toURI().toString();
}
/**
* Indexes the zip file on-demand, and only once over the life of the process.
*/
@Override
public synchronized Map<AbstractResource, PathPrefix> findApplicableResources(
TreeLogger logger, PathPrefixSet pathPrefixSet) {
index(logger);
ZipFileSnapshot snapshot = cachedSnapshots.get(pathPrefixSet);
if (snapshot == null || snapshot.prefixSetSize != pathPrefixSet.getSize()) {
snapshot = new ZipFileSnapshot(pathPrefixSet.getSize(),
computeApplicableResources(logger, pathPrefixSet));
cachedSnapshots.put(pathPrefixSet, snapshot);
}
return snapshot.cachedAnswers;
}
@Override
public String getLocation() {
return location;
}
public ZipFile getZipFile() {
return zipFile;
}
public long lastModified() {
return lastModified;
}
synchronized void index(TreeLogger logger) {
// Never re-index.
if (allZipFileResources == null) {
allZipFileResources = buildIndex(logger);
}
}
private Set<ZipFileResource> buildIndex(TreeLogger logger) {
logger = Messages.BUILDING_INDEX.branch(logger, zipFile.getName(), null);
Set<ZipFileResource> results = new IdentityHashSet<ZipFileResource>();
Enumeration<? extends ZipEntry> e = zipFile.entries();
while (e.hasMoreElements()) {
ZipEntry zipEntry = e.nextElement();
if (zipEntry.isDirectory()) {
// Skip directories.
continue;
}
if (zipEntry.getName().startsWith("META-INF/")) {
// Skip META-INF since classloaders normally make this invisible.
continue;
}
ZipFileResource zipResource = new ZipFileResource(this,
zipEntry.getName());
results.add(zipResource);
Messages.READ_ZIP_ENTRY.log(logger, zipEntry.getName(), null);
}
return Sets.normalize(results);
}
private Map<AbstractResource, PathPrefix> computeApplicableResources(
TreeLogger logger, PathPrefixSet pathPrefixSet) {
logger = Messages.FINDING_INCLUDED_RESOURCES.branch(logger,
zipFile.getName(), null);
Map<AbstractResource, PathPrefix> results = new IdentityHashMap<AbstractResource, PathPrefix>();
for (ZipFileResource r : allZipFileResources) {
String path = r.getPath();
String[] pathParts = r.getPathParts();
PathPrefix prefix = null;
if ((prefix = pathPrefixSet.includesResource(path, pathParts)) != null) {
Messages.INCLUDING_RESOURCE.log(logger, path, null);
results.put(r, prefix);
} else {
Messages.EXCLUDING_RESOURCE.log(logger, path, null);
}
}
return IdentityMaps.normalize(results);
}
}