Home | History | Annotate | Download | only in analysis
      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