View Javadoc

1   package org.apache.maven.shared.jar;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.Comparator;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.jar.JarEntry;
31  import java.util.jar.JarFile;
32  import java.util.jar.Manifest;
33  import java.util.regex.Matcher;
34  import java.util.regex.Pattern;
35  import java.util.zip.ZipException;
36  
37  /**
38   * Open a JAR file to be analyzed. Note that once created, the {@link #closeQuietly()} method should be called to
39   * release the associated file handle.
40   * <p/>
41   * Typical usage:
42   * <pre>
43   *  JarAnalyzer jar = new JarAnalyzer( jarFile );
44   * <p/>
45   *  try
46   *  {
47   *      // do some analysis, such as:
48   *      jarClasses = jarClassAnalyzer.analyze( jar );
49   *  }
50   *  finally
51   *  {
52   *      jar.closeQuietly();
53   *  }
54   * <p/>
55   *  // use jar.getJarData() in some way, or the data returned by the JAR analyzer. jar itself can no longer be used.
56   * </pre>
57   * <p/>
58   * Note: that the actual data is separated from this class by design to minimise the chance of forgetting to close the
59   * JAR file. The {@link org.apache.maven.shared.jar.JarData} class exposed, as well as any data returned by actual
60   * analyzers that use this class, can be used safely once this class is out of scope.
61   *
62   * @see org.apache.maven.shared.jar.identification.JarIdentificationAnalysis#analyze(JarAnalyzer)
63   * @see org.apache.maven.shared.jar.classes.JarClassesAnalysis#analyze(JarAnalyzer)
64   */
65  public class JarAnalyzer
66  {
67      /**
68       * Pattern to filter JAR entries for class files.
69       *
70       * @todo why are inner classes and other potentially valid classes omitted? (It flukes it by finding everything after $)
71       */
72      private static final Pattern CLASS_FILTER = Pattern.compile( "[A-Za-z0-9]*\\.class$" );
73  
74      /**
75       * Pattern to filter JAR entries for Maven POM files.
76       */
77      private static final Pattern MAVEN_POM_FILTER = Pattern.compile( "META-INF/maven/.*/pom\\.xml$" );
78  
79      /**
80       * Pattern to filter JAR entries for text files that may contain a version.
81       */
82      private static final Pattern VERSION_FILTER = Pattern.compile( "[Vv][Ee][Rr][Ss][Ii][Oo][Nn]" );
83  
84      /**
85       * The associated JAR file.
86       */
87      private final JarFile jarFile;
88  
89      /**
90       * Contains information about the data collected so far.
91       */
92      private final JarData jarData;
93  
94      /**
95       * Constructor. Opens the JAR file, so should be matched by a call to {@link #closeQuietly()}.
96       *
97       * @param file the JAR file to open
98       * @throws java.io.IOException if there is a problem opening the JAR file, or reading the manifest. The JAR file will be closed if this occurs.
99       */
100     public JarAnalyzer( File file )
101         throws IOException
102     {
103         try
104         {
105             this.jarFile = new JarFile( file );
106         }
107         catch ( ZipException e )
108         {
109             ZipException ioe = new ZipException( "Failed to open file " + file + " : " + e.getMessage() );
110             ioe.initCause( e );
111             throw ioe;
112         }
113 
114         // Obtain entries list.
115         List entries = Collections.list( jarFile.entries() );
116 
117         // Sorting of list is done by name to ensure a bytecode hash is always consistent.
118         Collections.sort( entries, new Comparator()
119         {
120             public int compare( Object o1, Object o2 )
121             {
122                 JarEntry entry1 = (JarEntry) o1;
123                 JarEntry entry2 = (JarEntry) o2;
124 
125                 return entry1.getName().compareTo( entry2.getName() );
126             }
127         } );
128 
129         Manifest manifest;
130         try
131         {
132             manifest = jarFile.getManifest();
133         }
134         catch ( IOException e )
135         {
136             closeQuietly();
137             throw e;
138         }
139         this.jarData = new JarData( file, manifest, entries );
140     }
141 
142     /**
143      * Get the data for an individual entry in the JAR. The caller should closeQuietly the input stream, and should not retain
144      * the stream as the JAR file may be closed elsewhere.
145      *
146      * @param entry the JAR entry to read from
147      * @return the input stream of the individual JAR entry.
148      * @throws java.io.IOException if there is a problem opening the individual entry
149      */
150     public InputStream getEntryInputStream( JarEntry entry )
151         throws IOException
152     {
153         return jarFile.getInputStream( entry );
154     }
155 
156     /**
157      * Close the associated JAR file, ignoring any errors that may occur.
158      */
159     public void closeQuietly()
160     {
161         try
162         {
163             jarFile.close();
164         }
165         catch ( IOException e )
166         {
167             // not much we can do about it but ignore it
168         }
169     }
170 
171     /**
172      * Filter a list of JAR entries against the pattern.
173      *
174      * @param pattern the pattern to filter against
175      * @return the list of files found, in {@link java.util.jar.JarEntry} elements
176      */
177     public List filterEntries( Pattern pattern )
178     {
179         List ret = new ArrayList();
180 
181         Iterator it = getEntries().iterator();
182         while ( it.hasNext() )
183         {
184             JarEntry entry = (JarEntry) it.next();
185 
186             Matcher mat = pattern.matcher( entry.getName() );
187             if ( mat.find() )
188             {
189                 ret.add( entry );
190             }
191         }
192         return ret;
193     }
194 
195     /**
196      * Get all the classes in the JAR.
197      *
198      * @return the list of files found, in {@link java.util.jar.JarEntry} elements
199      */
200     public List getClassEntries()
201     {
202         return filterEntries( CLASS_FILTER );
203     }
204 
205     /**
206      * Get all the Maven POM entries in the JAR.
207      *
208      * @return the list of files found, in {@link java.util.jar.JarEntry} elements
209      */
210     public List getMavenPomEntries()
211     {
212         return filterEntries( MAVEN_POM_FILTER );
213     }
214 
215     /**
216      * Get all the version text files in the JAR.
217      *
218      * @return the list of files found, in {@link java.util.jar.JarEntry} elements
219      */
220     public List getVersionEntries()
221     {
222         return filterEntries( VERSION_FILTER );
223     }
224 
225     /**
226      * Get all the contained files in the JAR.
227      *
228      * @return the list of files found, in {@link java.util.jar.JarEntry} elements
229      */
230     public List getEntries()
231     {
232         return jarData.getEntries();
233     }
234 
235     /**
236      * Get the file that was opened by this analyzer.
237      *
238      * @return the JAR file reference
239      */
240     public File getFile()
241     {
242         return jarData.getFile();
243     }
244 
245     public JarData getJarData()
246     {
247         return jarData;
248     }
249 }