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