KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openide > filesystems > JarFileSystem


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.openide.filesystems;
21
22 import java.beans.PropertyVetoException JavaDoc;
23 import java.io.ByteArrayInputStream JavaDoc;
24 import java.io.ByteArrayOutputStream JavaDoc;
25 import java.io.File JavaDoc;
26 import java.io.FileInputStream JavaDoc;
27 import java.io.FileOutputStream JavaDoc;
28 import java.io.IOException JavaDoc;
29 import java.io.InputStream JavaDoc;
30 import java.io.ObjectInputStream JavaDoc;
31 import java.io.OutputStream JavaDoc;
32 import java.io.UnsupportedEncodingException JavaDoc;
33 import java.lang.ref.Reference JavaDoc;
34 import java.lang.ref.SoftReference JavaDoc;
35 import java.lang.ref.WeakReference JavaDoc;
36 import java.util.Collection JavaDoc;
37 import java.util.Collections JavaDoc;
38 import java.util.Enumeration JavaDoc;
39 import java.util.HashMap JavaDoc;
40 import java.util.Iterator JavaDoc;
41 import java.util.Map JavaDoc;
42 import java.util.jar.Attributes JavaDoc;
43 import java.util.jar.JarEntry JavaDoc;
44 import java.util.jar.JarFile JavaDoc;
45 import java.util.jar.Manifest JavaDoc;
46 import java.util.zip.ZipException JavaDoc;
47 import org.openide.util.Enumerations;
48 import org.openide.util.RequestProcessor;
49 import org.openide.util.Utilities;
50
51 /** A virtual filesystem based on a JAR archive.
52 * <p>For historical reasons many AbstractFileSystem.* methods are implemented
53 * as protected in this class. Do not call them! Subclasses might override
54 * them, or (better) use delegation.
55  * <p><strong>Most module code should never create an instance of this class directly.</strong>
56  * Use {@link FileUtil#getArchiveRoot(FileObject)} instead.</p>
57 * @author Jan Jancura, Jaroslav Tulach, Petr Hamernik, Radek Matous
58 */

