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 }