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 }