| /******************************************************************************* |
| * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Marc R. Hoffmann - initial API and implementation |
| * |
| *******************************************************************************/ |
| package org.jacoco.core.analysis; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.StringTokenizer; |
| import java.util.zip.GZIPInputStream; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipInputStream; |
| |
| import org.jacoco.core.data.ExecutionData; |
| import org.jacoco.core.data.ExecutionDataStore; |
| import org.jacoco.core.internal.ContentTypeDetector; |
| import org.jacoco.core.internal.InputStreams; |
| import org.jacoco.core.internal.Pack200Streams; |
| import org.jacoco.core.internal.analysis.ClassAnalyzer; |
| import org.jacoco.core.internal.analysis.ClassCoverageImpl; |
| import org.jacoco.core.internal.analysis.StringPool; |
| import org.jacoco.core.internal.data.CRC64; |
| import org.jacoco.core.internal.flow.ClassProbesAdapter; |
| import org.objectweb.asm.ClassReader; |
| import org.objectweb.asm.ClassVisitor; |
| |
| /** |
| * An {@link Analyzer} instance processes a set of Java class files and |
| * calculates coverage data for them. For each class file the result is reported |
| * to a given {@link ICoverageVisitor} instance. In addition the |
| * {@link Analyzer} requires a {@link ExecutionDataStore} instance that holds |
| * the execution data for the classes to analyze. The {@link Analyzer} offers |
| * several methods to analyze classes from a variety of sources. |
| */ |
| public class Analyzer { |
| |
| private final ExecutionDataStore executionData; |
| |
| private final ICoverageVisitor coverageVisitor; |
| |
| private final StringPool stringPool; |
| |
| /** |
| * Creates a new analyzer reporting to the given output. |
| * |
| * @param executionData |
| * execution data |
| * @param coverageVisitor |
| * the output instance that will coverage data for every analyzed |
| * class |
| */ |
| public Analyzer(final ExecutionDataStore executionData, |
| final ICoverageVisitor coverageVisitor) { |
| this.executionData = executionData; |
| this.coverageVisitor = coverageVisitor; |
| this.stringPool = new StringPool(); |
| } |
| |
| /** |
| * Creates an ASM class visitor for analysis. |
| * |
| * @param classid |
| * id of the class calculated with {@link CRC64} |
| * @param className |
| * VM name of the class |
| * @return ASM visitor to write class definition to |
| */ |
| private ClassVisitor createAnalyzingVisitor(final long classid, |
| final String className) { |
| final ExecutionData data = executionData.get(classid); |
| final boolean[] probes; |
| final boolean noMatch; |
| if (data == null) { |
| probes = null; |
| noMatch = executionData.contains(className); |
| } else { |
| probes = data.getProbes(); |
| noMatch = false; |
| } |
| final ClassCoverageImpl coverage = new ClassCoverageImpl(className, |
| classid, noMatch); |
| final ClassAnalyzer analyzer = new ClassAnalyzer(coverage, probes, |
| stringPool) { |
| @Override |
| public void visitEnd() { |
| super.visitEnd(); |
| coverageVisitor.visitCoverage(coverage); |
| } |
| }; |
| return new ClassProbesAdapter(analyzer, false); |
| } |
| |
| /** |
| * Analyzes the class given as a ASM reader. |
| * |
| * @param reader |
| * reader with class definitions |
| */ |
| public void analyzeClass(final ClassReader reader) { |
| final ClassVisitor visitor = createAnalyzingVisitor( |
| CRC64.classId(reader.b), reader.getClassName()); |
| reader.accept(visitor, 0); |
| } |
| |
| /** |
| * Analyzes the class definition from a given in-memory buffer. |
| * |
| * @param buffer |
| * class definitions |
| * @param location |
| * a location description used for exception messages |
| * @throws IOException |
| * if the class can't be analyzed |
| */ |
| public void analyzeClass(final byte[] buffer, final String location) |
| throws IOException { |
| try { |
| analyzeClass(new ClassReader(buffer)); |
| } catch (final RuntimeException cause) { |
| throw analyzerError(location, cause); |
| } |
| } |
| |
| /** |
| * Analyzes the class definition from a given input stream. The provided |
| * {@link InputStream} is not closed by this method. |
| * |
| * @param input |
| * stream to read class definition from |
| * @param location |
| * a location description used for exception messages |
| * @throws IOException |
| * if the stream can't be read or the class can't be analyzed |
| */ |
| public void analyzeClass(final InputStream input, final String location) |
| throws IOException { |
| final byte[] buffer; |
| try { |
| buffer = InputStreams.readFully(input); |
| } catch (final IOException e) { |
| throw analyzerError(location, e); |
| } |
| analyzeClass(buffer, location); |
| } |
| |
| private IOException analyzerError(final String location, |
| final Exception cause) { |
| final IOException ex = new IOException( |
| String.format("Error while analyzing %s.", location)); |
| ex.initCause(cause); |
| return ex; |
| } |
| |
| /** |
| * Analyzes all classes found in the given input stream. The input stream |
| * may either represent a single class file, a ZIP archive, a Pack200 |
| * archive or a gzip stream that is searched recursively for class files. |
| * All other content types are ignored. The provided {@link InputStream} is |
| * not closed by this method. |
| * |
| * @param input |
| * input data |
| * @param location |
| * a location description used for exception messages |
| * @return number of class files found |
| * @throws IOException |
| * if the stream can't be read or a class can't be analyzed |
| */ |
| public int analyzeAll(final InputStream input, final String location) |
| throws IOException { |
| final ContentTypeDetector detector; |
| try { |
| detector = new ContentTypeDetector(input); |
| } catch (final IOException e) { |
| throw analyzerError(location, e); |
| } |
| switch (detector.getType()) { |
| case ContentTypeDetector.CLASSFILE: |
| analyzeClass(detector.getInputStream(), location); |
| return 1; |
| case ContentTypeDetector.ZIPFILE: |
| return analyzeZip(detector.getInputStream(), location); |
| case ContentTypeDetector.GZFILE: |
| return analyzeGzip(detector.getInputStream(), location); |
| case ContentTypeDetector.PACK200FILE: |
| return analyzePack200(detector.getInputStream(), location); |
| default: |
| return 0; |
| } |
| } |
| |
| /** |
| * Analyzes all class files contained in the given file or folder. Class |
| * files as well as ZIP files are considered. Folders are searched |
| * recursively. |
| * |
| * @param file |
| * file or folder to look for class files |
| * @return number of class files found |
| * @throws IOException |
| * if the file can't be read or a class can't be analyzed |
| */ |
| public int analyzeAll(final File file) throws IOException { |
| int count = 0; |
| if (file.isDirectory()) { |
| for (final File f : file.listFiles()) { |
| count += analyzeAll(f); |
| } |
| } else { |
| final InputStream in = new FileInputStream(file); |
| try { |
| count += analyzeAll(in, file.getPath()); |
| } finally { |
| in.close(); |
| } |
| } |
| return count; |
| } |
| |
| /** |
| * Analyzes all classes from the given class path. Directories containing |
| * class files as well as archive files are considered. |
| * |
| * @param path |
| * path definition |
| * @param basedir |
| * optional base directory, if <code>null</code> the current |
| * working directory is used as the base for relative path |
| * entries |
| * @return number of class files found |
| * @throws IOException |
| * if a file can't be read or a class can't be analyzed |
| */ |
| public int analyzeAll(final String path, final File basedir) |
| throws IOException { |
| int count = 0; |
| final StringTokenizer st = new StringTokenizer(path, |
| File.pathSeparator); |
| while (st.hasMoreTokens()) { |
| count += analyzeAll(new File(basedir, st.nextToken())); |
| } |
| return count; |
| } |
| |
| private int analyzeZip(final InputStream input, final String location) |
| throws IOException { |
| final ZipInputStream zip = new ZipInputStream(input); |
| ZipEntry entry; |
| int count = 0; |
| while ((entry = nextEntry(zip, location)) != null) { |
| count += analyzeAll(zip, location + "@" + entry.getName()); |
| } |
| return count; |
| } |
| |
| private ZipEntry nextEntry(final ZipInputStream input, |
| final String location) throws IOException { |
| try { |
| return input.getNextEntry(); |
| } catch (final IOException e) { |
| throw analyzerError(location, e); |
| } |
| } |
| |
| private int analyzeGzip(final InputStream input, final String location) |
| throws IOException { |
| GZIPInputStream gzipInputStream; |
| try { |
| gzipInputStream = new GZIPInputStream(input); |
| } catch (final IOException e) { |
| throw analyzerError(location, e); |
| } |
| return analyzeAll(gzipInputStream, location); |
| } |
| |
| private int analyzePack200(final InputStream input, final String location) |
| throws IOException { |
| InputStream unpackedInput; |
| try { |
| unpackedInput = Pack200Streams.unpack(input); |
| } catch (final IOException e) { |
| throw analyzerError(location, e); |
| } |
| return analyzeAll(unpackedInput, location); |
| } |
| |
| } |