001package arcommon;
002
003import java.io.BufferedInputStream;
004import java.io.BufferedOutputStream;
005import java.io.File;
006import java.io.FileInputStream;
007import java.io.FileOutputStream;
008import java.io.IOException;
009import java.nio.charset.Charset;
010import java.util.ArrayList;
011import java.util.Collection;
012import java.util.Iterator;
013import java.util.List;
014import java.util.zip.ZipEntry;
015import java.util.zip.ZipInputStream;
016import java.util.zip.ZipOutputStream;
017
018/**
019 * Util class for zip and unzip file methods.
020 */
021public class ZipUtil {
022    public static final char ZIP_DIRECTORY_SEPARATOR = '/';
023    
024    public static interface ZipStepInterface {
025        /**
026         * Called at the start of zipping a ZIP file. Only called once per ZIP file.
027         */
028        public void zippingZipFile(String zipFileName);
029        /**
030         * Converts the file name from absolute file name to ZIP file file name.
031         */
032        public String convertFileName(String fileName);
033        /**
034         * Called per file added to the ZIP file.
035         */
036        public void zipFile(ZipEntry zipEntry, String shortFileName, String fullFileName, ZipOutputStream zipOutputStream) throws IOException;
037        /**
038         * Called per directory added to the ZIP file.
039         */
040        public void zipDirectory(String shortFileName, String fullFileName, ZipOutputStream zipOutputStream);
041    }
042    
043    public static class ZipStep implements ZipStepInterface {
044        private String toPath;
045        private boolean echo;
046        
047        public ZipStep(String ptoPath, boolean pecho) {
048            toPath = ptoPath;
049            echo = pecho;
050        }
051        
052        public void zippingZipFile(String zipFileName) {
053            if (echo) {
054                System.out.println("Creating zip: \"" + zipFileName + "\"");
055            }
056        }
057        
058        public String convertFileName(String fileName) {
059            if (!fileName.startsWith(toPath)) {
060                throw new RuntimeException("Internal error: \"" + fileName + "\" \"" + toPath + "\"");
061            }
062
063            return fileName.substring(toPath.length());
064        }
065
066        public void zipFile(ZipEntry zipEntry, String shortFileName, String fileString, ZipOutputStream zipOutputStream) throws IOException {
067            String fullFileName = fileString.toString();
068            FileInputStream fileInputStream = new FileInputStream(fullFileName);
069            if (echo) {
070                System.out.println("  Adding: \"" + fullFileName + "\" as \"" + shortFileName + "\"");
071            }
072            
073            File file = new File(fullFileName);
074            zipEntry.setTime(file.lastModified());
075
076            byte[] buffer = new byte[1024];
077            int size;
078            // Read data to the end of the source file and write it to the zip output stream.
079            while ((size = fileInputStream.read(buffer)) > 0) {
080                zipOutputStream.write(buffer, 0, size);
081            }
082            fileInputStream.close();
083        }
084
085        public void zipDirectory(String shortFileName, String fullFileName, ZipOutputStream zipOutputStream) {
086            ;
087        }
088    }
089    
090    public static boolean zipFiles(String target, String path, boolean echo) {
091        List<String> files = FileUtil.findAllFilesInDirectory(path, true);
092        return zipFiles(target, files, path, echo);
093    }
094    
095    public static boolean zipFiles(String target, Collection<String> files, boolean echo) {
096        return zipFiles(target, files, "", echo);
097    }
098    
099    public static boolean zipFiles(String target, Collection<String> files, String toPath, boolean echo) {
100        return zipFiles(target, files, new ZipStep(toPath, echo));
101    }
102
103    public static boolean zipFiles(String target, Collection<String> files, ZipStepInterface zipStep) {
104        try {
105            zipStep.zippingZipFile(target);
106            FileUtil.mkdirs(FileUtil.calculatePath(target));
107            FileOutputStream fileOutputStream = new FileOutputStream(target);
108            ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
109            zipFiles(zipOutputStream, files, zipStep);
110            zipOutputStream.closeEntry(); // Finish zip process (Maybe)
111            zipOutputStream.finish(); // Finish zip process (Maybe)
112            fileOutputStream.close();
113        } catch (IOException e) {
114            throw new RuntimeException(e);
115        }
116        return true;
117    }
118    
119    public static void zipFiles(ZipOutputStream zipOutputStream, Collection<String> files, ZipStepInterface zipStep) throws IOException {
120        for (Iterator<String> iterator = files.iterator(); iterator.hasNext();) {
121            String fullFileName = iterator.next();
122
123            String shortFileName = zipStep.convertFileName(fullFileName);
124            shortFileName = shortFileName.replace(File.separatorChar, ZIP_DIRECTORY_SEPARATOR);
125            if (shortFileName.contains("..\\") || shortFileName.contains("../")) {
126                throw new RuntimeException("shortFileName.contains(\"..\\\") || shortFileName.contains(\"..//\") " + shortFileName);
127            }
128            if (shortFileName.startsWith("\\") || shortFileName.startsWith("/")) {
129                throw new RuntimeException("shortFileName.startsWith(\"\\\\\") || shortFileName.startsWith(\"/\")\") " + shortFileName);
130            }
131            ZipEntry zipEntry = new ZipEntry(shortFileName);
132/*            zipEntry.setCompressedSize(1000); // Don't work!!!!
133            zipEntry.setSize(1000); // Don't work!!!!
134            zipEntry.setMethod(ZipEntry.STORED); */
135            zipOutputStream.putNextEntry(zipEntry); // Put a new ZipEntry in the ZipOutputStream
136            
137            if (StringUtil.lastChar(shortFileName) == ZIP_DIRECTORY_SEPARATOR || shortFileName.endsWith(File.separator)) { // HACK
138                zipStep.zipDirectory(shortFileName, fullFileName, zipOutputStream);
139            } else {
140                zipStep.zipFile(zipEntry, shortFileName, fullFileName, zipOutputStream);
141            }
142            
143            zipOutputStream.flush();
144            zipOutputStream.closeEntry();
145        }
146        zipOutputStream.flush();
147    }
148    
149    public static interface UnzipStepInterface {
150        /**
151         * Called at the start of unzipping a ZIP file. Only called once per ZIP file.
152         */
153        public void unzipZipFile(String zipFileName);
154        /**
155         * Called per file contained in the ZIP file.
156         */
157        public void unzipFile(String fileName, ZipEntry zipEntry, ZipInputStream zipInputStream) throws IOException;
158        /**
159         * Called per directory contained in the ZIP file.
160         */
161        public void unzipDirectory(String fileName, ZipEntry zipEntry) throws IOException;
162    }
163    
164    public static ZipInputStream createZipInputStream(String zipFile, String charsetName) {
165        // See https://stackoverflow.com/questions/20013091/java-unzip-error-malformed
166        Charset charset;
167        if (charsetName != null) {
168            charset = Charset.forName(charsetName);
169        } else {
170            charset = null;
171        }
172        try {
173            BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(zipFile));
174            ZipInputStream zipInputStream;
175            if (charset != null) {
176                zipInputStream = new ZipInputStream(bufferedInputStream, charset);
177            } else {
178                zipInputStream = new ZipInputStream(bufferedInputStream);
179            }
180            return zipInputStream;
181        } catch (IOException e) {
182            throw new RuntimeException(e);
183        }
184    }
185    
186    public static class UnzipStep implements UnzipStepInterface {
187        private String toPath;
188        private boolean echo;
189        
190        public UnzipStep(String ptoPath, boolean pecho) {
191            if (ptoPath == null) {
192                throw new IllegalArgumentException("toPath is null");
193            }
194            toPath = ptoPath;
195            echo = pecho;
196            
197            if (toPath.length() > 0 && !toPath.endsWith("\\") && !toPath.endsWith("/")) {
198                toPath = toPath + "\\";
199            }
200        }
201
202        public void unzipZipFile(String zipFileName) {
203            if (echo) {
204                System.out.println("Unzipping zip: \"" + zipFileName + "\" to directory \"" + toPath + "\"");
205            }
206        }
207        
208        public void unzipFile(String fileName, ZipEntry zipEntry, ZipInputStream zipInputStream) throws IOException {
209            if (echo) {
210                System.out.println("  unzipping: " + fileName);
211            }
212            String fullFileName = toPath + fileName;
213            File parentFile = new File(fullFileName).getParentFile();
214            if (parentFile != null) {
215                FileUtil.mkdirs(parentFile);
216            }
217
218            byte[] buffer = new byte[1024];
219            BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(fullFileName), buffer.length);
220            int size;
221            // Read data to the end of the source file and write it to the zip output stream.
222            while ((size = zipInputStream.read(buffer, 0, buffer.length)) > 0) {
223                out.write(buffer, 0, size);
224            }
225
226            out.flush();
227            out.close();
228        }
229
230        public void unzipDirectory(String fileName, ZipEntry zipEntry) throws IOException {
231            if (echo) {
232                System.out.println("  unzipping directory: " + zipEntry.getName());
233            }
234        }
235    }
236
237    public static boolean unzipZipFile(String zipFile, String toPath, boolean echo) {
238        try {
239            unzipZipFile(zipFile, new UnzipStep(toPath, echo));
240        } catch (IOException e) {
241            throw new RuntimeException(e);
242        }
243        return true;
244    }
245    
246    public static void unzipZipFile(String zipFile, UnzipStepInterface unzipStep) throws IOException {
247        unzipZipFile(zipFile, unzipStep, null);
248    }
249    
250    public static void unzipZipFile(String zipFile, UnzipStepInterface unzipStep, String charsetName) throws IOException {
251        unzipStep.unzipZipFile(zipFile);
252        ZipInputStream zipInputStream = createZipInputStream(zipFile, charsetName);
253        unzipFiles(zipInputStream, unzipStep);
254        zipInputStream.close();
255    }
256    
257    public static void unzipFiles(ZipInputStream zipInputStream, UnzipStepInterface unzipStep) throws IOException {
258        ZipEntry zipEntry;
259        boolean fileFound = false;
260        while ((zipEntry = zipInputStream.getNextEntry()) != null) {
261            fileFound = true;
262            String fileName = zipEntry.getName().replace(ZIP_DIRECTORY_SEPARATOR, File.separatorChar);
263            if (fileName.contains("..\\") || fileName.contains("../")) {
264                throw new RuntimeException("fileName.contains(\"..\\\") || fileName.contains(\"..//\") " + fileName);
265            }
266            if (fileName.startsWith("\\") || fileName.startsWith("/")) {
267                throw new RuntimeException("fileName.startsWith(\"\\\") || fileName.startsWith(\"/\") " + fileName);
268            }
269            if (StringUtil.lastChar(fileName) == ZIP_DIRECTORY_SEPARATOR || fileName.endsWith(File.separator)) { // HACK
270                unzipStep.unzipDirectory(fileName, zipEntry);
271            } else {
272                unzipStep.unzipFile(fileName, zipEntry, zipInputStream);
273            }
274        }
275        if (!fileFound) {
276            throw new RuntimeException("Zip file was empty or ZipUtil.unzipFiles() was called directly with the zipInputStream parameter from UnzipStepInterface.unzipFile().");
277/*              The following code will produce the error above:
278                    public void unzipFile(String fileName, ZipEntry zipEntry, ZipInputStream zipInputStream) throws IOException {
279                        ZipUtil.unzipFiles(zipInputStream, this);
280                Fix:
281                    public void unzipFile(String fileName, ZipEntry zipEntry, ZipInputStream zipInputStream) throws IOException {
282                        ZipUtil.unzipFiles(new ZipInputStream(zipInputStream), this);
283 */
284        }
285    }
286    
287    public static List<String> listFilesInZipFile(String zipFile) {
288        return listFilesInZipFile(zipFile, null);
289    }
290    
291    public static List<String> listFilesInZipFile(String zipFile, String charsetName) {
292        try {
293            ZipInputStream zipInputStream = createZipInputStream(zipFile, charsetName);
294            List<String> result = listFilesInZipFile(zipInputStream);
295            zipInputStream.close();
296            return result;
297        } catch (IOException e) {
298            throw new RuntimeException(e);
299        }
300    }
301    
302    public static List<String> listFilesInZipFile(ZipInputStream zipInputStream) {
303        final List<String> result = new ArrayList<String>();
304        try {
305            unzipFiles(zipInputStream, new UnzipStepInterface() {
306                public void unzipZipFile(String zipFileName) {
307                    ;
308                }
309
310                public void unzipFile(String fileName, ZipEntry zipEntry, ZipInputStream zipInputStreamTemp) throws IOException {
311                    result.add(fileName);
312                }
313
314                public void unzipDirectory(String fileName, ZipEntry zipEntry) throws IOException {
315                    ;
316                }
317            });
318        } catch (IOException e) {
319            throw new RuntimeException(e);
320        }
321        return result;
322    }
323    
324    public static void deleteFileInZipFile(String zipFile, final String deleteFileName) {
325        try {
326            String tempFileName = zipFile + "-temp.zip";
327            FileOutputStream fileOutputStream = new FileOutputStream(tempFileName);
328            final ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
329            unzipZipFile(zipFile, new UnzipStepInterface() {
330                @Override
331                public void unzipZipFile(String zipFileName) {
332                    
333                }
334                
335                @Override
336                public void unzipFile(String fileName, ZipEntry zipEntry, ZipInputStream zipInputStream) throws IOException {
337                    if (fileName.equals(deleteFileName)) {
338                        System.out.println("Deleting: " + fileName);
339                        return ;
340                    }
341                    
342                    ZipEntry newZipEntry = new ZipEntry(fileName);
343                    zipOutputStream.putNextEntry(newZipEntry); // Put a new ZipEntry in the ZipOutputStream
344
345                    newZipEntry.setTime(zipEntry.getTime());
346
347                    byte[] buffer = new byte[1024];
348                    int size;
349                    // Read data to the end of the source file and write it to the zip output stream.
350                    while ((size = zipInputStream.read(buffer)) > 0) {
351                        zipOutputStream.write(buffer, 0, size);
352                    }
353                }
354                
355                @Override
356                public void unzipDirectory(String fileName, ZipEntry zipEntry) throws IOException {
357                    
358                }
359            });
360    
361            zipOutputStream.closeEntry(); // Finish zip process (Maybe)
362            zipOutputStream.finish(); // Finish zip process (Maybe)
363            fileOutputStream.close();
364            
365            FileUtil.delete(zipFile);
366            FileUtil.rename(tempFileName, zipFile);
367        } catch (IOException e) {
368            throw new RuntimeException(e);
369        }
370    }
371}