KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > caucho > vfs > Jar


1 /*
2  * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
3  *
4  * This file is part of Resin(R) Open Source
5  *
6  * Each copy or derived work must preserve the copyright notice and this
7  * notice unmodified.
8  *
9  * Resin Open Source is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Resin Open Source is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
17  * of NON-INFRINGEMENT. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Resin Open Source; if not, write to the
22  *
23  * Free Software Foundation, Inc.
24  * 59 Temple Place, Suite 330
25  * Boston, MA 02111-1307 USA
26  *
27  * @author Scott Ferguson
28  */

29
30 package com.caucho.vfs;
31
32 import com.caucho.loader.EnvironmentLocal;
33 import com.caucho.make.CachedDependency;
34 import com.caucho.util.Alarm;
35 import com.caucho.util.CacheListener;
36 import com.caucho.util.Log;
37 import com.caucho.util.LruCache;
38
39 import java.io.FileNotFoundException JavaDoc;
40 import java.io.IOException JavaDoc;
41 import java.io.InputStream JavaDoc;
42 import java.lang.ref.SoftReference JavaDoc;
43 import java.security.cert.Certificate JavaDoc;
44 import java.util.ArrayList JavaDoc;
45 import java.util.Enumeration JavaDoc;
46 import java.util.Iterator JavaDoc;
47 import java.util.jar.JarEntry JavaDoc;
48 import java.util.jar.JarFile JavaDoc;
49 import java.util.jar.Manifest JavaDoc;
50 import java.util.logging.Level JavaDoc;
51 import java.util.logging.Logger JavaDoc;
52 import java.util.zip.ZipEntry JavaDoc;
53 import java.util.zip.ZipFile JavaDoc;
54 import java.util.zip.ZipInputStream JavaDoc;
55
56 /**
57  * Jar is a cache around a jar file to avoid scanning through the whole
58  * file on each request.
59  *
60  * <p>When the Jar is created, it scans the file and builds a directory
61  * of the Jar entries.
62  */

