001    /*
002     * Copyright 2007-2008 Sun Microsystems, Inc.  All Rights Reserved.
003     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004     *
005     * This code is free software; you can redistribute it and/or modify it
006     * under the terms of the GNU General Public License version 2 only, as
007     * published by the Free Software Foundation.  Sun designates this
008     * particular file as subject to the "Classpath" exception as provided
009     * by Sun in the LICENSE file that accompanied this code.
010     *
011     * This code is distributed in the hope that it will be useful, but WITHOUT
012     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014     * version 2 for more details (a copy is included in the LICENSE file that
015     * accompanied this code).
016     *
017     * You should have received a copy of the GNU General Public License version
018     * 2 along with this work; if not, write to the Free Software Foundation,
019     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020     *
021     * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022     * CA 95054 USA or visit www.sun.com if you need additional information or
023     * have any questions.
024     */
025    
026    package com.sun.tools.javac.file;
027    
028    
029    import java.io.File;
030    import java.io.FileNotFoundException;
031    import java.io.IOException;
032    import java.io.RandomAccessFile;
033    import java.lang.ref.SoftReference;
034    import java.util.ArrayList;
035    import java.util.Arrays;
036    import java.util.Calendar;
037    import java.util.Collections;
038    import java.util.HashMap;
039    import java.util.HashSet;
040    import java.util.Iterator;
041    import java.util.List;
042    import java.util.Map;
043    import java.util.Set;
044    import java.util.concurrent.locks.ReentrantLock;
045    import java.util.zip.DataFormatException;
046    import java.util.zip.Inflater;
047    import java.util.zip.ZipException;
048    
049    import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
050    import com.sun.tools.javac.file.RelativePath.RelativeFile;
051    
052    /** This class implements building of index of a zip archive and access to it's context.
053     *  It also uses prebuild index if available. It supports invocations where it will
054     *  serialize an optimized zip index file to disk.
055     *
056     *  In oreder to use secondary index file make sure the option "usezipindex" is in the Options object,
057     *  when JavacFileManager is invoked. (You can pass "-XDusezipindex" on the command line.
058     *
059     *  Location where to look for/generate optimized zip index files can be provided using
060     *  "-XDcachezipindexdir=<directory>". If this flag is not provided, the dfault location is
061     *  the value of the "java.io.tmpdir" system property.
062     *
063     *  If key "-XDwritezipindexfiles" is specified, there will be new optimized index file
064     *  created for each archive, used by the compiler for compilation, at location,
065     *  specified by "cachezipindexdir" option.
066     *
067     * If nonBatchMode option is specified (-XDnonBatchMode) the compiler will use timestamp
068     * checking to reindex the zip files if it is needed. In batch mode the timestamps are not checked
069     * and the compiler uses the cached indexes.
070     */
071    public class ZipFileIndex {
072        private static final String MIN_CHAR = String.valueOf(Character.MIN_VALUE);
073        private static final String MAX_CHAR = String.valueOf(Character.MAX_VALUE);
074    
075        public final static long NOT_MODIFIED = Long.MIN_VALUE;
076    
077        private static Map<File, ZipFileIndex> zipFileIndexCache = new HashMap<File, ZipFileIndex>();
078        private static ReentrantLock lock = new ReentrantLock();
079    
080        private static boolean NON_BATCH_MODE = System.getProperty("nonBatchMode") != null;// TODO: Use -XD compiler switch for this.
081    
082        private Map<RelativeDirectory, DirectoryEntry> directories = Collections.<RelativeDirectory, DirectoryEntry>emptyMap();
083        private Set<RelativeDirectory> allDirs = Collections.<RelativeDirectory>emptySet();
084    
085        // ZipFileIndex data entries
086        private File zipFile;
087        private long zipFileLastModified = NOT_MODIFIED;
088        private RandomAccessFile zipRandomFile;
089        private Entry[] entries;
090    
091        private boolean readFromIndex = false;
092        private File zipIndexFile = null;
093        private boolean triedToReadIndex = false;
094        final RelativeDirectory symbolFilePrefix;
095        private int symbolFilePrefixLength = 0;
096        private boolean hasPopulatedData = false;
097        private long lastReferenceTimeStamp = NOT_MODIFIED;
098    
099        private boolean usePreindexedCache = false;
100        private String preindexedCacheLocation = null;
101    
102        private boolean writeIndex = false;
103    
104        private Map <String, SoftReference<RelativeDirectory>> relativeDirectoryCache =
105                new HashMap<String, SoftReference<RelativeDirectory>>();
106    
107        /**
108         * Returns a list of all ZipFileIndex entries
109         *
110         * @return A list of ZipFileIndex entries, or an empty list
111         */
112        public static List<ZipFileIndex> getZipFileIndexes() {
113            return getZipFileIndexes(false);
114        }
115    
116        /**
117         * Returns a list of all ZipFileIndex entries
118         *
119         * @param openedOnly If true it returns a list of only opened ZipFileIndex entries, otherwise
120         *                   all ZipFileEntry(s) are included into the list.
121         * @return A list of ZipFileIndex entries, or an empty list
122         */
123        public static List<ZipFileIndex> getZipFileIndexes(boolean openedOnly) {
124            List<ZipFileIndex> zipFileIndexes = new ArrayList<ZipFileIndex>();
125            lock.lock();
126            try {
127                zipFileIndexes.addAll(zipFileIndexCache.values());
128    
129                if (openedOnly) {
130                    for(ZipFileIndex elem : zipFileIndexes) {
131                        if (!elem.isOpen()) {
132                            zipFileIndexes.remove(elem);
133                        }
134                    }
135                }
136            }
137            finally {
138                lock.unlock();
139            }
140            return zipFileIndexes;
141        }
142    
143        public boolean isOpen() {
144            lock.lock();
145            try {
146                return zipRandomFile != null;
147            }
148            finally {
149                lock.unlock();
150            }
151        }
152    
153        public static ZipFileIndex getZipFileIndex(File zipFile,
154                RelativeDirectory symbolFilePrefix,
155                boolean useCache, String cacheLocation,
156                boolean writeIndex) throws IOException {
157            ZipFileIndex zi = null;
158            lock.lock();
159            try {
160                zi = getExistingZipIndex(zipFile);
161    
162                if (zi == null || (zi != null && zipFile.lastModified() != zi.zipFileLastModified)) {
163                    zi = new ZipFileIndex(zipFile, symbolFilePrefix, writeIndex,
164                            useCache, cacheLocation);
165                    zipFileIndexCache.put(zipFile, zi);
166                }
167            }
168            finally {
169                lock.unlock();
170            }
171            return zi;
172        }
173    
174        public static ZipFileIndex getExistingZipIndex(File zipFile) {
175            lock.lock();
176            try {
177                return zipFileIndexCache.get(zipFile);
178            }
179            finally {
180                lock.unlock();
181            }
182        }
183    
184        public static void clearCache() {
185            lock.lock();
186            try {
187                zipFileIndexCache.clear();
188            }
189            finally {
190                lock.unlock();
191            }
192        }
193    
194        public static void clearCache(long timeNotUsed) {
195            lock.lock();
196            try {
197                Iterator<File> cachedFileIterator = zipFileIndexCache.keySet().iterator();
198                while (cachedFileIterator.hasNext()) {
199                    File cachedFile = cachedFileIterator.next();
200                    ZipFileIndex cachedZipIndex = zipFileIndexCache.get(cachedFile);
201                    if (cachedZipIndex != null) {
202                        long timeToTest = cachedZipIndex.lastReferenceTimeStamp + timeNotUsed;
203                        if (timeToTest < cachedZipIndex.lastReferenceTimeStamp || // Overflow...
204                                System.currentTimeMillis() > timeToTest) {
205                            zipFileIndexCache.remove(cachedFile);
206                        }
207                    }
208                }
209            }
210            finally {
211                lock.unlock();
212            }
213        }
214    
215        public static void removeFromCache(File file) {
216            lock.lock();
217            try {
218                zipFileIndexCache.remove(file);
219            }
220            finally {
221                lock.unlock();
222            }
223        }
224    
225        /** Sets already opened list of ZipFileIndexes from an outside client
226          * of the compiler. This functionality should be used in a non-batch clients of the compiler.
227          */
228        public static void setOpenedIndexes(List<ZipFileIndex>indexes) throws IllegalStateException {
229            lock.lock();
230            try {
231                if (zipFileIndexCache.isEmpty()) {
232                    throw new IllegalStateException("Setting opened indexes should be called only when the ZipFileCache is empty. Call JavacFileManager.flush() before calling this method.");
233                }
234    
235                for (ZipFileIndex zfi : indexes) {
236                    zipFileIndexCache.put(zfi.zipFile, zfi);
237                }
238            }
239            finally {
240                lock.unlock();
241            }
242        }
243    
244        private ZipFileIndex(File zipFile, RelativeDirectory symbolFilePrefix, boolean writeIndex,
245                boolean useCache, String cacheLocation) throws IOException {
246            this.zipFile = zipFile;
247            this.symbolFilePrefix = symbolFilePrefix;
248            this.symbolFilePrefixLength = (symbolFilePrefix == null ? 0 :
249                symbolFilePrefix.getPath().getBytes("UTF-8").length);
250            this.writeIndex = writeIndex;
251            this.usePreindexedCache = useCache;
252            this.preindexedCacheLocation = cacheLocation;
253    
254            if (zipFile != null) {
255                this.zipFileLastModified = zipFile.lastModified();
256            }
257    
258            // Validate integrity of the zip file
259            checkIndex();
260        }
261    
262        public String toString() {
263            return "ZipFileIndex[" + zipFile + "]";
264        }
265    
266        // Just in case...
267        protected void finalize() {
268            closeFile();
269        }
270    
271        private boolean isUpToDate() {
272            if (zipFile != null &&
273                    ((!NON_BATCH_MODE) || zipFileLastModified == zipFile.lastModified()) &&
274                    hasPopulatedData) {
275                return true;
276            }
277    
278            return false;
279        }
280    
281        /**
282         * Here we need to make sure that the ZipFileIndex is valid. Check the timestamp of the file and
283         * if its the same as the one at the time the index was build we don't need to reopen anything.
284         */
285        private void checkIndex() throws IOException {
286            boolean isUpToDate = true;
287            if (!isUpToDate()) {
288                closeFile();
289                isUpToDate = false;
290            }
291    
292            if (zipRandomFile != null || isUpToDate) {
293                lastReferenceTimeStamp = System.currentTimeMillis();
294                return;
295            }
296    
297            hasPopulatedData = true;
298    
299            if (readIndex()) {
300                lastReferenceTimeStamp = System.currentTimeMillis();
301                return;
302            }
303    
304            directories = Collections.<RelativeDirectory, DirectoryEntry>emptyMap();
305            allDirs = Collections.<RelativeDirectory>emptySet();
306    
307            try {
308                openFile();
309                long totalLength = zipRandomFile.length();
310                ZipDirectory directory = new ZipDirectory(zipRandomFile, 0L, totalLength, this);
311                directory.buildIndex();
312            } finally {
313                if (zipRandomFile != null) {
314                    closeFile();
315                }
316            }
317    
318            lastReferenceTimeStamp = System.currentTimeMillis();
319        }
320    
321        private void openFile() throws FileNotFoundException {
322            if (zipRandomFile == null && zipFile != null) {
323                zipRandomFile = new RandomAccessFile(zipFile, "r");
324            }
325        }
326    
327        private void cleanupState() {
328            // Make sure there is a valid but empty index if the file doesn't exist
329            entries = Entry.EMPTY_ARRAY;
330            directories = Collections.<RelativeDirectory, DirectoryEntry>emptyMap();
331            zipFileLastModified = NOT_MODIFIED;
332            allDirs = Collections.<RelativeDirectory>emptySet();
333        }
334    
335        public void close() {
336            lock.lock();
337            try {
338                writeIndex();
339                closeFile();
340            }
341            finally {
342                lock.unlock();
343            }
344        }
345    
346        private void closeFile() {
347            if (zipRandomFile != null) {
348                try {
349                    zipRandomFile.close();
350                } catch (IOException ex) {
351                }
352                zipRandomFile = null;
353            }
354        }
355    
356        /**
357         * Returns the ZipFileIndexEntry for an absolute path, if there is one.
358         */
359        Entry getZipIndexEntry(RelativePath path) {
360            lock.lock();
361            try {
362                checkIndex();
363                DirectoryEntry de = directories.get(path.dirname());
364                String lookFor = path.basename();
365                return de == null ? null : de.getEntry(lookFor);
366            }
367            catch (IOException e) {
368                return null;
369            }
370            finally {
371                lock.unlock();
372            }
373        }
374    
375        /**
376         * Returns a javac List of filenames within an absolute path in the ZipFileIndex.
377         */
378        public com.sun.tools.javac.util.List<String> getFiles(RelativeDirectory path) {
379            lock.lock();
380            try {
381                checkIndex();
382    
383                DirectoryEntry de = directories.get(path);
384                com.sun.tools.javac.util.List<String> ret = de == null ? null : de.getFiles();
385    
386                if (ret == null) {
387                    return com.sun.tools.javac.util.List.<String>nil();
388                }
389                return ret;
390            }
391            catch (IOException e) {
392                return com.sun.tools.javac.util.List.<String>nil();
393            }
394            finally {
395                lock.unlock();
396            }
397        }
398    
399        public List<String> getDirectories(RelativeDirectory path) {
400            lock.lock();
401            try {
402                checkIndex();
403    
404                DirectoryEntry de = directories.get(path);
405                com.sun.tools.javac.util.List<String> ret = de == null ? null : de.getDirectories();
406    
407                if (ret == null) {
408                    return com.sun.tools.javac.util.List.<String>nil();
409                }
410    
411                return ret;
412            }
413            catch (IOException e) {
414                return com.sun.tools.javac.util.List.<String>nil();
415            }
416            finally {
417                lock.unlock();
418            }
419        }
420    
421        public Set<RelativeDirectory> getAllDirectories() {
422            lock.lock();
423            try {
424                checkIndex();
425                if (allDirs == Collections.EMPTY_SET) {
426                    allDirs = new HashSet<RelativeDirectory>(directories.keySet());
427                }
428    
429                return allDirs;
430            }
431            catch (IOException e) {
432                return Collections.<RelativeDirectory>emptySet();
433            }
434            finally {
435                lock.unlock();
436            }
437        }
438    
439        /**
440         * Tests if a specific path exists in the zip.  This method will return true
441         * for file entries and directories.
442         *
443         * @param path A path within the zip.
444         * @return True if the path is a file or dir, false otherwise.
445         */
446        public boolean contains(RelativePath path) {
447            lock.lock();
448            try {
449                checkIndex();
450                return getZipIndexEntry(path) != null;
451            }
452            catch (IOException e) {
453                return false;
454            }
455            finally {
456                lock.unlock();
457            }
458        }
459    
460        public boolean isDirectory(RelativePath path) throws IOException {
461            lock.lock();
462            try {
463                // The top level in a zip file is always a directory.
464                if (path.getPath().length() == 0) {
465                    lastReferenceTimeStamp = System.currentTimeMillis();
466                    return true;
467                }
468    
469                checkIndex();
470                return directories.get(path) != null;
471            }
472            finally {
473                lock.unlock();
474            }
475        }
476    
477        public long getLastModified(RelativeFile path) throws IOException {
478            lock.lock();
479            try {
480                Entry entry = getZipIndexEntry(path);
481                if (entry == null)
482                    throw new FileNotFoundException();
483                return entry.getLastModified();
484            }
485            finally {
486                lock.unlock();
487            }
488        }
489    
490        public int length(RelativeFile path) throws IOException {
491            lock.lock();
492            try {
493                Entry entry = getZipIndexEntry(path);
494                if (entry == null)
495                    throw new FileNotFoundException();
496    
497                if (entry.isDir) {
498                    return 0;
499                }
500    
501                byte[] header = getHeader(entry);
502                // entry is not compressed?
503                if (get2ByteLittleEndian(header, 8) == 0) {
504                    return entry.compressedSize;
505                } else {
506                    return entry.size;
507                }
508            }
509            finally {
510                lock.unlock();
511            }
512        }
513    
514        public byte[] read(RelativeFile path) throws IOException {
515            lock.lock();
516            try {
517                Entry entry = getZipIndexEntry(path);
518                if (entry == null)
519                    throw new FileNotFoundException("Path not found in ZIP: " + path.path);
520                return read(entry);
521            }
522            finally {
523                lock.unlock();
524            }
525        }
526    
527        byte[] read(Entry entry) throws IOException {
528            lock.lock();
529            try {
530                openFile();
531                byte[] result = readBytes(entry);
532                closeFile();
533                return result;
534            }
535            finally {
536                lock.unlock();
537            }
538        }
539    
540        public int read(RelativeFile path, byte[] buffer) throws IOException {
541            lock.lock();
542            try {
543                Entry entry = getZipIndexEntry(path);
544                if (entry == null)
545                    throw new FileNotFoundException();
546                return read(entry, buffer);
547            }
548            finally {
549                lock.unlock();
550            }
551        }
552    
553        int read(Entry entry, byte[] buffer)
554                throws IOException {
555            lock.lock();
556            try {
557                int result = readBytes(entry, buffer);
558                return result;
559            }
560            finally {
561                lock.unlock();
562            }
563        }
564    
565        private byte[] readBytes(Entry entry) throws IOException {
566            byte[] header = getHeader(entry);
567            int csize = entry.compressedSize;
568            byte[] cbuf = new byte[csize];
569            zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28));
570            zipRandomFile.readFully(cbuf, 0, csize);
571    
572            // is this compressed - offset 8 in the ZipEntry header
573            if (get2ByteLittleEndian(header, 8) == 0)
574                return cbuf;
575    
576            int size = entry.size;
577            byte[] buf = new byte[size];
578            if (inflate(cbuf, buf) != size)
579                throw new ZipException("corrupted zip file");
580    
581            return buf;
582        }
583    
584        /**
585         *
586         */
587        private int readBytes(Entry entry, byte[] buffer) throws IOException {
588            byte[] header = getHeader(entry);
589    
590            // entry is not compressed?
591            if (get2ByteLittleEndian(header, 8) == 0) {
592                zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28));
593                int offset = 0;
594                int size = buffer.length;
595                while (offset < size) {
596                    int count = zipRandomFile.read(buffer, offset, size - offset);
597                    if (count == -1)
598                        break;
599                    offset += count;
600                }
601                return entry.size;
602            }
603    
604            int csize = entry.compressedSize;
605            byte[] cbuf = new byte[csize];
606            zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28));
607            zipRandomFile.readFully(cbuf, 0, csize);
608    
609            int count = inflate(cbuf, buffer);
610            if (count == -1)
611                throw new ZipException("corrupted zip file");
612    
613            return entry.size;
614        }
615    
616        //----------------------------------------------------------------------------
617        // Zip utilities
618        //----------------------------------------------------------------------------
619    
620        private byte[] getHeader(Entry entry) throws IOException {
621            zipRandomFile.seek(entry.offset);
622            byte[] header = new byte[30];
623            zipRandomFile.readFully(header);
624            if (get4ByteLittleEndian(header, 0) != 0x04034b50)
625                throw new ZipException("corrupted zip file");
626            if ((get2ByteLittleEndian(header, 6) & 1) != 0)
627                throw new ZipException("encrypted zip file"); // offset 6 in the header of the ZipFileEntry
628            return header;
629        }
630    
631      /*
632       * Inflate using the java.util.zip.Inflater class
633       */
634        private static Inflater inflater;
635        private int inflate(byte[] src, byte[] dest) {
636    
637            // construct the inflater object or reuse an existing one
638            if (inflater == null)
639                inflater = new Inflater(true);
640    
641            synchronized (inflater) {
642                inflater.reset();
643                inflater.setInput(src);
644                try {
645                    return inflater.inflate(dest);
646                } catch (DataFormatException ex) {
647                    return -1;
648                }
649            }
650        }
651    
652        /**
653         * return the two bytes buf[pos], buf[pos+1] as an unsigned integer in little
654         * endian format.
655         */
656        private static int get2ByteLittleEndian(byte[] buf, int pos) {
657            return (buf[pos] & 0xFF) + ((buf[pos+1] & 0xFF) << 8);
658        }
659    
660        /**
661         * return the 4 bytes buf[i..i+3] as an integer in little endian format.
662         */
663        private static int get4ByteLittleEndian(byte[] buf, int pos) {
664            return (buf[pos] & 0xFF) + ((buf[pos + 1] & 0xFF) << 8) +
665                    ((buf[pos + 2] & 0xFF) << 16) + ((buf[pos + 3] & 0xFF) << 24);
666        }
667    
668        /* ----------------------------------------------------------------------------
669         * ZipDirectory
670         * ----------------------------------------------------------------------------*/
671    
672        private class ZipDirectory {
673            private RelativeDirectory lastDir;
674            private int lastStart;
675            private int lastLen;
676    
677            byte[] zipDir;
678            RandomAccessFile zipRandomFile = null;
679            ZipFileIndex zipFileIndex = null;
680    
681            public ZipDirectory(RandomAccessFile zipRandomFile, long start, long end, ZipFileIndex index) throws IOException {
682                this.zipRandomFile = zipRandomFile;
683                this.zipFileIndex = index;
684    
685                findCENRecord(start, end);
686            }
687    
688            /*
689             * Reads zip file central directory.
690             * For more details see readCEN in zip_util.c from the JDK sources.
691             * This is a Java port of that function.
692             */
693            private void findCENRecord(long start, long end) throws IOException {
694                long totalLength = end - start;
695                int endbuflen = 1024;
696                byte[] endbuf = new byte[endbuflen];
697                long endbufend = end - start;
698    
699                // There is a variable-length field after the dir offset record. We need to do consequential search.
700                while (endbufend >= 22) {
701                    if (endbufend < endbuflen)
702                        endbuflen = (int)endbufend;
703                    long endbufpos = endbufend - endbuflen;
704                    zipRandomFile.seek(start + endbufpos);
705                    zipRandomFile.readFully(endbuf, 0, endbuflen);
706                    int i = endbuflen - 22;
707                    while (i >= 0 &&
708                            !(endbuf[i] == 0x50 &&
709                            endbuf[i + 1] == 0x4b &&
710                            endbuf[i + 2] == 0x05 &&
711                            endbuf[i + 3] == 0x06 &&
712                            endbufpos + i + 22 +
713                            get2ByteLittleEndian(endbuf, i + 20) == totalLength)) {
714                        i--;
715                    }
716    
717                    if (i >= 0) {
718                        zipDir = new byte[get4ByteLittleEndian(endbuf, i + 12) + 2];
719                        zipDir[0] = endbuf[i + 10];
720                        zipDir[1] = endbuf[i + 11];
721                        zipRandomFile.seek(start + get4ByteLittleEndian(endbuf, i + 16));
722                        zipRandomFile.readFully(zipDir, 2, zipDir.length - 2);
723                        return;
724                    } else {
725                        endbufend = endbufpos + 21;
726                    }
727                }
728                throw new ZipException("cannot read zip file");
729            }
730    
731            private void buildIndex() throws IOException {
732                int entryCount = get2ByteLittleEndian(zipDir, 0);
733    
734                // Add each of the files
735                if (entryCount > 0) {
736                    directories = new HashMap<RelativeDirectory, DirectoryEntry>();
737                    ArrayList<Entry> entryList = new ArrayList<Entry>();
738                    int pos = 2;
739                    for (int i = 0; i < entryCount; i++) {
740                        pos = readEntry(pos, entryList, directories);
741                    }
742    
743                    // Add the accumulated dirs into the same list
744                    for (RelativeDirectory d: directories.keySet()) {
745                        // use shared RelativeDirectory objects for parent dirs
746                        RelativeDirectory parent = getRelativeDirectory(d.dirname().getPath());
747                        String file = d.basename();
748                        Entry zipFileIndexEntry = new Entry(parent, file);
749                        zipFileIndexEntry.isDir = true;
750                        entryList.add(zipFileIndexEntry);
751                    }
752    
753                    entries = entryList.toArray(new Entry[entryList.size()]);
754                    Arrays.sort(entries);
755                } else {
756                    cleanupState();
757                }
758            }
759    
760            private int readEntry(int pos, List<Entry> entryList,
761                    Map<RelativeDirectory, DirectoryEntry> directories) throws IOException {
762                if (get4ByteLittleEndian(zipDir, pos) != 0x02014b50) {
763                    throw new ZipException("cannot read zip file entry");
764                }
765    
766                int dirStart = pos + 46;
767                int fileStart = dirStart;
768                int fileEnd = fileStart + get2ByteLittleEndian(zipDir, pos + 28);
769    
770                if (zipFileIndex.symbolFilePrefixLength != 0 &&
771                        ((fileEnd - fileStart) >= symbolFilePrefixLength)) {
772                    dirStart += zipFileIndex.symbolFilePrefixLength;
773                   fileStart += zipFileIndex.symbolFilePrefixLength;
774                }
775                // Force any '\' to '/'. Keep the position of the last separator.
776                for (int index = fileStart; index < fileEnd; index++) {
777                    byte nextByte = zipDir[index];
778                    if (nextByte == (byte)'\\') {
779                        zipDir[index] = (byte)'/';
780                        fileStart = index + 1;
781                    } else if (nextByte == (byte)'/') {
782                        fileStart = index + 1;
783                    }
784                }
785    
786                RelativeDirectory directory = null;
787                if (fileStart == dirStart)
788                    directory = getRelativeDirectory("");
789                else if (lastDir != null && lastLen == fileStart - dirStart - 1) {
790                    int index = lastLen - 1;
791                    while (zipDir[lastStart + index] == zipDir[dirStart + index]) {
792                        if (index == 0) {
793                            directory = lastDir;
794                            break;
795                        }
796                        index--;
797                    }
798                }
799    
800                // Sub directories
801                if (directory == null) {
802                    lastStart = dirStart;
803                    lastLen = fileStart - dirStart - 1;
804    
805                    directory = getRelativeDirectory(new String(zipDir, dirStart, lastLen, "UTF-8"));
806                    lastDir = directory;
807    
808                    // Enter also all the parent directories
809                    RelativeDirectory tempDirectory = directory;
810    
811                    while (directories.get(tempDirectory) == null) {
812                        directories.put(tempDirectory, new DirectoryEntry(tempDirectory, zipFileIndex));
813                        if (tempDirectory.path.indexOf("/") == tempDirectory.path.length() - 1)
814                            break;
815                        else {
816                            // use shared RelativeDirectory objects for parent dirs
817                            tempDirectory = getRelativeDirectory(tempDirectory.dirname().getPath());
818                        }
819                    }
820                }
821                else {
822                    if (directories.get(directory) == null) {
823                        directories.put(directory, new DirectoryEntry(directory, zipFileIndex));
824                    }
825                }
826    
827                // For each dir create also a file
828                if (fileStart != fileEnd) {
829                    Entry entry = new Entry(directory,
830                            new String(zipDir, fileStart, fileEnd - fileStart, "UTF-8"));
831    
832                    entry.setNativeTime(get4ByteLittleEndian(zipDir, pos + 12));
833                    entry.compressedSize = get4ByteLittleEndian(zipDir, pos + 20);
834                    entry.size = get4ByteLittleEndian(zipDir, pos + 24);
835                    entry.offset = get4ByteLittleEndian(zipDir, pos + 42);
836                    entryList.add(entry);
837                }
838    
839                return pos + 46 +
840                        get2ByteLittleEndian(zipDir, pos + 28) +
841                        get2ByteLittleEndian(zipDir, pos + 30) +
842                        get2ByteLittleEndian(zipDir, pos + 32);
843            }
844        }
845    
846        /**
847         * Returns the last modified timestamp of a zip file.
848         * @return long
849         */
850        public long getZipFileLastModified() throws IOException {
851            lock.lock();
852            try {
853                checkIndex();
854                return zipFileLastModified;
855            }
856            finally {
857                lock.unlock();
858            }
859        }
860    
861        /** ------------------------------------------------------------------------
862         *  DirectoryEntry class
863         * -------------------------------------------------------------------------*/
864    
865        static class DirectoryEntry {
866            private boolean filesInited;
867            private boolean directoriesInited;
868            private boolean zipFileEntriesInited;
869            private boolean entriesInited;
870    
871            private long writtenOffsetOffset = 0;
872    
873            private RelativeDirectory dirName;
874    
875            private com.sun.tools.javac.util.List<String> zipFileEntriesFiles = com.sun.tools.javac.util.List.<String>nil();
876            private com.sun.tools.javac.util.List<String> zipFileEntriesDirectories = com.sun.tools.javac.util.List.<String>nil();
877            private com.sun.tools.javac.util.List<Entry>  zipFileEntries = com.sun.tools.javac.util.List.<Entry>nil();
878    
879            private List<Entry> entries = new ArrayList<Entry>();
880    
881            private ZipFileIndex zipFileIndex;
882    
883            private int numEntries;
884    
885            DirectoryEntry(RelativeDirectory dirName, ZipFileIndex index) {
886                filesInited = false;
887                directoriesInited = false;
888                entriesInited = false;
889    
890                this.dirName = dirName;
891                this.zipFileIndex = index;
892            }
893    
894            private com.sun.tools.javac.util.List<String> getFiles() {
895                if (!filesInited) {
896                    initEntries();
897                    for (Entry e : entries) {
898                        if (!e.isDir) {
899                            zipFileEntriesFiles = zipFileEntriesFiles.append(e.name);
900                        }
901                    }
902                    filesInited = true;
903                }
904                return zipFileEntriesFiles;
905            }
906    
907            private com.sun.tools.javac.util.List<String> getDirectories() {
908                if (!directoriesInited) {
909                    initEntries();
910                    for (Entry e : entries) {
911                        if (e.isDir) {
912                            zipFileEntriesDirectories = zipFileEntriesDirectories.append(e.name);
913                        }
914                    }
915                    directoriesInited = true;
916                }
917                return zipFileEntriesDirectories;
918            }
919    
920            private com.sun.tools.javac.util.List<Entry> getEntries() {
921                if (!zipFileEntriesInited) {
922                    initEntries();
923                    zipFileEntries = com.sun.tools.javac.util.List.nil();
924                    for (Entry zfie : entries) {
925                        zipFileEntries = zipFileEntries.append(zfie);
926                    }
927                    zipFileEntriesInited = true;
928                }
929                return zipFileEntries;
930            }
931    
932            private Entry getEntry(String rootName) {
933                initEntries();
934                int index = Collections.binarySearch(entries, new Entry(dirName, rootName));
935                if (index < 0) {
936                    return null;
937                }
938    
939                return entries.get(index);
940            }
941    
942            private void initEntries() {
943                if (entriesInited) {
944                    return;
945                }
946    
947                if (!zipFileIndex.readFromIndex) {
948                    int from = -Arrays.binarySearch(zipFileIndex.entries,
949                            new Entry(dirName, ZipFileIndex.MIN_CHAR)) - 1;
950                    int to = -Arrays.binarySearch(zipFileIndex.entries,
951                            new Entry(dirName, MAX_CHAR)) - 1;
952    
953                    for (int i = from; i < to; i++) {
954                        entries.add(zipFileIndex.entries[i]);
955                    }
956                } else {
957                    File indexFile = zipFileIndex.getIndexFile();
958                    if (indexFile != null) {
959                        RandomAccessFile raf = null;
960                        try {
961                            raf = new RandomAccessFile(indexFile, "r");
962                            raf.seek(writtenOffsetOffset);
963    
964                            for (int nFiles = 0; nFiles < numEntries; nFiles++) {
965                                // Read the name bytes
966                                int zfieNameBytesLen = raf.readInt();
967                                byte [] zfieNameBytes = new byte[zfieNameBytesLen];
968                                raf.read(zfieNameBytes);
969                                String eName = new String(zfieNameBytes, "UTF-8");
970    
971                                // Read isDir
972                                boolean eIsDir = raf.readByte() == (byte)0 ? false : true;
973    
974                                // Read offset of bytes in the real Jar/Zip file
975                                int eOffset = raf.readInt();
976    
977                                // Read size of the file in the real Jar/Zip file
978                                int eSize = raf.readInt();
979    
980                                // Read compressed size of the file in the real Jar/Zip file
981                                int eCsize = raf.readInt();
982    
983                                // Read java time stamp of the file in the real Jar/Zip file
984                                long eJavaTimestamp = raf.readLong();
985    
986                                Entry rfie = new Entry(dirName, eName);
987                                rfie.isDir = eIsDir;
988                                rfie.offset = eOffset;
989                                rfie.size = eSize;
990                                rfie.compressedSize = eCsize;
991                                rfie.javatime = eJavaTimestamp;
992                                entries.add(rfie);
993                            }
994                        } catch (Throwable t) {
995                            // Do nothing
996                        } finally {
997                            try {
998                                if (raf == null) {
999                                    raf.close();
1000                                }
1001                            } catch (Throwable t) {
1002                                // Do nothing
1003                            }
1004                        }
1005                    }
1006                }
1007    
1008                entriesInited = true;
1009            }
1010    
1011            List<Entry> getEntriesAsCollection() {
1012                initEntries();
1013    
1014                return entries;
1015            }
1016        }
1017    
1018        private boolean readIndex() {
1019            if (triedToReadIndex || !usePreindexedCache) {
1020                return false;
1021            }
1022    
1023            boolean ret = false;
1024            lock.lock();
1025            try {
1026                triedToReadIndex = true;
1027                RandomAccessFile raf = null;
1028                try {
1029                    File indexFileName = getIndexFile();
1030                    raf = new RandomAccessFile(indexFileName, "r");
1031    
1032                    long fileStamp = raf.readLong();
1033                    if (zipFile.lastModified() != fileStamp) {
1034                        ret = false;
1035                    } else {
1036                        directories = new HashMap<RelativeDirectory, DirectoryEntry>();
1037                        int numDirs = raf.readInt();
1038                        for (int nDirs = 0; nDirs < numDirs; nDirs++) {
1039                            int dirNameBytesLen = raf.readInt();
1040                            byte [] dirNameBytes = new byte[dirNameBytesLen];
1041                            raf.read(dirNameBytes);
1042    
1043                            RelativeDirectory dirNameStr = getRelativeDirectory(new String(dirNameBytes, "UTF-8"));
1044                            DirectoryEntry de = new DirectoryEntry(dirNameStr, this);
1045                            de.numEntries = raf.readInt();
1046                            de.writtenOffsetOffset = raf.readLong();
1047                            directories.put(dirNameStr, de);
1048                        }
1049                        ret = true;
1050                        zipFileLastModified = fileStamp;
1051                    }
1052                } catch (Throwable t) {
1053                    // Do nothing
1054                } finally {
1055                    if (raf != null) {
1056                        try {
1057                            raf.close();
1058                        } catch (Throwable tt) {
1059                            // Do nothing
1060                        }
1061                    }
1062                }
1063                if (ret == true) {
1064                    readFromIndex = true;
1065                }
1066            }
1067            finally {
1068                lock.unlock();
1069            }
1070    
1071            return ret;
1072        }
1073    
1074        private boolean writeIndex() {
1075            boolean ret = false;
1076            if (readFromIndex || !usePreindexedCache) {
1077                return true;
1078            }
1079    
1080            if (!writeIndex) {
1081                return true;
1082            }
1083    
1084            File indexFile = getIndexFile();
1085            if (indexFile == null) {
1086                return false;
1087            }
1088    
1089            RandomAccessFile raf = null;
1090            long writtenSoFar = 0;
1091            try {
1092                raf = new RandomAccessFile(indexFile, "rw");
1093    
1094                raf.writeLong(zipFileLastModified);
1095                writtenSoFar += 8;
1096    
1097                List<DirectoryEntry> directoriesToWrite = new ArrayList<DirectoryEntry>();
1098                Map<RelativeDirectory, Long> offsets = new HashMap<RelativeDirectory, Long>();
1099                raf.writeInt(directories.keySet().size());
1100                writtenSoFar += 4;
1101    
1102                for (RelativeDirectory dirName: directories.keySet()) {
1103                    DirectoryEntry dirEntry = directories.get(dirName);
1104    
1105                    directoriesToWrite.add(dirEntry);
1106    
1107                    // Write the dir name bytes
1108                    byte [] dirNameBytes = dirName.getPath().getBytes("UTF-8");
1109                    int dirNameBytesLen = dirNameBytes.length;
1110                    raf.writeInt(dirNameBytesLen);
1111                    writtenSoFar += 4;
1112    
1113                    raf.write(dirNameBytes);
1114                    writtenSoFar += dirNameBytesLen;
1115    
1116                    // Write the number of files in the dir
1117                    List<Entry> dirEntries = dirEntry.getEntriesAsCollection();
1118                    raf.writeInt(dirEntries.size());
1119                    writtenSoFar += 4;
1120    
1121                    offsets.put(dirName, new Long(writtenSoFar));
1122    
1123                    // Write the offset of the file's data in the dir
1124                    dirEntry.writtenOffsetOffset = 0L;
1125                    raf.writeLong(0L);
1126                    writtenSoFar += 8;
1127                }
1128    
1129                for (DirectoryEntry de : directoriesToWrite) {
1130                    // Fix up the offset in the directory table
1131                    long currFP = raf.getFilePointer();
1132    
1133                    long offsetOffset = offsets.get(de.dirName).longValue();
1134                    raf.seek(offsetOffset);
1135                    raf.writeLong(writtenSoFar);
1136    
1137                    raf.seek(currFP);
1138    
1139                    // Now write each of the files in the DirectoryEntry
1140                    List<Entry> entries = de.getEntriesAsCollection();
1141                    for (Entry zfie : entries) {
1142                        // Write the name bytes
1143                        byte [] zfieNameBytes = zfie.name.getBytes("UTF-8");
1144                        int zfieNameBytesLen = zfieNameBytes.length;
1145                        raf.writeInt(zfieNameBytesLen);
1146                        writtenSoFar += 4;
1147                        raf.write(zfieNameBytes);
1148                        writtenSoFar += zfieNameBytesLen;
1149    
1150                        // Write isDir
1151                        raf.writeByte(zfie.isDir ? (byte)1 : (byte)0);
1152                        writtenSoFar += 1;
1153    
1154                        // Write offset of bytes in the real Jar/Zip file
1155                        raf.writeInt(zfie.offset);
1156                        writtenSoFar += 4;
1157    
1158                        // Write size of the file in the real Jar/Zip file
1159                        raf.writeInt(zfie.size);
1160                        writtenSoFar += 4;
1161    
1162                        // Write compressed size of the file in the real Jar/Zip file
1163                        raf.writeInt(zfie.compressedSize);
1164                        writtenSoFar += 4;
1165    
1166                        // Write java time stamp of the file in the real Jar/Zip file
1167                        raf.writeLong(zfie.getLastModified());
1168                        writtenSoFar += 8;
1169                    }
1170                }
1171            } catch (Throwable t) {
1172                // Do nothing
1173            } finally {
1174                try {
1175                    if (raf != null) {
1176                        raf.close();
1177                    }
1178                } catch(IOException ioe) {
1179                    // Do nothing
1180                }
1181            }
1182    
1183            return ret;
1184        }
1185    
1186        public boolean writeZipIndex() {
1187            lock.lock();
1188            try {
1189                return writeIndex();
1190            }
1191            finally {
1192                lock.unlock();
1193            }
1194        }
1195    
1196        private File getIndexFile() {
1197            if (zipIndexFile == null) {
1198                if (zipFile == null) {
1199                    return null;
1200                }
1201    
1202                zipIndexFile = new File((preindexedCacheLocation == null ? "" : preindexedCacheLocation) +
1203                        zipFile.getName() + ".index");
1204            }
1205    
1206            return zipIndexFile;
1207        }
1208    
1209        public File getZipFile() {
1210            return zipFile;
1211        }
1212    
1213        private RelativeDirectory getRelativeDirectory(String path) {
1214            RelativeDirectory rd;
1215            SoftReference<RelativeDirectory> ref = relativeDirectoryCache.get(path);
1216            if (ref != null) {
1217                rd = ref.get();
1218                if (rd != null)
1219                    return rd;
1220            }
1221            rd = new RelativeDirectory(path);
1222            relativeDirectoryCache.put(path, new SoftReference<RelativeDirectory>(rd));
1223            return rd;
1224        }
1225    
1226        static class Entry implements Comparable<Entry> {
1227            public static final Entry[] EMPTY_ARRAY = {};
1228    
1229            // Directory related
1230            RelativeDirectory dir;
1231            boolean isDir;
1232    
1233            // File related
1234            String name;
1235    
1236            int offset;
1237            int size;
1238            int compressedSize;
1239            long javatime;
1240    
1241            private int nativetime;
1242    
1243            public Entry(RelativePath path) {
1244                this(path.dirname(), path.basename());
1245            }
1246    
1247            public Entry(RelativeDirectory directory, String name) {
1248                this.dir = directory;
1249                this.name = name;
1250            }
1251    
1252            public String getName() {
1253                return new RelativeFile(dir, name).getPath();
1254            }
1255    
1256            public String getFileName() {
1257                return name;
1258            }
1259    
1260            public long getLastModified() {
1261                if (javatime == 0) {
1262                        javatime = dosToJavaTime(nativetime);
1263                }
1264                return javatime;
1265            }
1266    
1267            // based on dosToJavaTime in java.util.Zip, but avoiding the
1268            // use of deprecated Date constructor
1269            private static long dosToJavaTime(int dtime) {
1270                Calendar c = Calendar.getInstance();
1271                c.set(Calendar.YEAR,        ((dtime >> 25) & 0x7f) + 1980);
1272                c.set(Calendar.MONTH,       ((dtime >> 21) & 0x0f) - 1);
1273                c.set(Calendar.DATE,        ((dtime >> 16) & 0x1f));
1274                c.set(Calendar.HOUR_OF_DAY, ((dtime >> 11) & 0x1f));
1275                c.set(Calendar.MINUTE,      ((dtime >>  5) & 0x3f));
1276                c.set(Calendar.SECOND,      ((dtime <<  1) & 0x3e));
1277                c.set(Calendar.MILLISECOND, 0);
1278                return c.getTimeInMillis();
1279            }
1280    
1281            void setNativeTime(int natTime) {
1282                nativetime = natTime;
1283            }
1284    
1285            public boolean isDirectory() {
1286                return isDir;
1287            }
1288    
1289            public int compareTo(Entry other) {
1290                RelativeDirectory otherD = other.dir;
1291                if (dir != otherD) {
1292                    int c = dir.compareTo(otherD);
1293                    if (c != 0)
1294                        return c;
1295                }
1296                return name.compareTo(other.name);
1297            }
1298    
1299            @Override
1300            public boolean equals(Object o) {
1301                if (!(o instanceof Entry))
1302                    return false;
1303                Entry other = (Entry) o;
1304                return dir.equals(other.dir) && name.equals(other.name);
1305            }
1306    
1307            @Override
1308            public int hashCode() {
1309                int hash = 7;
1310                hash = 97 * hash + (this.dir != null ? this.dir.hashCode() : 0);
1311                hash = 97 * hash + (this.name != null ? this.name.hashCode() : 0);
1312                return hash;
1313            }
1314    
1315    
1316            public String toString() {
1317                return isDir ? ("Dir:" + dir + " : " + name) :
1318                    (dir + ":" + name);
1319            }
1320        }
1321    
1322    }