001/*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * Sonar is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019 */
020package org.sonar.plugins.jacoco;
021
022import com.google.common.annotations.VisibleForTesting;
023import com.google.common.base.Strings;
024import org.apache.commons.lang.StringUtils;
025import org.jacoco.core.analysis.*;
026import org.jacoco.core.data.ExecutionDataReader;
027import org.jacoco.core.data.ExecutionDataStore;
028import org.jacoco.core.data.SessionInfoStore;
029import org.jacoco.core.runtime.WildcardMatcher;
030import org.sonar.api.batch.SensorContext;
031import org.sonar.api.measures.CoverageMeasuresBuilder;
032import org.sonar.api.measures.Measure;
033import org.sonar.api.resources.JavaFile;
034import org.sonar.api.resources.Project;
035import org.sonar.api.utils.SonarException;
036
037import java.io.File;
038import java.io.FileInputStream;
039import java.io.IOException;
040import java.util.Collection;
041
042/**
043 * @author Evgeny Mandrikov
044 */
045public abstract class AbstractAnalyzer {
046
047  public final void analyse(Project project, SensorContext context) {
048    final File buildOutputDir = project.getFileSystem().getBuildOutputDir();
049    if (!buildOutputDir.exists()) {
050      JaCoCoUtils.LOG.info("Project coverage is set to 0% as build output directory doesn't exists: {}", buildOutputDir);
051      return;
052    }
053    String path = getReportPath(project);
054    File jacocoExecutionData = project.getFileSystem().resolvePath(path);
055
056    WildcardMatcher excludes = new WildcardMatcher(Strings.nullToEmpty(getExcludes(project)));
057    try {
058      readExecutionData(jacocoExecutionData, buildOutputDir, context, excludes);
059    } catch (IOException e) {
060      throw new SonarException(e);
061    }
062  }
063
064  public final void readExecutionData(File jacocoExecutionData, File buildOutputDir, SensorContext context, WildcardMatcher excludes) throws IOException {
065    SessionInfoStore sessionInfoStore = new SessionInfoStore();
066    ExecutionDataStore executionDataStore = new ExecutionDataStore();
067
068    if (jacocoExecutionData == null || !jacocoExecutionData.exists() || !jacocoExecutionData.isFile()) {
069      JaCoCoUtils.LOG.info("Project coverage is set to 0% as no JaCoCo execution data has been dumped: {}", jacocoExecutionData);
070    } else {
071      JaCoCoUtils.LOG.info("Analysing {}", jacocoExecutionData);
072      ExecutionDataReader reader = new ExecutionDataReader(new FileInputStream(jacocoExecutionData));
073      reader.setSessionInfoVisitor(sessionInfoStore);
074      reader.setExecutionDataVisitor(executionDataStore);
075      reader.read();
076    }
077
078    CoverageBuilder coverageBuilder = new CoverageBuilder();
079    Analyzer analyzer = new Analyzer(executionDataStore, coverageBuilder);
080    analyzeAll(analyzer, buildOutputDir);
081
082    int analyzedResources = 0;
083    for (ISourceFileCoverage coverage : coverageBuilder.getSourceFiles()) {
084      JavaFile resource = getResource(coverage);
085      // Do not save measures on resource which doesn't exist in the context
086      if (context.getResource(resource) != null) {
087        if (!isExcluded(coverage, excludes)) {
088          analyzeFile(resource, coverage, context);
089        }
090        analyzedResources++;
091      }
092    }
093    if (analyzedResources == 0) {
094      JaCoCoUtils.LOG.warn("Coverage information was not collected. Perhaps you forget to include debug information into compiled classes?");
095    }
096  }
097
098  private static boolean isExcluded(ISourceFileCoverage coverage, WildcardMatcher excludesMatcher) {
099    String name = coverage.getPackageName() + "/" + coverage.getName();
100    return excludesMatcher.matches(name);
101  }
102
103  @VisibleForTesting
104  static JavaFile getResource(ISourceFileCoverage coverage) {
105    String packageName = StringUtils.replaceChars(coverage.getPackageName(), '/', '.');
106    String fileName = StringUtils.substringBeforeLast(coverage.getName(), ".");
107    return new JavaFile(packageName, fileName);
108  }
109
110  /**
111   * Copied from {@link Analyzer#analyzeAll(File)} in order to add logging.
112   */
113  private void analyzeAll(Analyzer analyzer, File file) {
114    if (file.isDirectory()) {
115      for (File f : file.listFiles()) {
116        analyzeAll(analyzer, f);
117      }
118    } else {
119      try {
120        analyzer.analyzeAll(file);
121      } catch (Exception e) {
122        JaCoCoUtils.LOG.warn("Exception during analysis of file " + file.getAbsolutePath(), e);
123      }
124    }
125  }
126
127  private void analyzeFile(JavaFile resource, ISourceFileCoverage coverage, SensorContext context) {
128    CoverageMeasuresBuilder builder = CoverageMeasuresBuilder.create();
129    for (int lineId = coverage.getFirstLine(); lineId <= coverage.getLastLine(); lineId++) {
130      final int hits;
131      ILine line = coverage.getLine(lineId);
132      switch (line.getInstructionCounter().getStatus()) {
133        case ICounter.FULLY_COVERED:
134        case ICounter.PARTLY_COVERED:
135          hits = 1;
136          break;
137        case ICounter.NOT_COVERED:
138          hits = 0;
139          break;
140        case ICounter.EMPTY:
141          continue;
142        default:
143          JaCoCoUtils.LOG.warn("Unknown status for line {} in {}", lineId, resource);
144          continue;
145      }
146      builder.setHits(lineId, hits);
147
148      ICounter branchCounter = line.getBranchCounter();
149      int conditions = branchCounter.getTotalCount();
150      if (conditions > 0) {
151        int coveredConditions = branchCounter.getCoveredCount();
152        builder.setConditions(lineId, conditions, coveredConditions);
153      }
154    }
155
156    saveMeasures(context, resource, builder.createMeasures());
157  }
158
159  protected abstract void saveMeasures(SensorContext context, JavaFile resource, Collection<Measure> measures);
160
161  protected abstract String getReportPath(Project project);
162
163  protected abstract String getExcludes(Project project);
164
165}