com.google.gwt.dev.resource.impl.ZipFileClassPathEntry.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.dev.resource.impl.ZipFileClassPathEntry.java

Source

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