1 /******************************************************************************* 2 * Copyright (c) 2009, 2019 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.containsCode() 226 && bundle.getLineCounter().getTotalCount() == 0) { 227 log.warn( 228 "To enable source code annotation class files have to be compiled with debug information."); 229 } 230 } 231 232 private class NoSourceLocator implements ISourceFileLocator { 233 getSourceFile(final String packageName, final String fileName)234 public Reader getSourceFile(final String packageName, 235 final String fileName) { 236 return null; 237 } 238 getTabWidth()239 public int getTabWidth() { 240 return 0; 241 } 242 } 243 244 private class SourceFileCollection implements ISourceFileLocator { 245 246 private final List<File> sourceRoots; 247 private final String encoding; 248 SourceFileCollection(final MavenProject project, final String encoding)249 public SourceFileCollection(final MavenProject project, 250 final String encoding) { 251 this.sourceRoots = getCompileSourceRoots(project); 252 this.encoding = encoding; 253 } 254 getSourceFile(final String packageName, final String fileName)255 public Reader getSourceFile(final String packageName, 256 final String fileName) throws IOException { 257 final String r; 258 if (packageName.length() > 0) { 259 r = packageName + '/' + fileName; 260 } else { 261 r = fileName; 262 } 263 for (final File sourceRoot : sourceRoots) { 264 final File file = new File(sourceRoot, r); 265 if (file.exists() && file.isFile()) { 266 return new InputStreamReader(new FileInputStream(file), 267 encoding); 268 } 269 } 270 return null; 271 } 272 getTabWidth()273 public int getTabWidth() { 274 return 4; 275 } 276 } 277 getCompileSourceRoots(final MavenProject project)278 private static List<File> getCompileSourceRoots(final MavenProject project) { 279 final List<File> result = new ArrayList<File>(); 280 for (final Object path : project.getCompileSourceRoots()) { 281 result.add(resolvePath(project, (String) path)); 282 } 283 return result; 284 } 285 resolvePath(final MavenProject project, final String path)286 private static File resolvePath(final MavenProject project, 287 final String path) { 288 File file = new File(path); 289 if (!file.isAbsolute()) { 290 file = new File(project.getBasedir(), path); 291 } 292 return file; 293 } 294 295 } 296