Analyzer.java revision b05619921551903a81b242b41d196b41c70e770f
1/*******************************************************************************
2 * Copyright (c) 2009, 2011 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.ZipEntry;
20import java.util.zip.ZipInputStream;
21
22import org.jacoco.core.data.CRC64;
23import org.jacoco.core.data.ExecutionData;
24import org.jacoco.core.data.ExecutionDataStore;
25import org.jacoco.core.internal.analysis.ClassAnalyzer;
26import org.jacoco.core.internal.analysis.ContentTypeDetector;
27import org.jacoco.core.internal.analysis.StringPool;
28import org.jacoco.core.internal.flow.ClassProbesAdapter;
29import org.objectweb.asm.ClassReader;
30import org.objectweb.asm.ClassVisitor;
31
32/**
33 * Several APIs to analyze class structures.
34 *
35 * @author Marc R. Hoffmann
36 * @version $qualified.bundle.version$
37 */
38public class Analyzer {
39
40	private final ExecutionDataStore executionData;
41
42	private final ICoverageVisitor structureVisitor;
43
44	private final StringPool stringPool;
45
46	/**
47	 * Creates a new analyzer reporting to the given output.
48	 *
49	 * @param executionData
50	 *            execution data
51	 * @param structureVisitor
52	 *            the output instance that will receive all structure data
53	 */
54	public Analyzer(final ExecutionDataStore executionData,
55			final ICoverageVisitor structureVisitor) {
56		this.executionData = executionData;
57		this.structureVisitor = structureVisitor;
58		this.stringPool = new StringPool();
59	}
60
61	/**
62	 * Creates an ASM class visitor for analysis.
63	 *
64	 * @param classid
65	 *            id of the class calculated with {@link CRC64}
66	 * @return ASM visitor to write class definition to
67	 */
68	public ClassVisitor createAnalyzingVisitor(final long classid) {
69		final ExecutionData data = executionData.get(classid);
70		final boolean[] classExec = data == null ? null : data.getData();
71		final ClassAnalyzer analyzer = new ClassAnalyzer(classid, classExec,
72				stringPool) {
73			@Override
74			public void visitEnd() {
75				super.visitEnd();
76				structureVisitor.visitCoverage(getCoverage());
77			}
78		};
79		return new ClassProbesAdapter(analyzer);
80	}
81
82	/**
83	 * Analyzes the class given as a ASM reader.
84	 *
85	 * @param reader
86	 *            reader with class definitions
87	 */
88	public void analyzeClass(final ClassReader reader) {
89		final ClassVisitor visitor = createAnalyzingVisitor(CRC64
90				.checksum(reader.b));
91		reader.accept(visitor, 0);
92	}
93
94	/**
95	 * Analyzes the class definition from a given in-memory buffer.
96	 *
97	 * @param buffer
98	 *            class definitions
99	 */
100	public void analyzeClass(final byte[] buffer) {
101		analyzeClass(new ClassReader(buffer));
102	}
103
104	/**
105	 * Analyzes the class definition from a given input stream.
106	 *
107	 * @param input
108	 *            stream to read class definition from
109	 * @throws IOException
110	 */
111	public void analyzeClass(final InputStream input) throws IOException {
112		analyzeClass(new ClassReader(input));
113	}
114
115	/**
116	 * Analyzes all classes contained in the ZIP archive (jar, war, ear, etc.)
117	 * given as an input stream. Contained archives are read recursively.
118	 *
119	 * @param input
120	 *            ZIP archive data
121	 * @return number of class files found
122	 * @throws IOException
123	 */
124	public int analyzeArchive(final InputStream input) throws IOException {
125		final ZipInputStream zip = new ZipInputStream(input);
126		int count = 0;
127		while (true) {
128			final ZipEntry entry = zip.getNextEntry();
129			if (entry == null) {
130				break;
131			}
132			count += analyzeAll(zip);
133		}
134		return count;
135	}
136
137	/**
138	 * Analyzes all classes found in the given input stream. The input stream
139	 * may either represent a single class file or a ZIP archive that is
140	 * searched recursively for class files. All other content types are
141	 * ignored.
142	 *
143	 * @param input
144	 *            input data
145	 * @return number of class files found
146	 * @throws IOException
147	 */
148	public int analyzeAll(final InputStream input) throws IOException {
149		final ContentTypeDetector detector = new ContentTypeDetector(input);
150		switch (detector.getType()) {
151		case ContentTypeDetector.CLASSFILE:
152			analyzeClass(detector.getInputStream());
153			return 1;
154		case ContentTypeDetector.ZIPFILE:
155			return analyzeArchive(detector.getInputStream());
156		}
157		return 0;
158	}
159
160	/**
161	 * Analyzes all class files contained in the given file or folder. Class
162	 * files as well as ZIP files are considered. Folders are searched
163	 * recursively.
164	 *
165	 * @param file
166	 *            file or folder to look for class files
167	 * @return number of class files found
168	 * @throws IOException
169	 */
170	public int analyzeAll(final File file) throws IOException {
171		int count = 0;
172		if (file.isDirectory()) {
173			for (final File f : file.listFiles()) {
174				count += analyzeAll(f);
175			}
176		} else {
177			final InputStream in = new FileInputStream(file);
178			count += analyzeAll(in);
179			in.close();
180		}
181		return count;
182	}
183
184	/**
185	 * Analyzes all classes from the given class path. Directories containing
186	 * class files as well as archive files are considered.
187	 *
188	 * @param path
189	 *            path definition
190	 * @param basedir
191	 *            optional base directory, if <code>null</code> the current
192	 *            working directory is used as the base for relative path
193	 *            entries
194	 * @return number of class files found
195	 * @throws IOException
196	 */
197	public int analyzeAll(final String path, final File basedir)
198			throws IOException {
199		int count = 0;
200		final StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
201		while (st.hasMoreTokens()) {
202			count += analyzeAll(new File(basedir, st.nextToken()));
203		}
204		return count;
205	}
206
207}
208