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
99       *             will be closed if this occurs.
100      */
101     public JarAnalyzer( File file )
102         throws IOException
103     {
104         try
105         {
106             this.jarFile = new JarFile( file );
107         }
108         catch ( ZipException e )
109         {
110             ZipException ioe = new ZipException( "Failed to open file " + file + " : " + e.getMessage() );
111             ioe.initCause( e );
112             throw ioe;
113         }
114 
115         // Obtain entries list.
116         List entries = Collections.list( jarFile.entries() );
117 
118         // Sorting of list is done by name to ensure a bytecode hash is always consistent.
119         Collections.sort( entries, new Comparator()
120         {
121             public int compare( Object o1, Object o2 )
122             {
123                 JarEntry entry1 = (JarEntry) o1;
124                 JarEntry entry2 = (JarEntry) o2;
125 
126                 return entry1.getName().compareTo( entry2.getName() );
127             }
128         } );
129 
130         Manifest manifest;
131         try
132         {
133             manifest = jarFile.getManifest();
134         }
135         catch ( IOException e )
136         {
137             closeQuietly();
138             throw e;
139         }
140         this.jarData = new JarData( file, manifest, entries );
141     }
142 
143     /**
144      * Get the data for an individual entry in the JAR. The caller should closeQuietly the input stream, and should not
145      * retain the stream as the JAR file may be closed elsewhere.
146      *
147      * @param entry the JAR entry to read from
148      * @return the input stream of the individual JAR entry.
149      * @throws java.io.IOException if there is a problem opening the individual entry
150      */
151     public InputStream getEntryInputStream( JarEntry entry )
152         throws IOException
153     {
154         return jarFile.getInputStream( entry );
155     }
156 
157     /**
158      * Close the associated JAR file, ignoring any errors that may occur.
159      */
160     public void closeQuietly()
161     {
162         try
163         {
164             jarFile.close();
165         }
166         catch ( IOException e )
167         {
168             // not much we can do about it but ignore it
169         }
170     }
171 
172     /**
173      * Filter a list of JAR entries against the pattern.
174      *
175      * @param pattern the pattern to filter against
176      * @return the list of files found, in {@link java.util.jar.JarEntry} elements
177      */
178     public List filterEntries( Pattern pattern )
179     {
180         List ret = new ArrayList();
181 
182         Iterator it = getEntries().iterator();
183         while ( it.hasNext() )
184         {
185             JarEntry entry = (JarEntry) it.next();
186 
187             Matcher mat = pattern.matcher( entry.getName() );
188             if ( mat.find() )
189             {
190                 ret.add( entry );
191             }
192         }
193         return ret;
194     }
195 
196     /**
197      * Get all the classes in the JAR.
198      *
199      * @return the list of files found, in {@link java.util.jar.JarEntry} elements
200      */
201     public List getClassEntries()
202     {
203         return filterEntries( CLASS_FILTER );
204     }
205 
206     /**
207      * Get all the Maven POM entries in the JAR.
208      *
209      * @return the list of files found, in {@link java.util.jar.JarEntry} elements
210      */
211     public List getMavenPomEntries()
212     {
213         return filterEntries( MAVEN_POM_FILTER );
214     }
215 
216     /**
217      * Get all the version text files in the JAR.
218      *
219      * @return the list of files found, in {@link java.util.jar.JarEntry} elements
220      */
221     public List getVersionEntries()
222     {
223         return filterEntries( VERSION_FILTER );
224     }
225 
226     /**
227      * Get all the contained files in the JAR.
228      *
229      * @return the list of files found, in {@link java.util.jar.JarEntry} elements
230      */
231     public List getEntries()
232     {
233         return jarData.getEntries();
234     }
235 
236     /**
237      * Get the file that was opened by this analyzer.
238      *
239      * @return the JAR file reference
240      */
241     public File getFile()
242     {
243         return jarData.getFile();
244     }
245 
246     public JarData getJarData()
247     {
248         return jarData;
249     }
250 }