View Javadoc

1   /*
2    *  Copyright 2007,2008 Ueli Kurmann, bbv Software Services AG
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.ximtec.igesture.util;
18  
19  import java.io.File;
20  import java.io.FileNotFoundException;
21  import java.io.FileOutputStream;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.util.ArrayList;
25  import java.util.Enumeration;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  import java.util.zip.ZipEntry;
32  import java.util.zip.ZipFile;
33  import java.util.zip.ZipOutputStream;
34  
35  import org.apache.commons.io.FileUtils;
36  import org.apache.commons.io.IOUtils;
37  import org.sigtec.util.Constant;
38  
39  /**
40   * Simplifies the access to ZIP files.
41   * 
42   * Java does not allow the modification of ZIP files. Therefore, a temporary
43   * file is used to do write operations. After a close, the original file is
44   * replaced by the temporary one.
45   * 
46   * @author UeliKurmann
47   * @version 1.0
48   * @since igesture
49   */
50  public class ZipFS {
51  
52    public final static String SEPERATOR = "/";
53  
54    private ZipFile zipFile;
55    private File originalZipFile;
56    private File tmpZipFile;
57    private ZipOutputStream zipOutput;
58    private Map<String, Map<String, ZipEntry>> fileIndex;
59    private Set<String> toIgnore;
60  
61    public ZipFS(File file) throws IOException {
62      this.originalZipFile = file;
63  
64      if (this.originalZipFile.exists()) {
65        zipFile = new ZipFile(this.originalZipFile);
66        fileIndex = indexFile(zipFile);
67      } else {
68        fileIndex = new HashMap<String, Map<String, ZipEntry>>();
69      }
70  
71      tmpZipFile = File.createTempFile(Long.toHexString(System.currentTimeMillis()), ".tmp");
72      zipOutput = new ZipOutputStream(new FileOutputStream(tmpZipFile));
73  
74      toIgnore = new HashSet<String>();
75    }
76  
77    /**
78     * Indexes the ZIP file.
79     * 
80     * Iterates over each element and populates the fileIndex. The file index
81     * allows random access to the elements (files and folders) in the ZIP file.
82     * 
83     * @throws IOException
84     */
85    private synchronized Map<String, Map<String, ZipEntry>> indexFile(ZipFile file) throws IOException {
86      Map<String, Map<String, ZipEntry>> fileIndex = new HashMap<String, Map<String, ZipEntry>>();
87  
88      Enumeration<? extends ZipEntry> enumerator = file.entries();
89      fileIndex.put(Constant.EMPTY_STRING, new HashMap<String, ZipEntry>());
90      while (enumerator.hasMoreElements()) {
91        ZipEntry entry = enumerator.nextElement();
92        addEntryToIndex(fileIndex, entry);
93      }
94  
95      return fileIndex;
96    }
97  
98    /**
99     * Helper Method used by indexFile().
100    * 
101    * @param zipEntry
102    */
103   private void addEntryToIndex(Map<String, Map<String, ZipEntry>> index, ZipEntry zipEntry) {
104     String parentPath = getParentPath(zipEntry);
105     createDirectoryEntry(index, parentPath);
106     // FIXME WTF?
107     if (zipEntry.isDirectory()) {
108       index.get(parentPath).put(getName(zipEntry), zipEntry);
109     } else {
110       index.get(parentPath).put(getName(zipEntry), zipEntry);
111     }
112   }
113 
114   /**
115    * Creates recursively directory entries.
116    * 
117    * @param index
118    * @param path
119    */
120   private void createDirectoryEntry(Map<String, Map<String, ZipEntry>> index, String path) {
121     path = normalizePath(path);
122     if (path.contains(SEPERATOR)) {
123       createDirectoryEntry(index, path.substring(0, path.lastIndexOf(SEPERATOR)));
124     }
125     if (!index.containsKey(path)) {
126       index.put(path, new HashMap<String, ZipEntry>());
127       String parentPath = getParentPath(path);
128       index.get(parentPath).put(getName(path), new ZipEntry(path + SEPERATOR));
129     }
130   }
131 
132   /**
133    * Returns the name of a ZIP entry.
134    * 
135    * @param zipEntry
136    * @return
137    */
138   public static String getName(ZipEntry zipEntry) {
139     return getName(zipEntry.getName());
140   }
141 
142   /**
143    * Returns the substring after the last slash. If there is no slash, the
144    * entire string is returned.
145    * 
146    * @param path
147    * @return
148    */
149   public static String getName(String path) {
150     path = normalizePath(path);
151     int index = path.lastIndexOf(SEPERATOR) + 1;
152     if (index == 0) {
153       return path;
154     } else {
155       return path.substring(index);
156     }
157   }
158 
159   /**
160    * Normalizes the path.
161    * 
162    * A path must not start/end with a slash.
163    * 
164    * @param path
165    * @return
166    */
167   private static String normalizePath(String path) {
168     while (path.endsWith(SEPERATOR)) {
169       path = path.substring(0, path.length() - 1);
170     }
171 
172     while (path.startsWith(SEPERATOR)) {
173       path = path.substring(1);
174     }
175 
176     return path;
177   }
178 
179   /**
180    * Returns the parent path of an element. If no parent path is available, an
181    * EMPTY string is returned.
182    * 
183    * @see getParentPath(String path)
184    * @param zipEntry
185    * @return
186    */
187   public static String getParentPath(ZipEntry zipEntry) {
188     return getParentPath(zipEntry.getName());
189   }
190 
191   /**
192    * Returns the parent path. If no path is available, an EMPTY string is
193    * returned.
194    * 
195    * @param path
196    * @return
197    */
198   public static String getParentPath(String path) {
199     if (path == null) {
200       return null;
201     }
202 
203     path = normalizePath(path);
204 
205     int lastIndex = path.lastIndexOf(SEPERATOR);
206 
207     if (lastIndex == -1) {
208       return Constant.EMPTY_STRING;
209     } else {
210       return path.substring(0, lastIndex);
211     }
212   }
213 
214   /**
215    * Lists all elements in a path (files and folders)
216    * 
217    * @param path
218    * @return
219    */
220   public List<ZipEntry> listFiles(String path) {
221     path = normalizePath(path);
222     if (fileIndex.containsKey(path)) {
223       return new ArrayList<ZipEntry>(fileIndex.get(path).values());
224     }
225     return new ArrayList<ZipEntry>();
226   }
227 
228   /**
229    * Returns the input stream of a path.
230    * 
231    * @param path
232    * @return
233    * @throws IOException
234    */
235   public InputStream getInputStream(String path) throws IOException {
236     return zipFile.getInputStream(getEntry(path));
237   }
238 
239   /**
240    * Returns the input stream of a path.
241    * 
242    * @param entry
243    * @return
244    * @throws IOException
245    */
246   public InputStream getInputStream(ZipEntry entry) throws IOException {
247     return getInputStream(entry.getName());
248   }
249 
250   /**
251    * Returns the ZipEntry with the given path.
252    * 
253    * @param path
254    * @return
255    * @throws IOException
256    */
257   public ZipEntry getEntry(String path) throws IOException {
258     String parentPath = getParentPath(path);
259     String name = getName(path);
260     Map<String, ZipEntry> entries = fileIndex.get(parentPath);
261     ZipEntry entry = null;
262 
263     if (entries != null) {
264       entry = entries.get(name);
265     }
266 
267     if (entry == null || entry.isDirectory()) {
268       throw new FileNotFoundException(path);
269     }
270 
271     return entry;
272   }
273 
274   public ZipFS(String fileName) throws IOException {
275     this(new File(fileName));
276   }
277 
278   /**
279    * Marks an element to be ignored.
280    * 
281    * @param path
282    */
283   public void delete(String path) {
284     toIgnore.add(normalizePath(path));
285   }
286 
287   /**
288    * Stores a new element in the ZIP file with the given path.
289    * 
290    * @param path
291    * @param stream
292    * @throws IOException
293    */
294   public void store(String path, InputStream stream) throws IOException {
295     toIgnore.add(normalizePath(path));
296     ZipEntry entry = new ZipEntry(path);
297     zipOutput.putNextEntry(entry);
298     IOUtils.copy(stream, zipOutput);
299     zipOutput.closeEntry();
300   }
301 
302   /**
303    * Copies the elements to the output file.
304    */
305   private void copyEntries() {
306     if (zipFile != null) {
307       Enumeration<? extends ZipEntry> enumerator = zipFile.entries();
308       while (enumerator.hasMoreElements()) {
309         ZipEntry entry = enumerator.nextElement();
310         if (!entry.isDirectory() && !toIgnore.contains(normalizePath(entry.getName()))) {
311           ZipEntry originalEntry = new ZipEntry(entry.getName());
312           try {
313             zipOutput.putNextEntry(originalEntry);
314             IOUtils.copy(getInputStream(entry.getName()), zipOutput);
315             zipOutput.closeEntry();
316           } catch (IOException e) {
317             e.printStackTrace();
318           }
319         }
320       }
321     }
322   }
323 
324   /**
325    * Closes ZipFS
326    * 
327    * @throws IOException
328    */
329   public void close() throws IOException {
330 
331     if (zipFile != null) {
332       copyEntries();
333       zipOutput.close();
334       zipFile.close();
335       originalZipFile.delete();
336       if (!originalZipFile.exists()) {
337         FileUtils.moveFile(tmpZipFile, originalZipFile);
338       }
339     } else {
340       store("readme", IOUtils.toInputStream("see http://www.igesture.org"));
341       zipOutput.flush();
342       zipOutput.close();
343       if (!originalZipFile.exists()) {
344         FileUtils.moveFile(tmpZipFile, originalZipFile);
345       }
346     }
347 
348   }
349 }