1/******************************************************************************* 2 * Copyright (c) 2009, 2017 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 *******************************************************************************/ 13package org.jacoco.maven; 14 15import static java.lang.String.format; 16 17import java.io.File; 18import java.io.FileInputStream; 19import java.io.FileOutputStream; 20import java.io.IOException; 21import java.io.InputStreamReader; 22import java.io.Reader; 23import java.util.ArrayList; 24import java.util.Collection; 25import java.util.List; 26import java.util.Locale; 27 28import org.apache.maven.plugin.logging.Log; 29import org.apache.maven.project.MavenProject; 30import org.jacoco.core.analysis.Analyzer; 31import org.jacoco.core.analysis.CoverageBuilder; 32import org.jacoco.core.analysis.IBundleCoverage; 33import org.jacoco.core.analysis.IClassCoverage; 34import org.jacoco.core.tools.ExecFileLoader; 35import org.jacoco.report.FileMultiReportOutput; 36import org.jacoco.report.IReportGroupVisitor; 37import org.jacoco.report.IReportVisitor; 38import org.jacoco.report.ISourceFileLocator; 39import org.jacoco.report.MultiReportVisitor; 40import org.jacoco.report.check.IViolationsOutput; 41import org.jacoco.report.check.Rule; 42import org.jacoco.report.check.RulesChecker; 43import org.jacoco.report.csv.CSVFormatter; 44import org.jacoco.report.html.HTMLFormatter; 45import 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 */ 59final 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 */ 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 */ 85 public void loadExecutionData(final File execFile) throws IOException { 86 log.info("Loading execution data file " + execFile); 87 loader.load(execFile); 88 } 89 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 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 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 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 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 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 */ 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 */ 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 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 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 233 public Reader getSourceFile(final String packageName, 234 final String fileName) { 235 return null; 236 } 237 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 248 public SourceFileCollection(final MavenProject project, 249 final String encoding) { 250 this.sourceRoots = getCompileSourceRoots(project); 251 this.encoding = encoding; 252 } 253 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 272 public int getTabWidth() { 273 return 4; 274 } 275 } 276 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 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