63 public class Jar implements CacheListener {
64   private static final Logger JavaDoc log = Log.open(Jar.class);
65   
66   private static LruCache<Path,Jar> _jarCache;
67
68   private static EnvironmentLocal<Integer JavaDoc> _jarSize
69     = new EnvironmentLocal<Integer JavaDoc>("caucho.vfs.jar-size");
70   
71   private Path _backing;
72
73   private JarDepend _depend;
74   
75   // saved last modified time
76
private long _lastModified;
77   // saved length
78
private long _length;
79   // last time the file was checked
80
private long _lastTime;
81
82   // cached zip file to read jar entries
83
private SoftReference JavaDoc<JarFile JavaDoc> _jarFileRef;
84   // last time the zip file was modified
85
private long _jarLastModified;
86
87   // file to be closed
88
private SoftReference JavaDoc<JarFile JavaDoc> _closeJarFileRef;
89
90   /**
91    * Creates a new Jar.
92    *
93    * @param path canonical path
94    */

95   private Jar(Path backing)
96   {
97     _backing = backing;
98   }
99
100   /**
101    * Return a Jar for the path. If the backing already exists, return
102    * the old jar.
103    */

104   static Jar create(Path backing)
105   {
106     if (_jarCache == null) {
107       int size = 256;
108
109       Integer JavaDoc iSize = _jarSize.get();
110
111       if (iSize != null)
112         size = iSize.intValue();
113       
114       _jarCache = new LruCache<Path,Jar>(size);
115     }
116     
117     Jar jar = _jarCache.get(backing);
118     if (jar == null) {
119       jar = new Jar(backing);
120       jar = _jarCache.putIfNew(backing, jar);
121     }
122     
123     return jar;
124   }
125
126   /**
127    * Return a Jar for the path. If the backing already exists, return
128    * the old jar.
129    */

130   static Jar getJar(Path backing)
131   {
132     if (_jarCache != null)
133       return _jarCache.get(backing);
134     else
135       return null;
136   }
137
138   /**
139    * Return a Jar for the path. If the backing already exists, return
140    * the old jar.
141    */

142   public static PersistentDependency createDepend(Path backing)
143   {
144     Jar jar = create(backing);
145
146     return jar.getDepend();
147   }
148
149   /**
150    * Return a Jar for the path. If the backing already exists, return
151    * the old jar.
152    */

153   public static PersistentDependency createDepend(Path backing, long digest)
154   {
155     Jar jar = create(backing);
156
157     return new JarDigestDepend(jar.getJarDepend(), digest);
158   }
159
160   /**
161    * Returns the backing path.
162    */

163   Path getBacking()
164   {
165     return _backing;
166   }
167
168   /**
169    * Returns the dependency.
170    */

171   public PersistentDependency getDepend()
172   {
173     return getJarDepend();
174   }
175
176   /**
177    * Returns the dependency.
178    */

179   private JarDepend getJarDepend()
180   {
181     if (_depend == null || _depend.isModified())
182       _depend = new JarDepend(new Depend(getBacking()));
183
184     return _depend;
185   }
186
187   /**
188    * Returns true if the entry is a file in the jar.
189    *
190    * @param path the path name inside the jar.
191    */

192   public Manifest JavaDoc getManifest()
193     throws IOException JavaDoc
194   {
195     Manifest JavaDoc manifest;
196
197     synchronized (this) {
198       JarFile JavaDoc jarFile = getJarFile();
199
200       if (jarFile == null)
201     manifest = null;
202       else
203     manifest = jarFile.getManifest();
204     }
205
206     closeJarFile();
207
208     return manifest;
209   }
210
211   /**
212    * Returns any certificates.
213    */

214   public Certificate JavaDoc []getCertificates(String JavaDoc path)
215   {
216     if (path.length() > 0 && path.charAt(0) == '/')
217       path = path.substring(1);
218
219     try {
220       if (! _backing.canRead())
221     return null;
222       
223       JarFile JavaDoc jarFile = new JarFile JavaDoc(_backing.getNativePath());
224       JarEntry JavaDoc entry;
225       InputStream JavaDoc is = null;
226
227       try {
228     entry = (JarEntry JavaDoc) jarFile.getEntry(path);
229       
230     if (entry != null) {
231       is = jarFile.getInputStream(entry);
232
233       while (is.skip(65536) > 0) {
234       }
235
236       is.close();
237
238       return entry.getCertificates();
239     }
240       } finally {
241     jarFile.close();
242       }
243     } catch (IOException JavaDoc e) {
244       log.log(Level.FINE, e.toString(), e);
245
246       return null;
247     }
248
249     return null;
250   }
251
252   /**
253    * Returns true if the entry exists in the jar.
254    *
255    * @param path the path name inside the jar.
256    */

257   public boolean exists(String JavaDoc path)
258   {
259     boolean exists;
260
261     synchronized (this) {
262       exists = getSafeJarEntry(path) != null;
263     }
264
265     closeJarFile();
266
267     return exists;
268   }
269
270   /**
271    * Returns true if the entry is a directory in the jar.
272    *
273    * @param path the path name inside the jar.
274    */

275   public boolean isDirectory(String JavaDoc path)
276   {
277     boolean isDirectory;
278
279     synchronized (this) {
280       if (! path.endsWith("/"))
281     path = path + '/';
282     
283       ZipEntry JavaDoc entry = getSafeJarEntry(path);
284
285       if (entry == null && (path.equals("/") || path.equals("")))
286     isDirectory = true;
287       else
288     isDirectory = entry != null && entry.isDirectory();
289     }
290
291     closeJarFile();
292
293     return isDirectory;
294   }
295
296   /**
297    * Returns true if the entry is a file in the jar.
298    *
299    * @param path the path name inside the jar.
300    */

301   public boolean isFile(String JavaDoc path)
302   {
303     boolean isFile;
304
305     synchronized (this) {
306       ZipEntry JavaDoc entry = getSafeJarEntry(path);
307
308       isFile = entry != null && ! entry.isDirectory();
309     }
310
311     closeJarFile();
312
313     return isFile;
314   }
315
316   /**
317    * Returns the length of the entry in the jar file.
318    *
319    * @param path full path to the jar entry
320    * @return the length of the entry
321    */

322   public long getLastModified(String JavaDoc path)
323   {
324     long lastModified;
325
326     synchronized (this) {
327       ZipEntry JavaDoc entry = getSafeJarEntry(path);
328
329       if (entry == null)
330     lastModified = 0;
331       else
332     lastModified = _lastModified;
333     }
334
335     closeJarFile();
336
337     return lastModified;
338   }
339
340   /**
341    * Returns the length of the entry in the jar file.
342    *
343    * @param path full path to the jar entry
344    * @return the length of the entry
345    */

346   public long getLength(Path path)
347   {
348     long length;
349
350     synchronized (this) {
351       ZipEntry JavaDoc entry = getSafeJarEntry(path.getPath());
352
353       if (entry == null)
354     length = -1;
355       else
356     length = entry.getSize();
357     }
358
359     closeJarFile();
360
361     return length;
362   }
363
364   /**
365    * Readable if the jar is readable and the path refers to a file.
366    */

367   public boolean canRead(String JavaDoc path)
368   {
369     boolean canRead;
370
371     synchronized (this) {
372       canRead = _backing.canRead() && isFile(path);
373     }
374
375     closeJarFile();
376
377     return canRead;
378   }
379
380   /**
381    * Can't write to jars.
382    */

383   public boolean canWrite(String JavaDoc path)
384   {
385     return false;
386   }
387
388   /**
389    * Lists all the files in this directory.
390    */

391   public String JavaDoc []list(String JavaDoc pathName) throws IOException JavaDoc
392   {
393     if (pathName.length() > 0 && ! pathName.endsWith("/"))
394       pathName = pathName + "/";
395     if (pathName.startsWith("/"))
396       pathName = pathName.substring(1);
397
398     ArrayList JavaDoc<String JavaDoc> list = new ArrayList JavaDoc<String JavaDoc>();
399
400     String JavaDoc []result = null;
401
402     synchronized (this) {
403       JarFile JavaDoc jarFile = getJarFile();
404
405       if (jarFile != null) {
406     Enumeration JavaDoc e = jarFile.entries();
407     while (e.hasMoreElements()) {
408       ZipEntry JavaDoc entry = (ZipEntry JavaDoc) e.nextElement();
409       String JavaDoc name = entry.getName();
410
411       if (name.startsWith(pathName) && ! name.equals(pathName)) {
412         String JavaDoc subName = name.substring(pathName.length());
413
414         int p = subName.indexOf('/');
415           
416         if (p < 0)
417           list.add(subName);
418         else if (p == subName.length() - 1)
419           list.add(subName.substring(0, p));
420       }
421     }
422
423     result = (String JavaDoc []) list.toArray(new String JavaDoc[list.size()]);
424       }
425     }
426
427     closeJarFile();
428
429     if (result != null) {
430       return result;
431     }
432     
433     ReadStream backingIs = _backing.openRead();
434     ZipInputStream JavaDoc is = new ZipInputStream JavaDoc(backingIs);
435
436     try {
437       ZipEntry JavaDoc entry;
438     
439       while ((entry = is.getNextEntry()) != null) {
440     String JavaDoc name = entry.getName();
441
442     if (name.startsWith(pathName) && ! name.equals(pathName)) {
443       String JavaDoc subName = name.substring(pathName.length());
444
445       int p = subName.indexOf('/');
446             
447       if (p < 0)
448         list.add(subName);
449       else if (p == subName.length() - 1)
450         list.add(subName.substring(0, p));
451     }
452       }
453     } finally {
454       is.close();
455       backingIs.close();
456     }
457
458     return (String JavaDoc []) list.toArray(new String JavaDoc[list.size()]);
459   }
460
461   /**
462    * Opens a stream to an entry in the jar.
463    *
464    * @param path relative path into the jar.
465    */

466   public StreamImpl openReadImpl(Path path) throws IOException JavaDoc
467   {
468     String JavaDoc pathName = path.getPath();
469
470     if (pathName.length() > 0 && pathName.charAt(0) == '/')
471       pathName = pathName.substring(1);
472
473     ZipFile JavaDoc zipFile = new ZipFile JavaDoc(_backing.getNativePath());
474     ZipEntry JavaDoc entry;
475     InputStream JavaDoc is = null;
476
477     try {
478       entry = zipFile.getEntry(pathName);
479       if (entry != null) {
480     is = zipFile.getInputStream(entry);
481
482     return new ZipStreamImpl(zipFile, is, null, path);
483       }
484       else {
485     throw new FileNotFoundException JavaDoc(path.toString());
486       }
487     } finally {
488       if (is == null) {
489     zipFile.close();
490       }
491     }
492   }
493
494   public String JavaDoc toString()
495   {
496     return _backing.toString();
497   }
498
499   /**
500    * Clears all the cached files in the jar. Needed to avoid some
501    * windows NT issues.
502    */

503   public static void clearJarCache()
504   {
505     LruCache<Path,Jar> jarCache = _jarCache;
506     
507     if (jarCache == null)
508       return;
509
510     ArrayList JavaDoc<Jar> jars = new ArrayList JavaDoc<Jar>();
511     
512     synchronized (jarCache) {
513       Iterator JavaDoc<Jar> iter = jarCache.values();
514       
515       while (iter.hasNext())
516     jars.add(iter.next());
517     }
518
519     for (int i = 0; i < jars.size(); i++) {
520       Jar jar = jars.get(i);
521         
522       if (jar != null)
523     jar.clearCache();
524     }
525   }
526
527   /**
528    * Clears any cached JarFile.
529    */

530   public void clearCache()
531   {
532     JarFile JavaDoc jarFile = null;
533
534     synchronized (this) {
535       SoftReference JavaDoc<JarFile JavaDoc> jarFileRef = _jarFileRef;
536       _jarFileRef = null;
537       
538       if (jarFileRef != null)
539     jarFile = jarFileRef.get();
540     }
541       
542     try {
543       if (jarFile != null)
544     jarFile.close();
545     } catch (Exception JavaDoc e) {
546     }
547   }
548
549   /**
550    * Returns the named jar entry.
551    *
552    * @param path name in the jar file of the path.
553    *
554    * @return the jar entry or null.
555    */

556   private ZipEntry JavaDoc getSafeJarEntry(String JavaDoc path)
557   {
558     try {
559       return getJarEntry(path);
560     } catch (Throwable JavaDoc e) {
561       _jarLastModified = 0;
562
563       try {
564     closeJarFile();
565       } catch (Throwable JavaDoc e1) {
566       }
567       
568       try {
569     return getJarEntry(path);
570       } catch (Throwable JavaDoc e1) {
571     log.log(Level.INFO, e1.toString(), e1);
572       
573     return null;
574       }
575     }
576   }
577
578   /**
579    * Returns the jar entry. Since the entries are cached, this needs to
580    * reload the cache on a jar file change.
581    *
582    * @param path name in the jar file of the path.
583    */

584   private ZipEntry JavaDoc getJarEntry(String JavaDoc path)
585     throws IOException JavaDoc
586   {
587     if (path.startsWith("/"))
588       path = path.substring(1);
589     
590     JarFile JavaDoc jarFile = getJarFile();
591     
592     if (jarFile != null)
593       return jarFile.getEntry(path);
594     else
595       return null;
596   }
597
598   /**
599    * Returns the Java ZipFile for this Jar. Accessing the entries with
600    * the ZipFile is faster than scanning through them.
601    *
602    * getJarFile is not thread safe.
603    */

604   private JarFile JavaDoc getJarFile()
605     throws IOException JavaDoc
606   {
607     JarFile JavaDoc jarFile = null;
608     
609     if (getLastModifiedImpl() == _jarLastModified) {
610       SoftReference JavaDoc<JarFile JavaDoc> jarFileRef = _jarFileRef;
611
612       if (jarFileRef != null) {
613     jarFile = jarFileRef.get();
614     
615     if (jarFile != null)
616       return jarFile;
617       }
618     }
619
620     SoftReference JavaDoc<JarFile JavaDoc> oldJarRef = _jarFileRef;
621     _jarFileRef = null;
622
623     JarFile JavaDoc oldFile = null;
624     if (oldJarRef == null) {
625     }
626     else if (_closeJarFileRef == null)
627       _closeJarFileRef = oldJarRef;
628     else
629       oldFile = oldJarRef.get();
630
631     if (oldFile != null) {
632       try {
633     oldFile.close();
634       } catch (Throwable JavaDoc e) {
635     e.printStackTrace();
636       }
637     }
638
639     if (_backing.getScheme().equals("file") && _backing.canRead()) {
640       jarFile = new JarFile JavaDoc(_backing.getNativePath());
641
642       _jarFileRef = new SoftReference JavaDoc<JarFile JavaDoc>(jarFile);
643       _jarLastModified = getLastModifiedImpl();
644     }
645
646     return jarFile;
647   }
648
649   /**
650    * Returns the last modified time for the path.
651    *
652    * @param path path into the jar.
653    *
654    * @return the last modified time of the jar in milliseconds.
655    */

656   private long getLastModifiedImpl()
657   {
658     long now = Alarm.getCurrentTime();
659
660     if (now == _lastTime)
661       return _lastModified;
662
663     long oldLastModified = _lastModified;
664     long oldLength = _length;
665     
666     _lastModified = _backing.getLastModified();
667     _length = _backing.getLength();
668     _lastTime = now;
669
670     // If the file has changed, close the old file
671
if (_lastModified != oldLastModified || _length != oldLength) {
672       SoftReference JavaDoc<JarFile JavaDoc> oldFileRef = _jarFileRef;
673     
674       _jarFileRef = null;
675       _jarLastModified = 0;
676
677       JarFile JavaDoc oldCloseFile = null;
678       if (_closeJarFileRef != null)
679     oldCloseFile = _closeJarFileRef.get();
680
681       _closeJarFileRef = oldFileRef;
682
683       if (oldCloseFile != null) {
684     try {
685       oldCloseFile.close();
686     } catch (Throwable JavaDoc e) {
687     }
688       }
689     }
690
691     return _lastModified;
692   }
693
694   /**
695    * Closes any old jar waiting for close.
696    */

697   private void closeJarFile()
698   {
699     JarFile JavaDoc jarFile = null;
700     
701     synchronized (this) {
702       if (_closeJarFileRef != null)
703     jarFile = _closeJarFileRef.get();
704       _closeJarFileRef = null;
705     }
706     
707     if (jarFile != null) {
708       try {
709     jarFile.close();
710       } catch (IOException JavaDoc e) {
711     log.log(Level.WARNING, e.toString(), e);
712       }
713     }
714   }
715
716   public void close()
717   {
718     removeEvent();
719   }
720   
721   public void removeEvent()
722   {
723     JarFile JavaDoc jarFile = null;
724     
725     synchronized (this) {
726       if (_jarFileRef != null)
727     jarFile = _jarFileRef.get();
728
729       _jarFileRef = null;
730     }
731
732     try {
733       if (jarFile != null)
734     jarFile.close();
735     } catch (Throwable JavaDoc e) {
736       log.log(Level.FINE, e.toString(), e);
737     }
738     
739     closeJarFile();
740   }
741
742   public boolean equals(Object JavaDoc o)
743   {
744     if (this == o)
745       return true;
746     else if (o == null || ! getClass().equals(o.getClass()))
747       return false;
748
749     Jar jar = (Jar) o;
750
751     return _backing.equals(jar._backing);
752   }
753
754   /**
755    * StreamImpl to read from a ZIP file.
756    */

757   static class ZipStreamImpl extends StreamImpl {
758     private ZipFile JavaDoc _zipFile;
759     private InputStream JavaDoc _zis;
760     private InputStream JavaDoc _is;
761
762     /**
763      * Create the new stream impl.
764      *
765      * @param zis the underlying zip stream.
766      * @param is the backing stream.
767      * @param path the path to the jar entry.
768      */

769     ZipStreamImpl(ZipFile JavaDoc file, InputStream JavaDoc zis, InputStream JavaDoc is, Path path)
770     {
771       _zipFile = file;
772       _zis = zis;
773       _is = is;
774       
775       setPath(path);
776     }
777
778     /**
779      * Returns true since this is a read stream.
780      */

781     public boolean canRead() { return true; }
782  
783     public int getAvailable() throws IOException JavaDoc
784     {
785       if (_zis == null)
786         return -1;
787       else
788         return _zis.available();
789     }
790  
791     public int read(byte []buf, int off, int len) throws IOException JavaDoc
792     {
793       int readLen = _zis.read(buf, off, len);
794  
795       return readLen;
796     }
797  
798     public void close() throws IOException JavaDoc
799     {
800       ZipFile JavaDoc zipFile = _zipFile;
801       _zipFile = null;
802       
803       InputStream JavaDoc zis = _zis;
804       _zis = null;
805       
806       InputStream JavaDoc is = _is;
807       _is = null;
808       
809       try {
810     if (zis != null)
811       zis.close();
812       } catch (Throwable JavaDoc e) {
813       }
814
815       try {
816     if (zipFile != null)
817       zipFile.close();
818       } catch (Throwable JavaDoc e) {
819       }
820
821       if (is != null)
822         is.close();
823     }
824
825     protected void finalize()
826       throws IOException JavaDoc
827     {
828       close();
829     }
830   }
831
832   static class JarDepend extends CachedDependency
833     implements PersistentDependency {
834     private Depend _depend;
835     private boolean _isDigestModified;
836     
837     /**
838      * Create a new dependency.
839      *
840      * @param source the source file
841      */

842     JarDepend(Depend depend)
843     {
844       _depend = depend;
845     }
846     
847     /**
848      * Create a new dependency.
849      *
850      * @param source the source file
851      */

852     JarDepend(Depend depend, long digest)
853     {
854       _depend = depend;
855
856       _isDigestModified = _depend.getDigest() != digest;
857     }
858
859     /**
860      * Returns the underlying depend.
861      */

862     Depend getDepend()
863     {
864       return _depend;
865     }
866
867     /**
868      * Returns true if the dependency is modified.
869      */

870     public boolean isModifiedImpl()
871     {
872       return _isDigestModified || _depend.isModified();
873     }
874
875     /**
876      * Returns the string to recreate the Dependency.
877      */

878     public String JavaDoc getJavaCreateString()
879     {
880       String JavaDoc sourcePath = _depend.getPath().getPath();
881       long digest = _depend.getDigest();
882       
883       return ("new com.caucho.vfs.Jar.createDepend(" +
884           "com.caucho.vfs.Vfs.lookup(\"" + sourcePath + "\"), " +
885           digest + "L)");
886     }
887
888     public String JavaDoc toString()
889     {
890       return "Jar$JarDepend[" + _depend.getPath() + "]";
891     }
892   }
893
894   static class JarDigestDepend implements PersistentDependency {
895     private JarDepend _jarDepend;
896     private Depend _depend;
897     private boolean _isDigestModified;
898     
899     /**
900      * Create a new dependency.
901      *
902      * @param source the source file
903      */

904     JarDigestDepend(JarDepend jarDepend, long digest)
905     {
906       _jarDepend = jarDepend;
907       _depend = jarDepend.getDepend();
908
909       _isDigestModified = _depend.getDigest() != digest;
910     }
911
912     /**
913      * Returns true if the dependency is modified.
914      */

915     public boolean isModified()
916     {
917       return _isDigestModified || _jarDepend.isModified();
918     }
919
920     /**
921      * Returns the string to recreate the Dependency.
922      */

923     public String JavaDoc getJavaCreateString()
924     {
925       String JavaDoc sourcePath = _depend.getPath().getPath();
926       long digest = _depend.getDigest();
927       
928       return ("new com.caucho.vfs.Jar.createDepend(" +
929           "com.caucho.vfs.Vfs.lookup(\"" + sourcePath + "\"), " +
930           digest + "L)");
931     }
932   }
933 }
934
Popular Tags