| /* |
| * 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); |
| } |
| } |