001    /*
002     * LAPIS lightweight structured text processing system
003     *
004     * Copyright (C) 1998-2002 Carnegie Mellon University,
005     * Copyright (C) 2003 Massachusetts Institute of Technology.
006     * All rights reserved.
007     *
008     * This library is free software; you can redistribute it
009     * and/or modify it under the terms of the GNU General
010     * Public License as published by the Free Software
011     * Foundation, version 2.
012     *
013     * LAPIS homepage: http://graphics.lcs.mit.edu/lapis/
014     */
015    
016    package lapisx.io;
017    
018    import java.io.*;
019    import java.util.*;
020    import java.util.zip.*;
021    
022    public class ZipArchive implements Serializable {
023    
024        private static final int BUFFER_SIZE = 512;
025    
026        byte[] zipdata;
027            // ZIP archive
028    
029        Vector names = new Vector ();     
030            // files contained in the ZIP archive
031    
032        // Used only for serialization
033        static final long serialVersionUID = -9185404800237564786L;
034    
035        public ZipArchive (String[] filenames) throws IOException {
036            byte[] buffer = new byte[BUFFER_SIZE];
037            int n;
038    
039            ByteArrayOutputStream bout = new ByteArrayOutputStream ();
040            ZipOutputStream out = new ZipOutputStream (bout);
041            out.setMethod (ZipOutputStream.STORED);
042            
043            try {
044                for (int i = 0; i < filenames.length; ++i) {
045                    String filename = filenames[i];
046                    if (filename.endsWith (File.separator)
047                        && !filename.equals (File.separator))
048                        filename = filename.substring (0, filename.length()-1);
049                        
050                    addFile (out, new File (filename), "", buffer);
051                }
052                out.finish ();
053            } finally {
054                out.close ();
055                zipdata = bout.toByteArray ();
056            }
057        }
058    
059        private void addFile (ZipOutputStream out, File f, String prefix, byte[] buffer) throws IOException {
060            String name = prefix + f.getName ();
061            
062            if (f.isDirectory ()) {
063                // Mark it as a directory for the ZipFile
064                name += "/";
065                
066                // Create an entry for the directory
067                ZipEntry entry = new ZipEntry (name);
068                entry.setSize (0);
069                entry.setCrc (0);
070                
071                // Add the entry to the ZIP archive
072                out.putNextEntry (entry);            
073                names.addElement (name);
074                
075                // Add each file in directory recursively
076                String[] subfiles = f.list ();
077                for (int i = 0; i < subfiles.length; ++i)
078                    addFile (out, new File (f, subfiles[i]), name, buffer);
079            }
080            else {
081                // Create an entry for this file
082                ZipEntry entry = new ZipEntry (name);
083                entry.setSize (f.length ());
084    
085                // compute the file's CRC
086                Checksum crc = new CRC32 ();
087                CheckedInputStream checkedInput = new CheckedInputStream (new FileInputStream (f), crc);
088                while (checkedInput.read (buffer) != -1)
089                    ;
090                checkedInput.close ();
091                entry.setCrc (crc.getValue ());
092    
093                // Add the entry to the ZIP archive
094                out.putNextEntry (entry);
095                names.addElement (name);
096                
097                // Copy the file's data into the ZIP archive
098                InputStream in = new FileInputStream (f);
099                int n;
100                while ((n = in.read (buffer)) != -1)
101                    out.write (buffer, 0, n);
102                in.close ();
103            }
104        }
105        
106        public ZipArchive (InputStream in) throws IOException {
107            // Save the input stream to memory
108            ByteArrayOutputStream out = new ByteArrayOutputStream ();
109            byte[] buffer = new byte[BUFFER_SIZE];
110            int n;
111            while ((n = in.read (buffer)) != -1)
112                out.write (buffer, 0, n);
113            out.close ();
114            zipdata = out.toByteArray ();
115    
116            afterCreatingFromStream ();
117        }
118    
119        public ZipArchive (InputStream in, int length) throws IOException {
120            // Save the input stream to memory
121            ByteArrayOutputStream out = new ByteArrayOutputStream ();
122            byte[] buffer = new byte[BUFFER_SIZE];
123            int total = 0;
124            int n;
125            while (total < length && (n = in.read (buffer)) != -1) {
126                out.write (buffer, 0, n);
127                total += n;
128            }
129            out.close ();
130            zipdata = out.toByteArray ();
131    
132            afterCreatingFromStream ();
133        }
134    
135        private void afterCreatingFromStream () throws IOException {
136            // Get the entry names out
137            ZipInputStream zin = new ZipInputStream (getInputStream ());
138            try {
139                ZipEntry entry;
140                while ((entry = zin.getNextEntry ()) != null) {
141                    names.addElement (entry.getName ());
142                    //System.err.println (entry.getName ());
143                    zin.closeEntry ();
144                }
145            } finally {
146                zin.close ();
147            }
148        }
149        
150        public String[] getNames () {
151            String[] fn = new String[names.size ()];
152            names.copyInto (fn);
153            return fn;
154        }
155    
156        public String filenameAt (int i) {
157            return names.elementAt (i).toString ();
158        }
159    
160        public int getFilenameCount () {
161            return names.size ();
162        }
163    
164        public int length () {
165            return zipdata.length;
166        }
167    
168        public InputStream getInputStream () {
169            return new ByteArrayInputStream (zipdata);
170        }
171    
172        /**
173         * Extract ZIP archive entries to the filesystem.
174         * Each destination filename is computed by prefixing 
175         * the ZIP entry name with directory.
176         * @param directory Directory where files should be placed.
177         * Null or empty string means current directory.  If directory
178         * is non-empty and doesn't end with a file separator like / or \, 
179         * then a file separator is added automatically.
180         */
181        public void extract (String directory) throws IOException {
182            extract (directory, null, null);
183        }
184    
185        public void extract (String directory, PrintStream nameStream) throws IOException {
186            extract (directory, null, nameStream);
187        }
188    
189        public void extract (String directory, String[] names) throws IOException {
190            extract (directory, names, null);
191        }
192    
193        public void extract (String directory, String[] names, PrintStream nameStream) throws IOException {
194            byte[] buffer = new byte[BUFFER_SIZE];
195            
196            // Make sure dir has a terminating separator
197            if (directory == null)
198                directory = "";
199            else if (directory.length () > 0
200                && !(directory.endsWith ("/") ||
201                     directory.endsWith (File.separator)))
202                directory += File.separator;
203              
204            // Make a Hashtable out of the names
205            Hashtable nameHash = null;
206            if (names != null) {
207                nameHash = new Hashtable (names.length);
208                for (int i=0; i < names.length; ++i)
209                    nameHash.put (names[i], names[i]);        
210            }
211    
212            ZipInputStream zin = new ZipInputStream (getInputStream ());
213            try {
214                ZipEntry entry;
215                while ((entry = zin.getNextEntry ()) != null) {
216                    if (names == null || nameHash.contains (entry.getName ())) {
217                        File f = new File (directory + entry.getName ());
218    
219                        if (nameStream != null)
220                            nameStream.println (f.toString ());
221    
222                        if (entry.isDirectory ())
223                            f.mkdir ();
224                        else {
225                            // Save the input stream to disk
226                            OutputStream out = new FileOutputStream (f);
227                            int length = (int)entry.getSize ();
228                            int n;
229                            while (length > buffer.length && (n = zin.read (buffer)) != -1) {
230                                out.write (buffer, 0, n);
231                                length -= n;
232                            }
233                            if (length > 0 && (n = zin.read (buffer, 0, length)) != -1) {
234                                out.write (buffer, 0, n);
235                                length -= n;
236                            }
237    
238                            out.close ();
239                        }
240                    }
241    
242                    zin.closeEntry ();
243                }
244                
245            } finally {
246                zin.close ();
247            }
248        }
249        
250        public String[] extractToTempDir () throws IOException {
251            return extractToTempDir (null);
252        }
253    
254        public String[] extractToTempDir (String[] names) throws IOException {
255            // Unpack to temporary directory
256            String dir = TempFile.getTempDirectory ().toString ();
257            extract (dir, names);
258    
259            if (names == null)
260                names = getNames ();
261    
262            // Register all the temp files we just created (in reverse order
263            // so that directories are freed when they're empty)
264            for (int i = names.length - 1; i >= 0; --i)
265                TempFile.registerTempFile (new File (dir + names[i]));
266            
267            // Return an array of just the root (top-level) names
268            Vector v = new Vector ();
269            for (int i = 0; i < names.length; ++i) {
270                int slash = names[i].indexOf ("/");
271                if (slash == -1 || slash == names[i].length()-1)
272                    v.addElement (dir + names[i]);
273            }
274    
275            String[] extractedFilenames = new String[v.size ()];
276            v.copyInto (extractedFilenames);
277            return extractedFilenames;
278        }
279    }