001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.vfs2.provider.zip;
018
019import java.io.File;
020import java.io.IOException;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Enumeration;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.zip.ZipEntry;
028import java.util.zip.ZipFile;
029
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.apache.commons.vfs2.Capability;
033import org.apache.commons.vfs2.FileName;
034import org.apache.commons.vfs2.FileObject;
035import org.apache.commons.vfs2.FileSystemException;
036import org.apache.commons.vfs2.FileSystemOptions;
037import org.apache.commons.vfs2.Selectors;
038import org.apache.commons.vfs2.VfsLog;
039import org.apache.commons.vfs2.provider.AbstractFileName;
040import org.apache.commons.vfs2.provider.AbstractFileSystem;
041import org.apache.commons.vfs2.provider.UriParser;
042
043/**
044 * A read-only file system for ZIP and JAR files.
045 */
046public class ZipFileSystem extends AbstractFileSystem {
047    private static final Log LOG = LogFactory.getLog(ZipFileSystem.class);
048
049    private final File file;
050    private ZipFile zipFile;
051
052    /**
053     * Cache doesn't need to be synchronized since it is read-only.
054     */
055    private final Map<FileName, FileObject> cache = new HashMap<>();
056
057    public ZipFileSystem(final AbstractFileName rootName, final FileObject parentLayer,
058            final FileSystemOptions fileSystemOptions) throws FileSystemException {
059        super(rootName, parentLayer, fileSystemOptions);
060
061        // Make a local copy of the file
062        file = parentLayer.getFileSystem().replicateFile(parentLayer, Selectors.SELECT_SELF);
063
064        // Open the Zip file
065        if (!file.exists()) {
066            // Don't need to do anything
067            zipFile = null;
068            return;
069        }
070
071        // zipFile = createZipFile(this.file);
072    }
073
074    @Override
075    public void init() throws FileSystemException {
076        super.init();
077
078        try {
079            // Build the index
080            final List<ZipFileObject> strongRef = new ArrayList<>(getZipFile().size());
081            final Enumeration<? extends ZipEntry> entries = getZipFile().entries();
082            while (entries.hasMoreElements()) {
083                final ZipEntry entry = entries.nextElement();
084                final AbstractFileName name = (AbstractFileName) getFileSystemManager().resolveName(getRootName(),
085                        UriParser.encode(entry.getName()));
086
087                // Create the file
088                ZipFileObject fileObj;
089                if (entry.isDirectory() && getFileFromCache(name) != null) {
090                    fileObj = (ZipFileObject) getFileFromCache(name);
091                    fileObj.setZipEntry(entry);
092                    continue;
093                }
094
095                fileObj = createZipFileObject(name, entry);
096                putFileToCache(fileObj);
097                strongRef.add(fileObj);
098                fileObj.holdObject(strongRef);
099
100                // Make sure all ancestors exist
101                // TODO - create these on demand
102                ZipFileObject parent;
103                for (AbstractFileName parentName = (AbstractFileName) name
104                        .getParent(); parentName != null; fileObj = parent, parentName = (AbstractFileName) parentName
105                                .getParent()) {
106                    // Locate the parent
107                    parent = (ZipFileObject) getFileFromCache(parentName);
108                    if (parent == null) {
109                        parent = createZipFileObject(parentName, null);
110                        putFileToCache(parent);
111                        strongRef.add(parent);
112                        parent.holdObject(strongRef);
113                    }
114
115                    // Attach child to parent
116                    parent.attachChild(fileObj.getName());
117                }
118            }
119        } finally {
120            closeCommunicationLink();
121        }
122    }
123
124    protected ZipFile getZipFile() throws FileSystemException {
125        if (zipFile == null && this.file.exists()) {
126            this.zipFile = createZipFile(this.file);
127        }
128
129        return zipFile;
130    }
131
132    protected ZipFileObject createZipFileObject(final AbstractFileName name, final ZipEntry entry)
133            throws FileSystemException {
134        return new ZipFileObject(name, entry, this, true);
135    }
136
137    protected ZipFile createZipFile(final File file) throws FileSystemException {
138        try {
139            return new ZipFile(file);
140        } catch (final IOException ioe) {
141            throw new FileSystemException("vfs.provider.zip/open-zip-file.error", file, ioe);
142        }
143    }
144
145    @Override
146    protected void doCloseCommunicationLink() {
147        // Release the zip file
148        try {
149            if (zipFile != null) {
150                zipFile.close();
151                zipFile = null;
152            }
153        } catch (final IOException e) {
154            // getLogger().warn("vfs.provider.zip/close-zip-file.error :" + file, e);
155            VfsLog.warn(getLogger(), LOG, "vfs.provider.zip/close-zip-file.error :" + file, e);
156        }
157    }
158
159    /**
160     * Returns the capabilities of this file system.
161     */
162    @Override
163    protected void addCapabilities(final Collection<Capability> caps) {
164        caps.addAll(ZipFileProvider.capabilities);
165    }
166
167    /**
168     * Creates a file object.
169     */
170    @Override
171    protected FileObject createFile(final AbstractFileName name) throws FileSystemException {
172        // This is only called for files which do not exist in the Zip file
173        return new ZipFileObject(name, null, this, false);
174    }
175
176    /**
177     * Adds a file object to the cache.
178     */
179    @Override
180    protected void putFileToCache(final FileObject file) {
181        cache.put(file.getName(), file);
182    }
183
184    /**
185     * Returns a cached file.
186     */
187    @Override
188    protected FileObject getFileFromCache(final FileName name) {
189        return cache.get(name);
190    }
191
192    /**
193     * remove a cached file.
194     */
195    @Override
196    protected void removeFileFromCache(final FileName name) {
197        cache.remove(name);
198    }
199
200    @Override
201    public String toString() {
202        return super.toString() + " for " + file;
203    }
204
205    /**
206     * will be called after all file-objects closed their streams. protected void notifyAllStreamsClosed() {
207     * closeCommunicationLink(); }
208     */
209}