• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  *    Evgeny Mandrikov - initial API and implementation
10  *    Kyle Lieber - implementation of CheckMojo
11  *
12  *******************************************************************************/
13 package org.jacoco.maven;
14 
15 import static java.lang.String.format;
16 
17 import java.io.File;
18 import java.io.FileInputStream;
19 import java.io.FileOutputStream;
20 import java.io.IOException;
21 import java.io.InputStreamReader;
22 import java.io.Reader;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.List;
26 import java.util.Locale;
27 
28 import org.apache.maven.plugin.logging.Log;
29 import org.apache.maven.project.MavenProject;
30 import org.jacoco.core.analysis.Analyzer;
31 import org.jacoco.core.analysis.CoverageBuilder;
32 import org.jacoco.core.analysis.IBundleCoverage;
33 import org.jacoco.core.analysis.IClassCoverage;
34 import org.jacoco.core.tools.ExecFileLoader;
35 import org.jacoco.report.FileMultiReportOutput;
36 import org.jacoco.report.IReportGroupVisitor;
37 import org.jacoco.report.IReportVisitor;
38 import org.jacoco.report.ISourceFileLocator;
39 import org.jacoco.report.MultiReportVisitor;
40 import org.jacoco.report.check.IViolationsOutput;
41 import org.jacoco.report.check.Rule;
42 import org.jacoco.report.check.RulesChecker;
43 import org.jacoco.report.csv.CSVFormatter;
44 import org.jacoco.report.html.HTMLFormatter;
45 import org.jacoco.report.xml.XMLFormatter;
46 
47 /**
48  * Encapsulates the tasks to create reports for Maven projects. Instances are
49  * supposed to be used in the following sequence:
50  *
51  * <ol>
52  * <li>Create an instance</li>
53  * <li>Load one or multiple exec files with <code>loadExecutionData()</code></li>
54  * <li>Add one or multiple formatters with <code>addXXX()</code> methods</li>
55  * <li>Create the root visitor with <code>initRootVisitor()</code></li>
56  * <li>Process one or multiple projects with <code>processProject()</code></li>
57  * </ol>
58  */
59 final class ReportSupport {
60 
61 	private final Log log;
62 	private final ExecFileLoader loader;
63 	private final List<IReportVisitor> formatters;
64 
65 	/**
66 	 * Construct a new instance with the given log output.
67 	 *
68 	 * @param log
69 	 *            for log output
70 	 */
ReportSupport(final Log log)71 	public ReportSupport(final Log log) {
72 		this.log = log;
73 		this.loader = new ExecFileLoader();
74 		this.formatters = new ArrayList<IReportVisitor>();
75 	}
76 
77 	/**
78 	 * Loads the given execution data file.
79 	 *
80 	 * @param execFile
81 	 *            execution data file to load
82 	 * @throws IOException
83 	 *             if the file can't be loaded
84 	 */
loadExecutionData(final File execFile)85 	public void loadExecutionData(final File execFile) throws IOException {
86 		log.info("Loading execution data file " + execFile);
87 		loader.load(execFile);
88 	}
89 
addXmlFormatter(final File targetfile, final String encoding)90 	public void addXmlFormatter(final File targetfile, final String encoding)
91 			throws IOException {
92 		final XMLFormatter xml = new XMLFormatter();
93 		xml.setOutputEncoding(encoding);
94 		formatters.add(xml.createVisitor(new FileOutputStream(targetfile)));
95 	}
96 
addCsvFormatter(final File targetfile, final String encoding)97 	public void addCsvFormatter(final File targetfile, final String encoding)
98 			throws IOException {
99 		final CSVFormatter csv = new CSVFormatter();
100 		csv.setOutputEncoding(encoding);
101 		formatters.add(csv.createVisitor(new FileOutputStream(targetfile)));
102 	}
103 
addHtmlFormatter(final File targetdir, final String encoding, final String footer, final Locale locale)104 	public void addHtmlFormatter(final File targetdir, final String encoding,
105 			final String footer, final Locale locale) throws IOException {
106 		final HTMLFormatter htmlFormatter = new HTMLFormatter();
107 		htmlFormatter.setOutputEncoding(encoding);
108 		htmlFormatter.setLocale(locale);
109 		if (footer != null) {
110 			htmlFormatter.setFooterText(footer);
111 		}
112 		formatters.add(htmlFormatter.createVisitor(new FileMultiReportOutput(
113 				targetdir)));
114 	}
115 
addAllFormatters(final File targetdir, final String encoding, final String footer, final Locale locale)116 	public void addAllFormatters(final File targetdir, final String encoding,
117 			final String footer, final Locale locale) throws IOException {
118 		targetdir.mkdirs();
119 		addXmlFormatter(new File(targetdir, "jacoco.xml"), encoding);
120 		addCsvFormatter(new File(targetdir, "jacoco.csv"), encoding);
121 		addHtmlFormatter(targetdir, encoding, footer, locale);
122 	}
123 
addRulesChecker(final List<Rule> rules, final IViolationsOutput output)124 	public void addRulesChecker(final List<Rule> rules,
125 			final IViolationsOutput output) {
126 		final RulesChecker checker = new RulesChecker();
127 		checker.setRules(rules);
128 		formatters.add(checker.createVisitor(output));
129 	}
130 
initRootVisitor()131 	public IReportVisitor initRootVisitor() throws IOException {
132 		final IReportVisitor visitor = new MultiReportVisitor(formatters);
133 		visitor.visitInfo(loader.getSessionInfoStore().getInfos(), loader
134 				.getExecutionDataStore().getContents());
135 		return visitor;
136 	}
137 
138 	/**
139 	 * Calculates coverage for the given project and emits it to the report
140 	 * group without source references
141 	 *
142 	 * @param visitor
143 	 *            group visitor to emit the project's coverage to
144 	 * @param project
145 	 *            the MavenProject
146 	 * @param includes
147 	 *            list of includes patterns
148 	 * @param excludes
149 	 *            list of excludes patterns
150 	 * @throws IOException
151 	 *             if class files can't be read
152 	 */
processProject(final IReportGroupVisitor visitor, final MavenProject project, final List<String> includes, final List<String> excludes)153 	public void processProject(final IReportGroupVisitor visitor,
154 			final MavenProject project, final List<String> includes,
155 			final List<String> excludes) throws IOException {
156 		processProject(visitor, project.getArtifactId(), project, includes,
157 				excludes, new NoSourceLocator());
158 	}
159 
160 	/**
161 	 * Calculates coverage for the given project and emits it to the report
162 	 * group including source references
163 	 *
164 	 * @param visitor
165 	 *            group visitor to emit the project's coverage to
166 	 * @param bundeName
167 	 *            name for this project in the report
168 	 * @param project
169 	 *            the MavenProject
170 	 * @param includes
171 	 *            list of includes patterns
172 	 * @param excludes
173 	 *            list of excludes patterns
174 	 * @param srcEncoding
175 	 *            encoding of the source files within this project
176 	 * @throws IOException
177 	 *             if class files can't be read
178 	 */
processProject(final IReportGroupVisitor visitor, final String bundeName, final MavenProject project, final List<String> includes, final List<String> excludes, final String srcEncoding)179 	public void processProject(final IReportGroupVisitor visitor,
180 			final String bundeName, final MavenProject project,
181 			final List<String> includes, final List<String> excludes,
182 			final String srcEncoding) throws IOException {
183 		processProject(visitor, bundeName, project, includes, excludes,
184 				new SourceFileCollection(project, srcEncoding));
185 	}
186 
processProject(final IReportGroupVisitor visitor, final String bundeName, final MavenProject project, final List<String> includes, final List<String> excludes, final ISourceFileLocator locator)187 	private void processProject(final IReportGroupVisitor visitor,
188 			final String bundeName, final MavenProject project,
189 			final List<String> includes, final List<String> excludes,
190 			final ISourceFileLocator locator) throws IOException {
191 		final CoverageBuilder builder = new CoverageBuilder();
192 		final File classesDir = new File(project.getBuild()
193 				.getOutputDirectory());
194 
195 		if (classesDir.isDirectory()) {
196 			final Analyzer analyzer = new Analyzer(
197 					loader.getExecutionDataStore(), builder);
198 			final FileFilter filter = new FileFilter(includes, excludes);
199 			for (final File file : filter.getFiles(classesDir)) {
200 				analyzer.analyzeAll(file);
201 			}
202 		}
203 
204 		final IBundleCoverage bundle = builder.getBundle(bundeName);
205 		logBundleInfo(bundle, builder.getNoMatchClasses());
206 
207 		visitor.visitBundle(bundle, locator);
208 	}
209 
logBundleInfo(final IBundleCoverage bundle, final Collection<IClassCoverage> nomatch)210 	private void logBundleInfo(final IBundleCoverage bundle,
211 			final Collection<IClassCoverage> nomatch) {
212 		log.info(format("Analyzed bundle '%s' with %s classes",
213 				bundle.getName(),
214 				Integer.valueOf(bundle.getClassCounter().getTotalCount())));
215 		if (!nomatch.isEmpty()) {
216 			log.warn(format(
217 					"Classes in bundle '%s' do no match with execution data. "
218 							+ "For report generation the same class files must be used as at runtime.",
219 					bundle.getName()));
220 			for (final IClassCoverage c : nomatch) {
221 				log.warn(format("Execution data for class %s does not match.",
222 						c.getName()));
223 			}
224 		}
225 		if (bundle.getClassCounter().getTotalCount() > 0
226 				&& bundle.getLineCounter().getTotalCount() == 0) {
227 			log.warn("To enable source code annotation class files have to be compiled with debug information.");
228 		}
229 	}
230 
231 	private class NoSourceLocator implements ISourceFileLocator {
232 
getSourceFile(final String packageName, final String fileName)233 		public Reader getSourceFile(final String packageName,
234 				final String fileName) {
235 			return null;
236 		}
237 
getTabWidth()238 		public int getTabWidth() {
239 			return 0;
240 		}
241 	}
242 
243 	private class SourceFileCollection implements ISourceFileLocator {
244 
245 		private final List<File> sourceRoots;
246 		private final String encoding;
247 
SourceFileCollection(final MavenProject project, final String encoding)248 		public SourceFileCollection(final MavenProject project,
249 				final String encoding) {
250 			this.sourceRoots = getCompileSourceRoots(project);
251 			this.encoding = encoding;
252 		}
253 
getSourceFile(final String packageName, final String fileName)254 		public Reader getSourceFile(final String packageName,
255 				final String fileName) throws IOException {
256 			final String r;
257 			if (packageName.length() > 0) {
258 				r = packageName + '/' + fileName;
259 			} else {
260 				r = fileName;
261 			}
262 			for (final File sourceRoot : sourceRoots) {
263 				final File file = new File(sourceRoot, r);
264 				if (file.exists() && file.isFile()) {
265 					return new InputStreamReader(new FileInputStream(file),
266 							encoding);
267 				}
268 			}
269 			return null;
270 		}
271 
getTabWidth()272 		public int getTabWidth() {
273 			return 4;
274 		}
275 	}
276 
getCompileSourceRoots(final MavenProject project)277 	private static List<File> getCompileSourceRoots(final MavenProject project) {
278 		final List<File> result = new ArrayList<File>();
279 		for (final Object path : project.getCompileSourceRoots()) {
280 			result.add(resolvePath(project, (String) path));
281 		}
282 		return result;
283 	}
284 
resolvePath(final MavenProject project, final String path)285 	private static File resolvePath(final MavenProject project,
286 			final String path) {
287 		File file = new File(path);
288 		if (!file.isAbsolute()) {
289 			file = new File(project.getBasedir(), path);
290 		}
291 		return file;
292 	}
293 
294 }
295