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