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 }