View Javadoc
1   /**
2    *
3    * Copyright 2004 The Apache Software Foundation
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.codehaus.plexus.archiver.jar;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.ByteArrayOutputStream;
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStreamWriter;
26  import java.io.PrintWriter;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.Comparator;
30  import java.util.Enumeration;
31  import java.util.HashSet;
32  import java.util.List;
33  import java.util.Set;
34  import java.util.SortedMap;
35  import java.util.StringTokenizer;
36  import java.util.TreeMap;
37  import java.util.Vector;
38  import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
39  import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
40  import org.apache.commons.compress.parallel.InputStreamSupplier;
41  import org.codehaus.plexus.archiver.ArchiverException;
42  import org.codehaus.plexus.archiver.zip.ConcurrentJarCreator;
43  import org.codehaus.plexus.archiver.zip.ZipArchiver;
44  import org.codehaus.plexus.logging.Logger;
45  import org.codehaus.plexus.logging.console.ConsoleLogger;
46  import org.codehaus.plexus.util.IOUtil;
47  import static org.codehaus.plexus.archiver.util.Streams.bufferedOutputStream;
48  import static org.codehaus.plexus.archiver.util.Streams.fileOutputStream;
49  
50  /**
51   * Base class for tasks that build archives in JAR file format.
52   */
53  @SuppressWarnings(
54  {
55      "NullableProblems"
56  } )
57  public class JarArchiver
58      extends ZipArchiver
59  {
60  
61      /**
62       * the name of the meta-inf dir
63       */
64      private static final String META_INF_NAME = "META-INF";
65  
66      /**
67       * The index file name.
68       */
69      private static final String INDEX_NAME = "META-INF/INDEX.LIST";
70  
71      /**
72       * The manifest file name.
73       */
74      private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
75  
76      /**
77       * merged manifests added through addConfiguredManifest
78       */
79      private Manifest configuredManifest;
80  
81      /**
82       * shadow of the above if upToDate check alters the value
83       */
84      private Manifest savedConfiguredManifest;
85  
86      /**
87       * merged manifests added through filesets
88       */
89      private Manifest filesetManifest;
90  
91      /**
92       * Manifest of original archive, will be set to null if not in
93       * update mode.
94       */
95      private Manifest originalManifest;
96  
97      /**
98       * whether to merge fileset manifests;
99       * value is true if filesetmanifest is 'merge' or 'mergewithoutmain'
100      */
101     private FilesetManifestConfig filesetManifestConfig;
102 
103     /**
104      * whether to merge the main section of fileset manifests;
105      * value is true if filesetmanifest is 'merge'
106      */
107     private boolean mergeManifestsMain = true;
108 
109     /**
110      * the manifest specified by the 'manifest' attribute *
111      */
112     private Manifest manifest;
113 
114     /**
115      * The file found from the 'manifest' attribute. This can be
116      * either the location of a manifest, or the name of a jar added
117      * through a fileset. If its the name of an added jar, the
118      * manifest is looked for in META-INF/MANIFEST.MF
119      */
120     private File manifestFile;
121 
122     /**
123      * jar index is JDK 1.3+ only
124      */
125     private boolean index = false;
126 
127     /**
128      * whether to really create the archive in createEmptyZip, will
129      * get set in getResourcesToAdd.
130      */
131     private boolean createEmpty = false;
132 
133     /**
134      * Stores all files that are in the root of the archive (i.e. that
135      * have a name that doesn't contain a slash) so they can get
136      * listed in the index.
137      * <p/>
138      * Will not be filled unless the user has asked for an index.
139      */
140     private Vector<String> rootEntries;
141 
142     /**
143      * Path containing jars that shall be indexed in addition to this archive.
144      */
145     private ArrayList<String> indexJars;
146 
147     /**
148      * Creates a minimal default manifest with {@code Manifest-Version: 1.0} only.
149      */
150     private boolean minimalDefaultManifest = false;
151 
152     /**
153      * constructor
154      */
155     public JarArchiver()
156     {
157         super();
158         archiveType = "jar";
159         setEncoding( "UTF8" );
160         rootEntries = new Vector<String>();
161     }
162 
163     /**
164      * Set whether or not to create an index list for classes.
165      * This may speed up classloading in some cases.
166      *
167      * @param flag true to create an index
168      */
169     public void setIndex( boolean flag )
170     {
171         index = flag;
172     }
173 
174     /**
175      * Set whether the default manifest is minimal, thus having only {@code Manifest-Version: 1.0} in it.
176      *
177      * @param minimalDefaultManifest true to create minimal default manifest
178      */
179     public void setMinimalDefaultManifest( boolean minimalDefaultManifest ) {
180         this.minimalDefaultManifest = minimalDefaultManifest;
181     }
182 
183     @SuppressWarnings(
184     {
185         "JavaDoc", "UnusedDeclaration"
186     } )
187     @Deprecated // Useless method. Manifests should be UTF-8 by convention. Calling this setter does nothing
188     public void setManifestEncoding( String manifestEncoding )
189     {
190     }
191 
192     /**
193      * Allows the manifest for the archive file to be provided inline
194      * in the build file rather than in an external file.
195      *
196      * @param newManifest The new manifest
197      *
198      * @throws ManifestException .
199      */
200     public void addConfiguredManifest( Manifest newManifest )
201         throws ManifestException
202     {
203         if ( configuredManifest == null )
204         {
205             configuredManifest = newManifest;
206         }
207         else
208         {
209             JdkManifestFactory.merge( configuredManifest, newManifest, false );
210         }
211         savedConfiguredManifest = configuredManifest;
212     }
213 
214     /**
215      * The manifest file to use. This can be either the location of a manifest, or the name of a jar added through a
216      * fileset. If its the name of an added jar, the task expects the manifest to be in the jar at META-INF/MANIFEST.MF.
217      *
218      * @param manifestFile the manifest file to use.
219      *
220      * @throws org.codehaus.plexus.archiver.ArchiverException
221      * .
222      */
223     @SuppressWarnings(
224     {
225         "UnusedDeclaration"
226     } )
227     public void setManifest( File manifestFile )
228         throws ArchiverException
229     {
230         if ( !manifestFile.exists() )
231         {
232             throw new ArchiverException( "Manifest file: " + manifestFile + " does not exist." );
233         }
234 
235         this.manifestFile = manifestFile;
236     }
237 
238     private Manifest getManifest( File manifestFile )
239         throws ArchiverException
240     {
241         InputStream in = null;
242         try
243         {
244             in = new FileInputStream( manifestFile );
245             final Manifest mf = getManifest( in );
246             in.close();
247             in = null;
248             return mf;
249         }
250         catch ( IOException e )
251         {
252             throw new ArchiverException( "Unable to read manifest file: " + manifestFile + " (" + e.getMessage() + ")",
253                                          e );
254         }
255         finally
256         {
257             IOUtil.close( in );
258         }
259     }
260 
261     private Manifest getManifest( InputStream is )
262         throws ArchiverException
263     {
264         try
265         {
266             return new Manifest( is );
267         }
268         catch ( IOException e )
269         {
270             throw new ArchiverException( "Unable to read manifest file" + " (" + e.getMessage() + ")", e );
271         }
272     }
273 
274     /**
275      * Behavior when a Manifest is found in a zipfileset or zipgroupfileset file.
276      * Valid values are "skip", "merge", and "mergewithoutmain".
277      * "merge" will merge all of manifests together, and merge this into any
278      * other specified manifests.
279      * "mergewithoutmain" merges everything but the Main section of the manifests.
280      * Default value is "skip".
281      * <p>
282      * Note: if this attribute's value is not "skip", the created jar will not
283      * be readable by using java.util.jar.JarInputStream</p>
284      *
285      * @param config setting for found manifest behavior.
286      */
287     @SuppressWarnings(
288     {
289         "UnusedDeclaration"
290     } )
291     public void setFilesetmanifest( FilesetManifestConfig config )
292     {
293         filesetManifestConfig = config;
294         mergeManifestsMain = FilesetManifestConfig.merge == config;
295 
296         if ( ( filesetManifestConfig != null ) && filesetManifestConfig != FilesetManifestConfig.skip )
297         {
298 
299             doubleFilePass = true;
300         }
301     }
302 
303     /**
304      * @param indexJar The indexjar
305      */
306     public void addConfiguredIndexJars( File indexJar )
307     {
308         if ( indexJars == null )
309         {
310             indexJars = new ArrayList<String>();
311         }
312         indexJars.add( indexJar.getAbsolutePath() );
313     }
314 
315     @Override
316     protected void initZipOutputStream( ConcurrentJarCreator zOut )
317         throws ArchiverException, IOException
318     {
319         if ( !skipWriting )
320         {
321             Manifest jarManifest = createManifest();
322             writeManifest( zOut, jarManifest );
323         }
324     }
325 
326     @Override
327     protected boolean hasVirtualFiles()
328     {
329         getLogger().debug( "\n\n\nChecking for jar manifest virtual files...\n\n\n" );
330         System.out.flush();
331 
332         return ( configuredManifest != null ) || ( manifest != null ) || ( manifestFile != null )
333                    || super.hasVirtualFiles();
334     }
335 
336     /**
337      * Creates the manifest to be added to the JAR archive.
338      * Sub-classes may choose to override this method
339      * in order to inspect or modify the JAR manifest file.
340      *
341      * @return the manifest for the JAR archive.
342      *
343      * @throws ArchiverException
344      */
345     protected Manifest createManifest()
346         throws ArchiverException
347     {
348         Manifest finalManifest = Manifest.getDefaultManifest( minimalDefaultManifest );
349 
350         if ( ( manifest == null ) && ( manifestFile != null ) )
351         {
352             // if we haven't got the manifest yet, attempt to
353             // get it now and have manifest be the final merge
354             manifest = getManifest( manifestFile );
355         }
356 
357         /*
358          * Precedence: manifestFile wins over inline manifest,
359          * over manifests read from the filesets over the original
360          * manifest.
361          *
362          * merge with null argument is a no-op
363          */
364         if ( isInUpdateMode() )
365         {
366             JdkManifestFactory.merge( finalManifest, originalManifest, false );
367         }
368         JdkManifestFactory.merge( finalManifest, filesetManifest, false );
369         JdkManifestFactory.merge( finalManifest, configuredManifest, false );
370         JdkManifestFactory.merge( finalManifest, manifest, !mergeManifestsMain );
371 
372         return finalManifest;
373     }
374 
375     private void writeManifest( ConcurrentJarCreator zOut, Manifest manifest )
376         throws IOException, ArchiverException
377     {
378         for ( Enumeration e = manifest.getWarnings(); e.hasMoreElements(); )
379         {
380             getLogger().warn( "Manifest warning: " + e.nextElement() );
381         }
382 
383         zipDir( null, zOut, "META-INF/", DEFAULT_DIR_MODE, getEncoding() );
384         // time to write the manifest
385         ByteArrayOutputStream baos = new ByteArrayOutputStream();
386         manifest.write( baos );
387 
388         ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray() );
389         super.zipFile( createInputStreamSupplier( bais ), zOut, MANIFEST_NAME, System.currentTimeMillis(), null,
390                        DEFAULT_FILE_MODE, null, false );
391         super.initZipOutputStream( zOut );
392     }
393 
394     @Override
395     protected void finalizeZipOutputStream( ConcurrentJarCreator zOut )
396         throws IOException, ArchiverException
397     {
398         if ( index )
399         {
400             createIndexList( zOut );
401         }
402     }
403 
404     /**
405      * Create the index list to speed up classloading.
406      * This is a JDK 1.3+ specific feature and is enabled by default. See
407      * <a href="http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#JAR%20Index">
408      * the JAR index specification</a> for more details.
409      *
410      * @param zOut the zip stream representing the jar being built.
411      *
412      * @throws IOException thrown if there is an error while creating the
413      * index and adding it to the zip stream.
414      * @throws org.codehaus.plexus.archiver.ArchiverException
415      * .
416      */
417     private void createIndexList( ConcurrentJarCreator zOut )
418         throws IOException, ArchiverException
419     {
420         ByteArrayOutputStream baos = new ByteArrayOutputStream();
421         // encoding must be UTF8 as specified in the specs.
422         PrintWriter writer = new PrintWriter( new OutputStreamWriter( baos, "UTF8" ) );
423 
424         // version-info blankline
425         writer.println( "JarIndex-Version: 1.0" );
426         writer.println();
427 
428         // header newline
429         writer.println( getDestFile().getName() );
430 
431         // filter out META-INF if it doesn't contain anything other than the index and manifest.
432         // this is what sun.misc.JarIndex does, guess we ought to be consistent.
433         Set<String> filteredDirs = addedDirs.allAddedDirs();
434         // our added dirs always have a trailing slash
435         if ( filteredDirs.contains( META_INF_NAME + '/' ) )
436         {
437             boolean add = false;
438             for ( String entry : entries.keySet() )
439             {
440                 if ( entry.startsWith( META_INF_NAME + '/' ) && !entry.equals( INDEX_NAME )
441                          && !entry.equals( MANIFEST_NAME ) )
442                 {
443                     add = true;
444                     break;
445                 }
446             }
447             if ( !add )
448             {
449                 filteredDirs.remove( META_INF_NAME + '/' );
450             }
451         }
452         writeIndexLikeList( new ArrayList<String>( filteredDirs ), rootEntries, writer );
453         writer.println();
454 
455         if ( indexJars != null )
456         {
457             java.util.jar.Manifest mf = createManifest();
458             String classpath = mf.getMainAttributes().getValue( ManifestConstants.ATTRIBUTE_CLASSPATH );
459             String[] cpEntries = null;
460             if ( classpath != null )
461             {
462                 StringTokenizer tok = new StringTokenizer( classpath, " " );
463                 cpEntries = new String[ tok.countTokens() ];
464                 int c = 0;
465                 while ( tok.hasMoreTokens() )
466                 {
467                     cpEntries[c++] = tok.nextToken();
468                 }
469             }
470 
471             for ( String indexJar : indexJars )
472             {
473                 String name = findJarName( indexJar, cpEntries );
474                 if ( name != null )
475                 {
476                     ArrayList<String> dirs = new ArrayList<String>();
477                     ArrayList<String> files = new ArrayList<String>();
478                     grabFilesAndDirs( indexJar, dirs, files );
479                     if ( dirs.size() + files.size() > 0 )
480                     {
481                         writer.println( name );
482                         writeIndexLikeList( dirs, files, writer );
483                         writer.println();
484                     }
485                 }
486             }
487         }
488 
489         writer.flush();
490 
491         ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray() );
492 
493         super.zipFile( createInputStreamSupplier( bais ), zOut, INDEX_NAME, System.currentTimeMillis(), null,
494                        DEFAULT_FILE_MODE, null, true );
495     }
496 
497     /**
498      * Overridden from Zip class to deal with manifests and index lists.
499      */
500     @Override
501     protected void zipFile( InputStreamSupplier is, ConcurrentJarCreator zOut, String vPath,
502                             long lastModified, File fromArchive,
503                             int mode, String symlinkDestination, boolean addInParallel )
504         throws IOException, ArchiverException
505     {
506         if ( MANIFEST_NAME.equalsIgnoreCase( vPath ) )
507         {
508             if ( !doubleFilePass || skipWriting )
509             {
510                 try ( InputStream manifestInputStream = is.get() )
511                 {
512                     filesetManifest( fromArchive, manifestInputStream );
513                 }
514             }
515         }
516         else if ( INDEX_NAME.equalsIgnoreCase( vPath ) && index )
517         {
518             getLogger().warn( "Warning: selected " + archiveType + " files include a META-INF/INDEX.LIST which will"
519                                   + " be replaced by a newly generated one." );
520         }
521         else
522         {
523             if ( index && ( !vPath.contains( "/" ) ) )
524             {
525                 rootEntries.addElement( vPath );
526             }
527             super.zipFile( is, zOut, vPath, lastModified, fromArchive, mode, symlinkDestination, addInParallel );
528         }
529     }
530 
531     private void filesetManifest( File file, InputStream is )
532         throws ArchiverException
533     {
534         if ( ( manifestFile != null ) && manifestFile.equals( file ) )
535         {
536             // If this is the same name specified in 'manifest', this
537             // is the manifest to use
538             getLogger().debug( "Found manifest " + file );
539             if ( is != null )
540             {
541                 manifest = getManifest( is );
542             }
543             else
544             {
545                 manifest = getManifest( file );
546             }
547         }
548         else if ( ( filesetManifestConfig != null ) && filesetManifestConfig != FilesetManifestConfig.skip )
549         {
550             // we add this to our group of fileset manifests
551             getLogger().debug( "Found manifest to merge in file " + file );
552 
553             Manifest newManifest;
554             if ( is != null )
555             {
556                 newManifest = getManifest( is );
557             }
558             else
559             {
560                 newManifest = getManifest( file );
561             }
562 
563             if ( filesetManifest == null )
564             {
565                 filesetManifest = newManifest;
566             }
567             else
568             {
569                 JdkManifestFactory.merge( filesetManifest, newManifest, false );
570             }
571         }
572     }
573 
574     @Override
575     protected boolean createEmptyZip( File zipFile )
576         throws ArchiverException
577     {
578         if ( !createEmpty )
579         {
580             return true;
581         }
582 
583         try
584         {
585             getLogger().debug( "Building MANIFEST-only jar: " + getDestFile().getAbsolutePath() );
586             zipArchiveOutputStream =
587                 new ZipArchiveOutputStream( bufferedOutputStream( fileOutputStream( getDestFile(), "jar" ) ) );
588 
589             zipArchiveOutputStream.setEncoding( getEncoding() );
590             if ( isCompress() )
591             {
592                 zipArchiveOutputStream.setMethod( ZipArchiveOutputStream.DEFLATED );
593             }
594             else
595             {
596                 zipArchiveOutputStream.setMethod( ZipArchiveOutputStream.STORED );
597             }
598             ConcurrentJarCreator ps =
599                 new ConcurrentJarCreator( isRecompressAddedZips(), Runtime.getRuntime().availableProcessors() );
600             initZipOutputStream( ps );
601             finalizeZipOutputStream( ps );
602         }
603         catch ( IOException ioe )
604         {
605             throw new ArchiverException( "Could not create almost empty JAR archive (" + ioe.getMessage() + ")", ioe );
606         }
607         finally
608         {
609             // Close the output stream.
610             //IOUtil.close( zOut );
611             createEmpty = false;
612         }
613         return true;
614     }
615 
616     /**
617      * Make sure we don't think we already have a MANIFEST next time this task
618      * gets executed.
619      *
620      * @see ZipArchiver#cleanUp
621      */
622     @Override
623     protected void cleanUp()
624         throws IOException
625     {
626         super.cleanUp();
627 
628         // we want to save this info if we are going to make another pass
629         if ( !doubleFilePass || !skipWriting )
630         {
631             manifest = null;
632             configuredManifest = savedConfiguredManifest;
633             filesetManifest = null;
634             originalManifest = null;
635         }
636         rootEntries.removeAllElements();
637     }
638 
639     /**
640      * reset to default values.
641      *
642      * @see ZipArchiver#reset
643      */
644     @Override
645     public void reset()
646     {
647         super.reset();
648         configuredManifest = null;
649         filesetManifestConfig = null;
650         mergeManifestsMain = false;
651         manifestFile = null;
652         index = false;
653     }
654 
655     public enum FilesetManifestConfig
656     {
657 
658         skip,
659         merge,
660         mergewithoutmain
661 
662     }
663 
664     /**
665      * Writes the directory entries from the first and the filenames
666      * from the second list to the given writer, one entry per line.
667      *
668      * @param dirs The directories
669      * @param files The files
670      * @param writer The printwriter ;)
671      */
672     protected final void writeIndexLikeList( List<String> dirs, List<String> files, PrintWriter writer )
673     {
674         // JarIndex is sorting the directories by ascending order.
675         // it has no value but cosmetic since it will be read into a
676         // hashtable by the classloader, but we'll do so anyway.
677         Collections.sort( dirs );
678         Collections.sort( files );
679         for ( String dir : dirs )
680         {
681             // try to be smart, not to be fooled by a weird directory name
682             dir = dir.replace( '\\', '/' );
683             if ( dir.startsWith( "./" ) )
684             {
685                 dir = dir.substring( 2 );
686             }
687             while ( dir.startsWith( "/" ) )
688             {
689                 dir = dir.substring( 1 );
690             }
691             int pos = dir.lastIndexOf( '/' );
692             if ( pos != -1 )
693             {
694                 dir = dir.substring( 0, pos );
695             }
696 
697             // name newline
698             writer.println( dir );
699         }
700 
701         for ( String file : files )
702         {
703             writer.println( file );
704         }
705     }
706 
707     /**
708      * try to guess the name of the given file.
709      * <p>
710      * If this jar has a classpath attribute in its manifest, we
711      * can assume that it will only require an index of jars listed
712      * there. try to find which classpath entry is most likely the
713      * one the given file name points to.</p>
714      * <p>
715      * In the absence of a classpath attribute, assume the other
716      * files will be placed inside the same directory as this jar and
717      * use their basename.</p>
718      * <p>
719      * if there is a classpath and the given file doesn't match any
720      * of its entries, return null.</p>
721      *
722      * @param fileName .
723      * @param classpath .
724      *
725      * @return The guessed name
726      */
727     protected static String findJarName( String fileName, String[] classpath )
728     {
729         if ( classpath == null )
730         {
731             return new File( fileName ).getName();
732         }
733         fileName = fileName.replace( File.separatorChar, '/' );
734         SortedMap<String, String> matches = new TreeMap<String, String>( new Comparator<String>()
735         {
736 
737             // longest match comes first
738             @Override
739             public int compare( String o1, String o2 )
740             {
741                 if ( ( o1 != null ) && ( o2 != null ) )
742                 {
743                     return o2.length() - o1.length();
744                 }
745                 return 0;
746             }
747 
748         } );
749 
750         for ( String aClasspath : classpath )
751         {
752             if ( fileName.endsWith( aClasspath ) )
753             {
754                 matches.put( aClasspath, aClasspath );
755             }
756             else
757             {
758                 int slash = aClasspath.indexOf( "/" );
759                 String candidate = aClasspath;
760                 while ( slash > -1 )
761                 {
762                     candidate = candidate.substring( slash + 1 );
763                     if ( fileName.endsWith( candidate ) )
764                     {
765                         matches.put( candidate, aClasspath );
766                         break;
767                     }
768                     slash = candidate.indexOf( "/" );
769                 }
770             }
771         }
772 
773         return matches.size() == 0 ? null : matches.get( matches.firstKey() );
774     }
775 
776     /**
777      * Grab lists of all root-level files and all directories
778      * contained in the given archive.
779      *
780      * @param file .
781      * @param files .
782      * @param dirs .
783      *
784      * @throws java.io.IOException .
785      */
786     protected static void grabFilesAndDirs( String file, List<String> dirs, List<String> files )
787         throws IOException
788     {
789         File zipFile = new File( file );
790         if ( !zipFile.exists() )
791         {
792             Logger logger = new ConsoleLogger( Logger.LEVEL_INFO, "console" );
793             logger.error( "JarArchive skipping non-existing file: " + zipFile.getAbsolutePath() );
794         }
795         else if ( zipFile.isDirectory() )
796         {
797             Logger logger = new ConsoleLogger( Logger.LEVEL_INFO, "console" );
798             logger.info( "JarArchiver skipping indexJar " + zipFile + " because it is not a jar" );
799         }
800         else
801         {
802             org.apache.commons.compress.archivers.zip.ZipFile zf = null;
803             try
804             {
805                 zf = new org.apache.commons.compress.archivers.zip.ZipFile( file, "utf-8" );
806                 Enumeration<ZipArchiveEntry> entries = zf.getEntries();
807                 HashSet<String> dirSet = new HashSet<String>();
808                 while ( entries.hasMoreElements() )
809                 {
810                     ZipArchiveEntry ze = entries.nextElement();
811                     String name = ze.getName();
812                     // avoid index for manifest-only jars.
813                     if ( !name.equals( META_INF_NAME ) && !name.equals( META_INF_NAME + '/' ) && !name.equals(
814                         INDEX_NAME ) && !name.equals( MANIFEST_NAME ) )
815                     {
816                         if ( ze.isDirectory() )
817                         {
818                             dirSet.add( name );
819                         }
820                         else if ( !name.contains( "/" ) )
821                         {
822                             files.add( name );
823                         }
824                         else
825                         {
826                             // a file, not in the root
827                             // since the jar may be one without directory
828                             // entries, add the parent dir of this file as
829                             // well.
830                             dirSet.add( name.substring( 0, name.lastIndexOf( "/" ) + 1 ) );
831                         }
832                     }
833                 }
834                 dirs.addAll( dirSet );
835             }
836             finally
837             {
838                 if ( zf != null )
839                 {
840                     zf.close();
841                 }
842             }
843         }
844     }
845 
846 }