59 public class JarFileSystem extends AbstractFileSystem {
60     /** generated Serialized Version UID */
61     static final long serialVersionUID = -98124752801761145L;
62
63     /** One request proccesor shared for all instances of JarFileSystem*/
64     private static RequestProcessor req = new RequestProcessor("JarFs - modification watcher"); // NOI18N
65

66     /** Controlls the LocalFileSystem's automatic refresh.
67     * If the refresh time interval is set from the System.property, than this value is used.
68     * Otherwise, the refresh time interval is set to 0, which means the refresh is disabled. */

69     private static final int REFRESH_TIME = Integer.getInteger("org.openide.filesystems.JarFileSystem.REFRESH_TIME", 0)
70                                                    .intValue(); // NOI18N
71

72     /** maxsize for passing ByteArrayInputStream*/
73     private static final long MEM_STREAM_SIZE = 100000;
74
75     /**
76     * Opened zip file of this filesystem is stored here or null.
77     */

78     private transient JarFile JavaDoc jar;
79
80     /** Manifest file for jar
81     */

82     private transient Manifest JavaDoc manifest;
83
84     /** Archive file.1
85     */

86     private File JavaDoc root = new File JavaDoc("."); // NOI18N
87

88     /** Watches modification on root file */
89     private transient RequestProcessor.Task watcherTask = null;
90     private transient RequestProcessor.Task closeTask = null;
91     private transient long lastModification = 0;
92
93     /*Should help to prevent closing JarFile if anybody has InputStream. Also this variable
94      is used as object for synchronization: synchronized(closeSync)*/

95     private transient Object JavaDoc closeSync = new Object JavaDoc();
96     private int checkTime = REFRESH_TIME;
97
98     /** number of FileObjects in using. If no one is used then the cached data
99      * is freed */

100     private transient long aliveCount = 0;
101
102     /** Cached image of JarFile capable of answering queries on type and children.
103      * There is a strong reference held while there is a living FileObject
104      * and a SoftReference for caching after all FOs are freed.*/

105     private transient Cache strongCache;
106
107     /** The soft part of the cache reference. For simplicity never null*/
108     private transient Reference JavaDoc<Cache> softCache = new SoftReference JavaDoc<Cache>(null);
109     private transient FileObject foRoot;
110     private transient FileChangeListener fcl;
111
112     /**
113     * Default constructor.
114      * <p><strong>Most module code should never create an instance of this class directly.</strong>
115      * Use {@link FileUtil#getArchiveRoot(FileObject)} instead.</p>
116     */

117     public JarFileSystem() {
118         Impl impl = new Impl(this);
119         this.list = impl;
120         this.info = impl;
121         this.change = impl;
122         this.attr = impl;
123     }
124
125     /**
126     * Constructor that can provide own capability for the filesystem.
127     * @param cap the capability
128      * @deprecated Useless.
129     */

130     @Deprecated JavaDoc
131     public JarFileSystem(FileSystemCapability cap) {
132         this();
133         setCapability(cap);
134     }
135
136     /* Creates Reference. In FileSystem, which subclasses AbstractFileSystem, you can overload method
137      * createReference(FileObject fo) to achieve another type of Reference (weak, strong etc.)
138      * @param fo is FileObject. It`s reference yourequire to get.
139      * @return Reference to FileObject
140      */

141     protected <T extends FileObject> Reference JavaDoc<T> createReference(T fo) {
142         aliveCount++;
143
144         if ((checkTime > 0) && (watcherTask == null)) {
145             watcherTask = req.post(watcherTask(), checkTime);
146         }
147
148         return new Ref<T>(fo);
149     }
150
151     private void freeReference() {
152         aliveCount--;
153
154         // Nobody uses this JarFileSystem => stop watcher, close JarFile and throw away cache.
155
if (aliveCount == 0) {
156             if (watcherTask != null) {
157                 watcherTask.cancel();
158                 watcherTask = null;
159             }
160
161             strongCache = null; // no more active FO, keep only soft ref
162
closeCurrentRoot(false);
163         }
164     }
165
166     /** Get the JAR manifest.
167     * It will be lazily initialized.
168     * @return parsed manifest file for this archive
169     */

170     public Manifest JavaDoc getManifest() {
171         if (manifest == null) {
172             try {
173                 synchronized (closeSync) {
174                     JarFile JavaDoc j = reOpenJarFile();
175                     manifest = (j == null) ? null : j.getManifest();
176                     manifest = (manifest == null) ? null : new Manifest JavaDoc(manifest);
177                 }
178             } catch (IOException JavaDoc ex) {
179             } finally {
180                 closeCurrentRoot(false);
181             }
182
183             if (manifest == null) {
184                 manifest = new Manifest JavaDoc();
185             }
186         }
187
188         return manifest;
189     }
190
191     /**
192     * Set name of the ZIP/JAR file.
193     * @param aRoot path to new ZIP or JAR file
194     * @throws IOException if the file is not valid
195     */

196     public void setJarFile(final File JavaDoc aRoot) throws IOException JavaDoc, PropertyVetoException JavaDoc {
197         setJarFile(aRoot, true);
198     }
199     
200     @SuppressWarnings JavaDoc("deprecation") // need to set it for compat
201
private void _setSystemName(String JavaDoc s) throws PropertyVetoException JavaDoc {
202         setSystemName(s);
203     }
204
205     private void setJarFile(final File JavaDoc aRoot, boolean refreshRoot)
206     throws IOException JavaDoc, PropertyVetoException JavaDoc {
207         if (!aRoot.equals(FileUtil.normalizeFile(aRoot))) {
208             throw new IllegalArgumentException JavaDoc(
209                 "Parameter aRoot was not " + // NOI18N
210
"normalized. Was " + aRoot + " instead of " + FileUtil.normalizeFile(aRoot)
211             ); // NOI18N
212
}
213
214         FileObject newRoot = null;
215         String JavaDoc oldDisplayName = getDisplayName();
216
217         if (getRefreshTime() > 0) {
218             setRefreshTime(0);
219         }
220
221         if (aRoot == null) {
222             FSException.io("EXC_NotValidFile", aRoot); // NOI18N
223
}
224
225         if (!aRoot.exists()) {
226             FSException.io("EXC_FileNotExists", aRoot.getAbsolutePath()); // NOI18N
227
}
228
229         if (!aRoot.canRead()) {
230             FSException.io("EXC_CanntRead", aRoot.getAbsolutePath()); // NOI18N
231
}
232
233         if (!aRoot.isFile()) {
234             FSException.io("EXC_NotValidFile", aRoot.getAbsolutePath()); // NOI18N
235
}
236
237         String JavaDoc s;
238         s = aRoot.getAbsolutePath();
239         s = s.intern();
240
241         JarFile JavaDoc tempJar = null;
242
243         try {
244             tempJar = new JarFile JavaDoc(s);
245         } catch (ZipException JavaDoc e) {
246             FSException.io("EXC_NotValidJarFile2", e.getLocalizedMessage(), s); // NOI18N
247
}
248
249         synchronized (closeSync) {
250             _setSystemName(s);
251
252             closeCurrentRoot(false);
253             jar = tempJar;
254             root = new File JavaDoc(s);
255
256             if (refreshRoot) {
257                 strongCache = null;
258                 softCache.clear();
259                 aliveCount = 0;
260                 newRoot = refreshRoot();
261                 manifest = null;
262                 lastModification = 0;
263
264                 if (newRoot != null) {
265                     firePropertyChange("root", null, newRoot); // NOI18N
266
}
267             }
268         }
269
270         firePropertyChange(PROP_DISPLAY_NAME, oldDisplayName, getDisplayName());
271
272         foRoot = FileUtil.toFileObject(root);
273
274         if ((foRoot != null) && (fcl == null)) {
275             fcl = new FileChangeAdapter() {
276                         public void fileChanged(FileEvent fe) {
277                             if (watcherTask == null) {
278                                 parse(true);
279                             }
280                         }
281
282                         public void fileRenamed(FileRenameEvent fe) {
283                             File JavaDoc f = FileUtil.toFile(fe.getFile());
284
285                             if ((f != null) && !f.equals(aRoot)) {
286                                 try {
287                                     setJarFile(f, false);
288                                 } catch (IOException JavaDoc iex) {
289                                     ExternalUtil.exception(iex);
290                                 } catch (PropertyVetoException JavaDoc pvex) {
291                                     ExternalUtil.exception(pvex);
292                                 }
293                             }
294                         }
295
296                         public void fileDeleted(FileEvent fe) {
297                             Enumeration JavaDoc<? extends FileObject> en = existingFileObjects(getRoot());
298
299                             while (en.hasMoreElements()) {
300                                 AbstractFolder fo = (AbstractFolder) en.nextElement();
301                                 fo.validFlag = false;
302                                 fo.fileDeleted0(new FileEvent(fo));
303                             }
304
305                             refreshRoot();
306                         }
307                     };
308
309             if (refreshRoot) {
310                 foRoot.addFileChangeListener(FileUtil.weakFileChangeListener(fcl, foRoot));
311             }
312         }
313     }
314
315     /** Get the file path for the ZIP or JAR file.
316     * @return the file path
317     */

318     public File JavaDoc getJarFile() {
319         return root;
320     }
321
322     /*
323     * Provides name of the system that can be presented to the user.
324     * @return user presentable name of the filesystem
325     */

326     public String JavaDoc getDisplayName() {
327         return (root != null) ? root.getAbsolutePath() : getString("JAR_UnknownJar");
328     }
329
330     /** This filesystem is read-only.
331     * @return <code>true</code>
332     */

333     public boolean isReadOnly() {
334         return true;
335     }
336
337     /* Closes associated JAR file on cleanup, if possible. */
338     public void removeNotify() {
339         closeCurrentRoot(true);
340     }
341
342     /* initialization of jar variable, that is necessary after JarFileSystem was removed from Repository */
343
344     // public void addNotify () {
345
// super.addNotify ();
346
// }
347

348     /** Prepare environment for external compilation or execution.
349     * <P>
350     * Adds name of the ZIP/JAR file, if it has been set, to the class path.
351      * @deprecated Useless.
352     */

353     @Deprecated JavaDoc
354     public void prepareEnvironment(Environment env) {
355         if (root != null) {
356             env.addClassPath(root.getAbsolutePath());
357         }
358     }
359
360     //
361
// List
362
//
363
protected String JavaDoc[] children(String JavaDoc name) {
364         Cache cache = getCache();
365
366         return cache.getChildrenOf(name);
367     }
368
369     //
370
// Change
371
//
372
protected void createFolder(String JavaDoc name) throws java.io.IOException JavaDoc {
373         throw new IOException JavaDoc();
374     }
375
376     protected void createData(String JavaDoc name) throws IOException JavaDoc {
377         throw new IOException JavaDoc();
378     }
379
380     protected void rename(String JavaDoc oldName, String JavaDoc newName)
381     throws IOException JavaDoc {
382         throw new IOException JavaDoc();
383     }
384
385     protected void delete(String JavaDoc name) throws IOException JavaDoc {
386         throw new IOException JavaDoc();
387     }
388
389     //
390
// Info
391
//
392
protected java.util.Date JavaDoc lastModified(String JavaDoc name) {
393         try {
394             return new java.util.Date JavaDoc(getEntry(name).getTime());
395         } finally {
396             closeCurrentRoot(false);
397         }
398     }
399
400     protected boolean folder(String JavaDoc name) {
401         if ("".equals(name)) {
402             return true; // NOI18N
403
}
404
405         Cache cache = getCache();
406
407         return cache.isFolder(name);
408     }
409
410     protected boolean readOnly(String JavaDoc name) {
411         return true;
412     }
413
414     protected String JavaDoc mimeType(String JavaDoc name) {
415         return null;
416     }
417
418     protected long size(String JavaDoc name) {
419         long retVal = getEntry(name).getSize();
420         closeCurrentRoot(false);
421
422         return (retVal == -1) ? 0 : retVal;
423     }
424
425     private InputStream JavaDoc getMemInputStream(JarFile JavaDoc jf, JarEntry JavaDoc je)
426     throws IOException JavaDoc {
427         InputStream JavaDoc is = getInputStream4336753(jf, je);
428         ByteArrayOutputStream JavaDoc os = new ByteArrayOutputStream JavaDoc(is.available());
429
430         try {
431             FileUtil.copy(is, os);
432         } finally {
433             os.close();
434         }
435
436         return new ByteArrayInputStream JavaDoc(os.toByteArray());
437     }
438
439     private InputStream JavaDoc getTemporaryInputStream(JarFile JavaDoc jf, JarEntry JavaDoc je, boolean forceRecreate)
440     throws IOException JavaDoc {
441         String JavaDoc filePath = jf.getName();
442         String JavaDoc entryPath = je.getName();
443         StringBuffer JavaDoc jarCacheFolder = new StringBuffer JavaDoc("jarfscache"); //NOI18N
444
jarCacheFolder.append(System.getProperty("user.name")).append("/"); //NOI18N
445

446         File JavaDoc jarfscache = new File JavaDoc(System.getProperty("java.io.tmpdir"), jarCacheFolder.toString()); //NOI18N
447

448         if (!jarfscache.exists()) {
449             jarfscache.mkdirs();
450         }
451
452         File JavaDoc f = new File JavaDoc(jarfscache, temporaryName(filePath, entryPath));
453
454         boolean createContent = !f.exists();
455
456         if (createContent) {
457             f.createNewFile();
458         } else {
459             forceRecreate |= (Math.abs((System.currentTimeMillis() - f.lastModified())) > 10000);
460         }
461
462         if (createContent || forceRecreate) {
463             // JDK 1.3 contains bug #4336753
464
//is = j.getInputStream (je);
465
InputStream JavaDoc is = getInputStream4336753(jf, je);
466
467             try {
468                 OutputStream JavaDoc os = new FileOutputStream JavaDoc(f);
469
470                 try {
471                     FileUtil.copy(is, os);
472                 } finally {
473                     os.close();
474                 }
475             } finally {
476                 is.close();
477             }
478         }
479
480         f.deleteOnExit();
481
482         return new FileInputStream JavaDoc(f);
483     }
484
485     private static String JavaDoc temporaryName(String JavaDoc filePath, String JavaDoc entryPath) {
486         String JavaDoc fileHash = String.valueOf(filePath.hashCode());
487         String JavaDoc entryHash = String.valueOf(entryPath.hashCode());
488
489         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
490         sb.append("f").append(fileHash).append("e").append(entryHash);
491
492         return sb.toString().replace('-', 'x'); //NOI18N
493
}
494
495     protected InputStream JavaDoc inputStream(String JavaDoc name) throws java.io.FileNotFoundException JavaDoc {
496         InputStream JavaDoc is = null;
497
498         try {
499             synchronized (closeSync) {
500                 JarFile JavaDoc j = reOpenJarFile();
501
502                 if (j != null) {
503                     JarEntry JavaDoc je = j.getJarEntry(name);
504
505                     if (je != null) {
506                         if (je.getSize() < MEM_STREAM_SIZE) {
507                             is = getMemInputStream(j, je);
508                         } else {
509                             is = getTemporaryInputStream(j, je, (strongCache != null));
510                         }
511                     }
512                 }
513             }
514         } catch (java.io.FileNotFoundException JavaDoc e) {
515             throw e;
516         } catch (IOException JavaDoc e) {
517             throw new java.io.FileNotFoundException JavaDoc(e.getMessage());
518         } catch (RuntimeException JavaDoc e) {
519             throw new java.io.FileNotFoundException JavaDoc(e.getMessage());
520         } finally {
521             closeCurrentRoot(false);
522         }
523
524         if (is == null) {
525             throw new java.io.FileNotFoundException JavaDoc(name);
526         }
527
528         return is;
529     }
530
531     // 4336753 workaround
532
private InputStream JavaDoc getInputStream4336753(JarFile JavaDoc j, JarEntry JavaDoc je)
533     throws IOException JavaDoc {
534         InputStream JavaDoc in = null;
535
536         while (in == null) {
537             try {
538                 in = j.getInputStream(je);
539
540                 break;
541             } catch (NullPointerException JavaDoc ex) {
542                 // ignore, it occured during reseting reused Inflanter
543
// try again until there will be no Inflanter to reuse
544
}
545         }
546
547         return in;
548     }
549
550     protected OutputStream JavaDoc outputStream(String JavaDoc name) throws java.io.IOException JavaDoc {
551         throw new IOException JavaDoc();
552     }
553
554     protected void lock(String JavaDoc name) throws IOException JavaDoc {
555         FSException.io("EXC_CannotLock", name, getDisplayName(), name); // NOI18N
556
}
557
558     protected void unlock(String JavaDoc name) {
559     }
560
561     protected void markUnimportant(String JavaDoc name) {
562     }
563
564     protected Object JavaDoc readAttribute(String JavaDoc name, String JavaDoc attrName) {
565         Attributes JavaDoc attr = getManifest().getAttributes(name);
566
567         try {
568             return (attr == null) ? null : attr.getValue(attrName);
569         } catch (IllegalArgumentException JavaDoc iax) {
570             return null;
571         }
572     }
573
574     protected void writeAttribute(String JavaDoc name, String JavaDoc attrName, Object JavaDoc value)
575     throws IOException JavaDoc {
576         throw new IOException JavaDoc();
577     }
578
579     protected Enumeration JavaDoc<String JavaDoc> attributes(String JavaDoc name) {
580         Attributes JavaDoc attr = getManifest().getAttributes(name);
581
582         if (attr != null) {
583             class ToString implements org.openide.util.Enumerations.Processor<Object JavaDoc, String JavaDoc> {
584                 public String JavaDoc process(Object JavaDoc obj, Collection JavaDoc<Object JavaDoc> ignore) {
585                     return obj.toString();
586                 }
587             }
588
589             return org.openide.util.Enumerations.convert(Collections.enumeration(attr.keySet()), new ToString());
590         } else {
591             return org.openide.util.Enumerations.empty();
592         }
593     }
594
595     protected void renameAttributes(String JavaDoc oldName, String JavaDoc newName) {
596     }
597
598     protected void deleteAttributes(String JavaDoc name) {
599     }
600
601     /** Close the jar file when we go away...*/
602     protected void finalize() throws Throwable JavaDoc {
603         super.finalize();
604         closeCurrentRoot(false);
605     }
606
607     /** Initializes the root of FS.
608     */

609     private void readObject(ObjectInputStream JavaDoc ois) throws IOException JavaDoc, ClassNotFoundException JavaDoc {
610         ois.defaultReadObject();
611         closeSync = new Object JavaDoc();
612         strongCache = null;
613         softCache = new SoftReference JavaDoc<Cache>(null);
614         aliveCount = 0;
615
616         try {
617             setJarFile(root);
618         } catch (PropertyVetoException JavaDoc ex) {
619             throw new IOException JavaDoc(ex.getMessage());
620         } catch (IOException JavaDoc iex) {
621             ExternalUtil.log(iex.getLocalizedMessage());
622         }
623     }
624
625     /** Must be called from synchronized block*/
626     private JarFile JavaDoc reOpenJarFile() throws IOException JavaDoc {
627         synchronized (closeSync) {
628             if (closeTask != null) {
629                 closeTask.cancel();
630             }
631
632             JarFile JavaDoc j = jar;
633
634             if (j != null) {
635                 return j;
636             }
637
638             if ((jar == null) && (root != null)) {
639                 jar = new JarFile JavaDoc(root);
640             }
641
642             return jar;
643         }
644     }
645
646     /** Performs a clean-up
647      * After close of JarFile must be always reference to JarFile set to null
648      */

649     private void closeCurrentRoot(boolean isRealClose) {
650         synchronized (closeSync) {
651             if (closeTask != null) {
652                 closeTask.cancel();
653             }
654
655             if (isRealClose) {
656                 realClose().run();
657             } else {
658                 closeTask = req.post(realClose(), 300);
659             }
660         }
661     }
662
663     private Runnable JavaDoc realClose() {
664         return new Runnable JavaDoc() {
665                 public void run() {
666                     synchronized (closeSync) {
667                         if (jar != null) {
668                             try {
669                                 jar.close();
670                             } catch (Exception JavaDoc exc) {
671                                 // ignore exception during closing, just log it
672
ExternalUtil.exception(exc);
673                             } finally {
674                                 jar = null;
675                                 closeTask = null;
676                             }
677                         }
678                     }
679                 }
680             };
681     }
682
683     private Cache getCache() {
684         Cache ret = strongCache;
685
686         if (ret == null) {
687             ret = softCache.get();
688         }
689
690         if (ret == null) {
691             ret = parse(false);
692         }
693
694         assert ret != null;
695
696         return ret;
697     }
698
699     /** refreshes children recursively.*/
700     private void refreshExistingFileObjects() {
701         Cache cache = getCache();
702         String JavaDoc[] empty = new String JavaDoc[0];
703
704         Enumeration JavaDoc<? extends FileObject> en = existingFileObjects(getRoot());
705
706         while (en.hasMoreElements()) {
707             AbstractFolder fo = (AbstractFolder) en.nextElement();
708             assert fo != null;
709
710             if (fo.isFolder() && !fo.isInitialized()) {
711                 continue;
712             }
713
714             String JavaDoc[] children = cache.getChildrenOf(fo.getPath());
715
716             if (children == null) {
717                 children = empty;
718             }
719
720             fo.refresh(null, null, true, true, children);
721         }
722     }
723
724     /**parses entries of JarFile into EntryCache hierarchical structure and sets
725      * lastModified to actual value.
726      */

727     private Cache parse(boolean refresh) {
728         // force watcher to reschedule us if not succesfull
729
JarFile JavaDoc j = null;
730         long start;
731
732         beginAtomicAction();
733
734         try {
735             synchronized (closeSync) {
736                 start = System.currentTimeMillis();
737
738                 lastModification = 0;
739                 closeCurrentRoot(false);
740
741                 for (int i = 0; i <= 2; i++) {
742                     try {
743                         j = reOpenJarFile();
744
745                         break;
746                     } catch (IOException JavaDoc ex) {
747                         if (i >= 2) {
748                             return Cache.INVALID;
749                         }
750
751                         continue;
752                     }
753                 }
754
755                 try {
756                     Enumeration JavaDoc<JarEntry JavaDoc> en = j.entries();
757                     Cache newCache = new Cache(en);
758                     lastModification = root.lastModified();
759                     strongCache = newCache;
760                     softCache = new SoftReference JavaDoc<Cache>(newCache);
761
762                     return newCache;
763                 } catch (Throwable JavaDoc t) {
764                     // jar is invalid; perhaps it's being rebuilt
765
// don't touch filesystem
766
return Cache.INVALID;
767                 }
768             }
769         } finally {
770             closeCurrentRoot(false);
771
772             if (refresh) {
773                 refreshExistingFileObjects();
774             }
775
776             if ((checkTime > 0) && (watcherTask == null)) {
777                 watcherTask = req.post(watcherTask(), checkTime);
778             }
779
780             finishAtomicAction();
781         }
782     }
783
784     /* Anonymous Runnable class - responsible for checking whether JarFile was modified => standalone thread.
785      * If JarFile was modified, parsing is invoked.
786      */

787     private Runnable JavaDoc watcherTask() {
788         return new Runnable JavaDoc() {
789                 public void run() {
790                     try {
791                         if (root == null) {
792                             return;
793                         }
794
795                         /** JarFile was modified => parse it and refresh existing FileObjects*/
796                         if (root.lastModified() != lastModification) {
797                             parse(true);
798                         }
799                     } finally {
800                         /** reschedule watcherTask*/
801                         if (watcherTask != null) {
802                             watcherTask.schedule(checkTime);
803                         }
804                     }
805                 }
806             };
807     }
808
809     /** Getter for entry.
810     */

811     private final JarEntry JavaDoc getEntry(String JavaDoc file) {
812         JarFile JavaDoc j = null;
813
814         try {
815             synchronized (closeSync) {
816                 j = reOpenJarFile();
817
818                 JarEntry JavaDoc je = j.getJarEntry(file);
819
820                 if (je != null) {
821                     return je;
822                 }
823             }
824         } catch (IOException JavaDoc iox) {
825         }
826
827         return new JarEntry JavaDoc(file);
828     }
829
830     /** Use soft-references to not throw away the data that quickly.
831      * JarFS if often queried for its FOs e.g. by java parser, which
832      * leaves the references immediately.
833      */

834     private class Ref<T extends FileObject> extends WeakReference JavaDoc<T> implements Runnable JavaDoc {
835         public Ref(T fo) {
836             super(fo, Utilities.activeReferenceQueue());
837         }
838
839         // do the cleanup
840
public void run() {
841             freeReference();
842         }
843     }
844
845     /** Implementation of all interfaces List, Change, Info and Attr
846     * that delegates to JarFileSystem
847     */

848     public static class Impl extends Object JavaDoc implements AbstractFileSystem.List, AbstractFileSystem.Info,
849         AbstractFileSystem.Change, AbstractFileSystem.Attr {
850         /** generated Serialized Version UID */
851         static final long serialVersionUID = -67233308132567232L;
852
853         /** the pointer to filesystem */
854         private JarFileSystem fs;
855
856         /** Constructor.
857         * @param fs the filesystem to delegate to
858         */

859         public Impl(JarFileSystem fs) {
860             this.fs = fs;
861         }
862
863         /*
864         *
865         * Scans children for given name
866         */

867         public String JavaDoc[] children(String JavaDoc name) {
868             return fs.children(name);
869         }
870
871         //
872
// Change
873
//
874

875         /*
876         * Creates new folder named name.
877         * @param name name of folder
878         * @throws IOException if operation fails
879         */

880         public void createFolder(String JavaDoc name) throws java.io.IOException JavaDoc {
881             fs.createFolder(name);
882         }
883
884         /*
885         * Create new data file.
886         *
887         * @param name name of the file
888         *
889         * @return the new data file object
890         * @exception IOException if the file cannot be created (e.g. already exists)
891         */

892         public void createData(String JavaDoc name) throws IOException JavaDoc {
893             fs.createData(name);
894         }
895
896         /*
897         * Renames a file.
898         *
899         * @param oldName old name of the file
900         * @param newName new name of the file
901         */

902         public void rename(String JavaDoc oldName, String JavaDoc newName)
903         throws IOException JavaDoc {
904             fs.rename(oldName, newName);
905         }
906
907         /*
908         * Delete the file.
909         *
910         * @param name name of file
911         * @exception IOException if the file could not be deleted
912         */

913         public void delete(String JavaDoc name) throws IOException JavaDoc {
914             fs.delete(name);
915         }
916
917         //
918
// Info
919
//
920

921         /*
922         *
923         * Get last modification time.
924         * @param name the file to test
925         * @return the date
926         */

927         public java.util.Date JavaDoc lastModified(String JavaDoc name) {
928             return fs.lastModified(name);
929         }
930
931         /*
932         * Test if the file is folder or contains data.
933         * @param name name of the file
934         * @return true if the file is folder, false otherwise
935         */

936         public boolean folder(String JavaDoc name) {
937             return fs.folder(name);
938         }
939
940         /*
941         * Test whether this file can be written to or not.
942         * @param name the file to test
943         * @return <CODE>true</CODE> if file is read-only
944         */

945         public boolean readOnly(String JavaDoc name) {
946             return fs.readOnly(name);
947         }
948
949         /*
950         * Get the MIME type of the file.
951         * Uses {@link FileUtil#getMIMEType}.
952         *
953         * @param name the file to test
954         * @return the MIME type textual representation, e.g. <code>"text/plain"</code>
955         */

956         public String JavaDoc mimeType(String JavaDoc name) {
957             return fs.mimeType(name);
958         }
959
960         /*
961         * Get the size of the file.
962         *
963         * @param name the file to test
964         * @return the size of the file in bytes or zero if the file does not contain data (does not
965         * exist or is a folder).
966         */

967         public long size(String JavaDoc name) {
968             return fs.size(name);
969         }
970
971         /*
972         * Get input stream.
973         *
974         * @param name the file to test
975         * @return an input stream to read the contents of this file
976         * @exception FileNotFoundException if the file does not exists or is invalid
977         */

978         public InputStream JavaDoc inputStream(String JavaDoc name) throws java.io.FileNotFoundException JavaDoc {
979             return fs.inputStream(name);
980         }
981
982         /*
983         * Get output stream.
984         *
985         * @param name the file to test
986         * @return output stream to overwrite the contents of this file
987         * @exception IOException if an error occures (the file is invalid, etc.)
988         */

989         public OutputStream JavaDoc outputStream(String JavaDoc name) throws java.io.IOException JavaDoc {
990             return fs.outputStream(name);
991         }
992
993         /*
994         * Does nothing to lock the file.
995         *
996         * @param name name of the file
997         */

998         public void lock(String JavaDoc name) throws IOException JavaDoc {
999             fs.lock(name);
1000        }
1001
1002        /*
1003        * Does nothing to unlock the file.
1004        *
1005        * @param name name of the file
1006        */

1007        public void unlock(String JavaDoc name) {
1008            fs.unlock(name);
1009        }
1010
1011        /*
1012        * Does nothing to mark the file as unimportant.
1013        *
1014        * @param name the file to mark
1015        */

1016        public void markUnimportant(String JavaDoc name) {
1017            fs.markUnimportant(name);
1018        }
1019
1020        /*
1021        * Get the file attribute with the specified name.
1022        * @param name the file
1023        * @param attrName name of the attribute
1024        * @return appropriate (serializable) value or <CODE>null</CODE> if the attribute is unset (or could not be properly restored for some reason)
1025        */

1026        public Object JavaDoc readAttribute(String JavaDoc name, String JavaDoc attrName) {
1027            return fs.readAttribute(name, attrName);
1028        }
1029
1030        /*
1031        * Set the file attribute with the specified name.
1032        * @param name the file
1033        * @param attrName name of the attribute
1034        * @param value new value or <code>null</code> to clear the attribute. Must be serializable, although particular filesystems may or may not use serialization to store attribute values.
1035        * @exception IOException if the attribute cannot be set. If serialization is used to store it, this may in fact be a subclass such as {@link NotSerializableException}.
1036        */

1037        public void writeAttribute(String JavaDoc name, String JavaDoc attrName, Object JavaDoc value)
1038        throws IOException JavaDoc {
1039            fs.writeAttribute(name, attrName, value);
1040        }
1041
1042        /*
1043        * Get all file attribute names for the file.
1044        * @param name the file
1045        * @return enumeration of keys (as strings)
1046        */

1047        public Enumeration JavaDoc<String JavaDoc> attributes(String JavaDoc name) {
1048            return fs.attributes(name);
1049        }
1050
1051        /*
1052        * Called when a file is renamed, to appropriatelly update its attributes.
1053        * <p>
1054        * @param oldName old name of the file
1055        * @param newName new name of the file
1056        */

1057        public void renameAttributes(String JavaDoc oldName, String JavaDoc newName) {
1058            fs.renameAttributes(oldName, newName);
1059        }
1060
1061        /*
1062        * Called when a file is deleted to also delete its attributes.
1063        *
1064        * @param name name of the file
1065        */

1066        public void deleteAttributes(String JavaDoc name) {
1067            fs.deleteAttributes(name);
1068        }
1069    }
1070
1071    private static class Cache {
1072        static Cache INVALID = new Cache(Enumerations.<JarEntry JavaDoc>empty());
1073        byte[] names = new byte[1000];
1074        private int nameOffset = 0;
1075        int[] EMPTY = new int[0];
1076        private Map JavaDoc<String JavaDoc, Folder> folders = new HashMap JavaDoc<String JavaDoc, Folder>();
1077
1078        public Cache(Enumeration JavaDoc<JarEntry JavaDoc> en) {
1079            parse(en);
1080            trunc();
1081        }
1082
1083        public boolean isFolder(String JavaDoc name) {
1084            return folders.get(name) != null;
1085        }
1086
1087        public String JavaDoc[] getChildrenOf(String JavaDoc folder) {
1088            Folder fol = folders.get(folder);
1089
1090            if (fol != null) {
1091                return fol.getNames();
1092            }
1093
1094            return new String JavaDoc[] { };
1095        }
1096
1097        private void parse(Enumeration JavaDoc<JarEntry JavaDoc> en) {
1098            folders.put("", new Folder()); // root folder
1099

1100            while (en.hasMoreElements()) {
1101                JarEntry JavaDoc je = en.nextElement();
1102                String JavaDoc name = je.getName();
1103                boolean isFolder = false;
1104
1105                // work only with slashes
1106
name = name.replace('\\', '/');
1107
1108                if (name.startsWith("/")) {
1109                    name = name.substring(1); // NOI18N
1110
}
1111
1112                if (name.endsWith("/")) {
1113                    name = name.substring(0, name.length() - 1); // NOI18N
1114
isFolder = true;
1115                }
1116
1117                int lastSlash = name.lastIndexOf('/');
1118                String JavaDoc dirName = ""; // root
1119
String JavaDoc realName = name;
1120
1121                if (lastSlash > 0) {
1122                    dirName = name.substring(0, lastSlash); // or folder
1123
realName = name.substring(lastSlash + 1);
1124                }
1125
1126                if (isFolder) {
1127                    getFolder(name); // will create the folder item
1128
} else {
1129                    Folder fl = getFolder(dirName);
1130                    fl.addChild(realName);
1131                }
1132            }
1133        }
1134
1135        private Folder getFolder(String JavaDoc name) {
1136            Folder fl = folders.get(name);
1137
1138            if (fl == null) {
1139                // add all the superfolders on the way to the root
1140
int lastSlash = name.lastIndexOf('/');
1141                String JavaDoc dirName = ""; // root
1142
String JavaDoc realName = name;
1143
1144                if (lastSlash > 0) {
1145                    dirName = name.substring(0, lastSlash); // or folder
1146
realName = name.substring(lastSlash + 1);
1147                }
1148
1149                getFolder(dirName).addChild(realName);
1150
1151                fl = new Folder();
1152                folders.put(name, fl);
1153            }
1154
1155            return fl;
1156        }
1157
1158        private void trunc() {
1159            // strip the name array:
1160
byte[] newNames = new byte[nameOffset];
1161            System.arraycopy(names, 0, newNames, 0, nameOffset);
1162            names = newNames;
1163
1164            // strip all the indices arrays:
1165
for (Iterator JavaDoc it = folders.values().iterator(); it.hasNext();) {
1166                ((Folder) it.next()).trunc();
1167            }
1168        }
1169
1170        private int putName(byte[] name) {
1171            int start = nameOffset;
1172
1173            if ((start + name.length) > names.length) {
1174                byte[] newNames = new byte[(names.length * 2) + name.length];
1175                System.arraycopy(names, 0, newNames, 0, start);
1176                names = newNames;
1177            }
1178
1179            System.arraycopy(name, 0, names, start, name.length);
1180            nameOffset += name.length;
1181
1182            return start;
1183        }
1184
1185        private class Folder {
1186            private int[] indices = EMPTY;
1187            private int idx = 0;
1188
1189            public Folder() {
1190            }
1191
1192            public String JavaDoc[] getNames() {
1193                String JavaDoc[] ret = new String JavaDoc[idx / 2];
1194
1195                for (int i = 0; i < ret.length; i++) {
1196                    byte[] name = new byte[indices[(2 * i) + 1]];
1197                    System.arraycopy(names, indices[2 * i], name, 0, name.length);
1198
1199                    try {
1200                        ret[i] = new String JavaDoc(name, "UTF-8");
1201                    } catch (UnsupportedEncodingException JavaDoc e) {
1202                        throw new InternalError JavaDoc("No UTF-8");
1203                    }
1204                }
1205
1206                return ret;
1207            }
1208
1209            void addChild(String JavaDoc name) {
1210                // ensure enough space
1211
if ((idx + 2) > indices.length) {
1212                    int[] newInd = new int[(2 * indices.length) + 2];
1213                    System.arraycopy(indices, 0, newInd, 0, idx);
1214                    indices = newInd;
1215                }
1216
1217                try {
1218                    byte[] bytes = name.getBytes("UTF-8");
1219                    indices[idx++] = putName(bytes);
1220                    indices[idx++] = bytes.length;
1221                } catch (UnsupportedEncodingException JavaDoc e) {
1222                    throw new InternalError JavaDoc("No UTF-8");
1223                }
1224            }
1225
1226            void trunc() {
1227                if (indices.length > idx) {
1228                    int[] newInd = new int[idx];
1229                    System.arraycopy(indices, 0, newInd, 0, idx);
1230                    indices = newInd;
1231                }
1232            }
1233        }
1234    }
1235}
1236
Popular Tags