View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.provider.zip;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.nio.charset.Charset;
22  import java.nio.charset.StandardCharsets;
23  import java.util.Collection;
24  import java.util.Enumeration;
25  import java.util.HashMap;
26  import java.util.Map;
27  import java.util.zip.ZipEntry;
28  import java.util.zip.ZipFile;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.commons.vfs2.Capability;
33  import org.apache.commons.vfs2.FileName;
34  import org.apache.commons.vfs2.FileObject;
35  import org.apache.commons.vfs2.FileSystemException;
36  import org.apache.commons.vfs2.FileSystemOptions;
37  import org.apache.commons.vfs2.Selectors;
38  import org.apache.commons.vfs2.VfsLog;
39  import org.apache.commons.vfs2.provider.AbstractFileName;
40  import org.apache.commons.vfs2.provider.AbstractFileSystem;
41  import org.apache.commons.vfs2.provider.UriParser;
42  
43  /**
44   * A read-only file system for ZIP and JAR files.
45   */
46  public class ZipFileSystem extends AbstractFileSystem {
47  
48      private static final char[] ENC = {'!'};
49  
50      private static final Log LOG = LogFactory.getLog(ZipFileSystem.class);
51  
52      private final File file;
53      private final Charset charset;
54      private ZipFile zipFile;
55  
56      /**
57       * Cache doesn't need to be synchronized since it is read-only.
58       */
59      private final Map<FileName, FileObject> cache = new HashMap<>();
60  
61      /**
62       * Constructs a new instance.
63       *
64       * @param rootFileName The root file name of this file system.
65       * @param parentLayer The parent layer of this file system.
66       * @param fileSystemOptions Options to build this file system.
67       * @throws FileSystemException If the parent layer does not exist, or on error replicating the file.
68       */
69      public ZipFileSystem(final AbstractFileName rootFileName, final FileObject parentLayer, final FileSystemOptions fileSystemOptions)
70          throws FileSystemException {
71          super(rootFileName, parentLayer, fileSystemOptions);
72  
73          // Make a local copy of the file
74          file = parentLayer.getFileSystem().replicateFile(parentLayer, Selectors.SELECT_SELF);
75          charset = ZipFileSystemConfigBuilder.getInstance().getCharset(fileSystemOptions);
76  
77          // Open the Zip file
78          if (!file.exists()) {
79              // Don't need to do anything
80              zipFile = null;
81          }
82      }
83  
84      /**
85       * Returns the capabilities of this file system.
86       */
87      @Override
88      protected void addCapabilities(final Collection<Capability> caps) {
89          caps.addAll(ZipFileProvider.capabilities);
90      }
91  
92      /**
93       * Creates a file object.
94       */
95      @Override
96      protected FileObject createFile(final AbstractFileName name) throws FileSystemException {
97          // This is only called for files which do not exist in the Zip file
98          return new ZipFileObject(name, null, this, false);
99      }
100 
101     /**
102      * Creates a Zip file.
103      *
104      * @param file the underlying file.
105      * @return a Zip file.
106      * @throws FileSystemException if a file system error occurs.
107      */
108     protected ZipFile createZipFile(final File file) throws FileSystemException {
109         try {
110             return new ZipFile(file, charset);
111         } catch (final IOException ioe) {
112             throw new FileSystemException("vfs.provider.zip/open-zip-file.error", file, ioe);
113         }
114     }
115 
116     /**
117      * Creates a new Zip file object.
118      *
119      * @param fileName the underlying file.
120      * @param entry the Zip entry.
121      * @return a new ZipFileObject.
122      * @throws FileSystemException if a file system error occurs.
123      */
124     protected ZipFileObject createZipFileObject(final AbstractFileName fileName, final ZipEntry entry) throws FileSystemException {
125         return new ZipFileObject(fileName, entry, this, true);
126     }
127 
128     @Override
129     protected void doCloseCommunicationLink() {
130         // Release the zip file
131         try {
132             if (zipFile != null) {
133                 zipFile.close();
134                 zipFile = null;
135             }
136         } catch (final IOException e) {
137             // getLogger().warn("vfs.provider.zip/close-zip-file.error :" + file, e);
138             VfsLog.warn(getLogger(), LOG, "vfs.provider.zip/close-zip-file.error :" + file, e);
139         }
140     }
141 
142     /**
143      * Gets the Charset, defaults to {@link StandardCharsets#UTF_8}, the value used in {@link ZipFile}.
144      *
145      * @return the Charset.
146      */
147     protected Charset getCharset() {
148         return charset;
149     }
150 
151     /**
152      * Gets a cached file.
153      */
154     @Override
155     protected FileObject getFileFromCache(final FileName name) {
156         return cache.get(name);
157     }
158 
159     /**
160      * Gets the zip file.
161      *
162      * @return the zip file.
163      * @throws FileSystemException if a file system error occurs.
164      */
165     protected ZipFile getZipFile() throws FileSystemException {
166         if (zipFile == null && file.exists()) {
167             zipFile = createZipFile(file);
168         }
169         return zipFile;
170     }
171 
172     @Override
173     public void init() throws FileSystemException {
174         super.init();
175 
176         try {
177             // Build the index
178             final Enumeration<? extends ZipEntry> entries = getZipFile().entries();
179             while (entries.hasMoreElements()) {
180                 final ZipEntry entry = entries.nextElement();
181                 final AbstractFileName name = (AbstractFileName) getFileSystemManager().resolveName(getRootName(),
182                         UriParser.encode(entry.getName(), ENC));
183 
184                 // Create the file
185                 ZipFileObject fileObj;
186                 if (entry.isDirectory() && getFileFromCache(name) != null) {
187                     fileObj = (ZipFileObject) getFileFromCache(name);
188                     fileObj.setZipEntry(entry);
189                     continue;
190                 }
191 
192                 fileObj = createZipFileObject(name, entry);
193                 putFileToCache(fileObj);
194 
195                 // Make sure all ancestors exist
196                 // TODO - create these on demand
197                 ZipFileObject parent;
198                 for (AbstractFileName parentName = (AbstractFileName) name
199                         .getParent(); parentName != null; fileObj = parent, parentName = (AbstractFileName) parentName
200                                 .getParent()) {
201                     // Locate the parent
202                     parent = (ZipFileObject) getFileFromCache(parentName);
203                     if (parent == null) {
204                         parent = createZipFileObject(parentName, null);
205                         putFileToCache(parent);
206                     }
207 
208                     // Attach child to parent
209                     parent.attachChild(fileObj.getName());
210                 }
211             }
212         } finally {
213             closeCommunicationLink();
214         }
215     }
216 
217     /**
218      * Adds a file object to the cache.
219      */
220     @Override
221     protected void putFileToCache(final FileObject file) {
222         cache.put(file.getName(), file);
223     }
224 
225     /**
226      * remove a cached file.
227      */
228     @Override
229     protected void removeFileFromCache(final FileName name) {
230         cache.remove(name);
231     }
232 
233     @Override
234     public String toString() {
235         return super.toString() + " for " + file;
236     }
237 
238     /*
239       will be called after all file-objects closed their streams. protected void notifyAllStreamsClosed() {
240       closeCommunicationLink(); }
241      */
242 }