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 }