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