1 /******************************************************************************* 2 * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors 3 * All rights reserved. This program and the accompanying materials 4 * are made available under the terms of the Eclipse Public License v1.0 5 * which accompanies this distribution, and is available at 6 * http://www.eclipse.org/legal/epl-v10.html 7 * 8 * Contributors: 9 * Marc R. Hoffmann - initial API and implementation 10 * 11 *******************************************************************************/ 12 package org.jacoco.core.analysis; 13 14 import java.io.File; 15 import java.io.FileInputStream; 16 import java.io.IOException; 17 import java.io.InputStream; 18 import java.util.StringTokenizer; 19 import java.util.zip.GZIPInputStream; 20 import java.util.zip.ZipEntry; 21 import java.util.zip.ZipInputStream; 22 23 import org.jacoco.core.data.ExecutionData; 24 import org.jacoco.core.data.ExecutionDataStore; 25 import org.jacoco.core.internal.ContentTypeDetector; 26 import org.jacoco.core.internal.InputStreams; 27 import org.jacoco.core.internal.Pack200Streams; 28 import org.jacoco.core.internal.analysis.ClassAnalyzer; 29 import org.jacoco.core.internal.analysis.ClassCoverageImpl; 30 import org.jacoco.core.internal.analysis.StringPool; 31 import org.jacoco.core.internal.data.CRC64; 32 import org.jacoco.core.internal.flow.ClassProbesAdapter; 33 import org.objectweb.asm.ClassReader; 34 import org.objectweb.asm.ClassVisitor; 35 36 /** 37 * An {@link Analyzer} instance processes a set of Java class files and 38 * calculates coverage data for them. For each class file the result is reported 39 * to a given {@link ICoverageVisitor} instance. In addition the 40 * {@link Analyzer} requires a {@link ExecutionDataStore} instance that holds 41 * the execution data for the classes to analyze. The {@link Analyzer} offers 42 * several methods to analyze classes from a variety of sources. 43 */ 44 public class Analyzer { 45 46 private final ExecutionDataStore executionData; 47 48 private final ICoverageVisitor coverageVisitor; 49 50 private final StringPool stringPool; 51 52 /** 53 * Creates a new analyzer reporting to the given output. 54 * 55 * @param executionData 56 * execution data 57 * @param coverageVisitor 58 * the output instance that will coverage data for every analyzed 59 * class 60 */ 61 public Analyzer(final ExecutionDataStore executionData, 62 final ICoverageVisitor coverageVisitor) { 63 this.executionData = executionData; 64 this.coverageVisitor = coverageVisitor; 65 this.stringPool = new StringPool(); 66 } 67 68 /** 69 * Creates an ASM class visitor for analysis. 70 * 71 * @param classid 72 * id of the class calculated with {@link CRC64} 73 * @param className 74 * VM name of the class 75 * @return ASM visitor to write class definition to 76 */ 77 private ClassVisitor createAnalyzingVisitor(final long classid, 78 final String className) { 79 final ExecutionData data = executionData.get(classid); 80 final boolean[] probes; 81 final boolean noMatch; 82 if (data == null) { 83 probes = null; 84 noMatch = executionData.contains(className); 85 } else { 86 probes = data.getProbes(); 87 noMatch = false; 88 } 89 final ClassCoverageImpl coverage = new ClassCoverageImpl(className, 90 classid, noMatch); 91 final ClassAnalyzer analyzer = new ClassAnalyzer(coverage, probes, 92 stringPool) { 93 @Override 94 public void visitEnd() { 95 super.visitEnd(); 96 coverageVisitor.visitCoverage(coverage); 97 } 98 }; 99 return new ClassProbesAdapter(analyzer, false); 100 } 101 102 /** 103 * Analyzes the class given as a ASM reader. 104 * 105 * @param reader 106 * reader with class definitions 107 */ 108 public void analyzeClass(final ClassReader reader) { 109 final ClassVisitor visitor = createAnalyzingVisitor( 110 CRC64.classId(reader.b), reader.getClassName()); 111 reader.accept(visitor, 0); 112 } 113 114 /** 115 * Analyzes the class definition from a given in-memory buffer. 116 * 117 * @param buffer 118 * class definitions 119 * @param location 120 * a location description used for exception messages 121 * @throws IOException 122 * if the class can't be analyzed 123 */ 124 public void analyzeClass(final byte[] buffer, final String location) 125 throws IOException { 126 try { 127 analyzeClass(new ClassReader(buffer)); 128 } catch (final RuntimeException cause) { 129 throw analyzerError(location, cause); 130 } 131 } 132 133 /** 134 * Analyzes the class definition from a given input stream. The provided 135 * {@link InputStream} is not closed by this method. 136 * 137 * @param input 138 * stream to read class definition from 139 * @param location 140 * a location description used for exception messages 141 * @throws IOException 142 * if the stream can't be read or the class can't be analyzed 143 */ 144 public void analyzeClass(final InputStream input, final String location) 145 throws IOException { 146 final byte[] buffer; 147 try { 148 buffer = InputStreams.readFully(input); 149 } catch (final IOException e) { 150 throw analyzerError(location, e); 151 } 152 analyzeClass(buffer, location); 153 } 154 155 private IOException analyzerError(final String location, 156 final Exception cause) { 157 final IOException ex = new IOException( 158 String.format("Error while analyzing %s.", location)); 159 ex.initCause(cause); 160 return ex; 161 } 162 163 /** 164 * Analyzes all classes found in the given input stream. The input stream 165 * may either represent a single class file, a ZIP archive, a Pack200 166 * archive or a gzip stream that is searched recursively for class files. 167 * All other content types are ignored. The provided {@link InputStream} is 168 * not closed by this method. 169 * 170 * @param input 171 * input data 172 * @param location 173 * a location description used for exception messages 174 * @return number of class files found 175 * @throws IOException 176 * if the stream can't be read or a class can't be analyzed 177 */ 178 public int analyzeAll(final InputStream input, final String location) 179 throws IOException { 180 final ContentTypeDetector detector; 181 try { 182 detector = new ContentTypeDetector(input); 183 } catch (final IOException e) { 184 throw analyzerError(location, e); 185 } 186 switch (detector.getType()) { 187 case ContentTypeDetector.CLASSFILE: 188 analyzeClass(detector.getInputStream(), location); 189 return 1; 190 case ContentTypeDetector.ZIPFILE: 191 return analyzeZip(detector.getInputStream(), location); 192 case ContentTypeDetector.GZFILE: 193 return analyzeGzip(detector.getInputStream(), location); 194 case ContentTypeDetector.PACK200FILE: 195 return analyzePack200(detector.getInputStream(), location); 196 default: 197 return 0; 198 } 199 } 200 201 /** 202 * Analyzes all class files contained in the given file or folder. Class 203 * files as well as ZIP files are considered. Folders are searched 204 * recursively. 205 * 206 * @param file 207 * file or folder to look for class files 208 * @return number of class files found 209 * @throws IOException 210 * if the file can't be read or a class can't be analyzed 211 */ 212 public int analyzeAll(final File file) throws IOException { 213 int count = 0; 214 if (file.isDirectory()) { 215 for (final File f : file.listFiles()) { 216 count += analyzeAll(f); 217 } 218 } else { 219 final InputStream in = new FileInputStream(file); 220 try { 221 count += analyzeAll(in, file.getPath()); 222 } finally { 223 in.close(); 224 } 225 } 226 return count; 227 } 228 229 /** 230 * Analyzes all classes from the given class path. Directories containing 231 * class files as well as archive files are considered. 232 * 233 * @param path 234 * path definition 235 * @param basedir 236 * optional base directory, if <code>null</code> the current 237 * working directory is used as the base for relative path 238 * entries 239 * @return number of class files found 240 * @throws IOException 241 * if a file can't be read or a class can't be analyzed 242 */ 243 public int analyzeAll(final String path, final File basedir) 244 throws IOException { 245 int count = 0; 246 final StringTokenizer st = new StringTokenizer(path, 247 File.pathSeparator); 248 while (st.hasMoreTokens()) { 249 count += analyzeAll(new File(basedir, st.nextToken())); 250 } 251 return count; 252 } 253 254 private int analyzeZip(final InputStream input, final String location) 255 throws IOException { 256 final ZipInputStream zip = new ZipInputStream(input); 257 ZipEntry entry; 258 int count = 0; 259 while ((entry = nextEntry(zip, location)) != null) { 260 count += analyzeAll(zip, location + "@" + entry.getName()); 261 } 262 return count; 263 } 264 265 private ZipEntry nextEntry(final ZipInputStream input, 266 final String location) throws IOException { 267 try { 268 return input.getNextEntry(); 269 } catch (final IOException e) { 270 throw analyzerError(location, e); 271 } 272 } 273 274 private int analyzeGzip(final InputStream input, final String location) 275 throws IOException { 276 GZIPInputStream gzipInputStream; 277 try { 278 gzipInputStream = new GZIPInputStream(input); 279 } catch (final IOException e) { 280 throw analyzerError(location, e); 281 } 282 return analyzeAll(gzipInputStream, location); 283 } 284 285 private int analyzePack200(final InputStream input, final String location) 286 throws IOException { 287 InputStream unpackedInput; 288 try { 289 unpackedInput = Pack200Streams.unpack(input); 290 } catch (final IOException e) { 291 throw analyzerError(location, e); 292 } 293 return analyzeAll(unpackedInput, location); 294 } 295 296 } 297