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 }