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