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.core.timemachine;
021
022import com.google.common.collect.Lists;
023import com.google.common.collect.Maps;
024import org.apache.commons.lang.ObjectUtils;
025import org.sonar.api.batch.*;
026import org.sonar.api.measures.CoreMetrics;
027import org.sonar.api.measures.Measure;
028import org.sonar.api.measures.Metric;
029import org.sonar.api.resources.Project;
030import org.sonar.api.resources.Qualifiers;
031import org.sonar.api.resources.Resource;
032import org.sonar.api.resources.Scopes;
033import org.sonar.api.utils.KeyValueFormat;
034import org.sonar.batch.components.PastSnapshot;
035import org.sonar.batch.components.TimeMachineConfiguration;
036import org.sonar.core.NotDryRun;
037
038import java.util.Arrays;
039import java.util.Date;
040import java.util.List;
041import java.util.Map;
042
043/**
044 * @since 2.7
045 */
046@NotDryRun
047@DependedUpon(DecoratorBarriers.END_OF_TIME_MACHINE)
048public abstract class AbstractNewCoverageFileAnalyzer implements Decorator {
049
050  private List<PeriodStruct> structs;
051
052  public AbstractNewCoverageFileAnalyzer(TimeMachineConfiguration timeMachineConfiguration) {
053    structs = Lists.newArrayList();
054    for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) {
055      structs.add(new PeriodStruct(pastSnapshot));
056    }
057  }
058
059  AbstractNewCoverageFileAnalyzer(List<PeriodStruct> structs) {
060    this.structs = structs;
061  }
062
063  public abstract Metric getCoverageLineHitsDataMetric();
064
065  public abstract Metric getConditionsByLineMetric();
066
067  public abstract Metric getCoveredConditionsByLineMetric();
068
069  public abstract  Metric getNewLinesToCoverMetric();
070
071  public abstract Metric getNewUncoveredLinesMetric();
072
073  public abstract Metric getNewConditionsToCoverMetric();
074
075  public abstract Metric getNewUncoveredConditionsMetric();
076
077  public boolean shouldExecuteOnProject(Project project) {
078    return project.isLatestAnalysis() && !structs.isEmpty();
079  }
080
081  private boolean shouldDecorate(Resource resource) {
082    return Scopes.isFile(resource) && !Qualifiers.UNIT_TEST_FILE.equals(resource.getQualifier());
083  }
084
085  @DependsUpon
086  public List<Metric> dependsOnMetrics() {
087
088    return Arrays.asList(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE,
089      getCoverageLineHitsDataMetric(), getConditionsByLineMetric(), getCoveredConditionsByLineMetric());
090  }
091
092  @DependedUpon
093  public List<Metric> generatesNewCoverageMetrics() {
094    return Arrays.asList(getNewLinesToCoverMetric(), getNewUncoveredLinesMetric(), getNewConditionsToCoverMetric(), getNewUncoveredConditionsMetric());
095  }
096
097  public void decorate(Resource resource, DecoratorContext context) {
098    if (shouldDecorate(resource)) {
099      doDecorate(context);
100    }
101  }
102
103  void doDecorate(DecoratorContext context) {
104    if (parse(context)) {
105      compute(context);
106    }
107  }
108
109  private boolean parse(DecoratorContext context) {
110    Measure lastCommits = context.getMeasure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE);
111    Measure hitsByLineMeasure = context.getMeasure(getCoverageLineHitsDataMetric());
112
113    if (lastCommits != null && lastCommits.hasData() && hitsByLineMeasure != null && hitsByLineMeasure.hasData()) {
114      Map<Integer, Date> datesByLine = KeyValueFormat.parseIntDateTime(lastCommits.getData());
115      Map<Integer, Integer> hitsByLine = parseCountByLine(hitsByLineMeasure);
116      Map<Integer, Integer> conditionsByLine = parseCountByLine(context.getMeasure(getConditionsByLineMetric()));
117      Map<Integer, Integer> coveredConditionsByLine = parseCountByLine(context.getMeasure(getCoveredConditionsByLineMetric()));
118
119      reset();
120
121      for (Map.Entry<Integer, Integer> entry : hitsByLine.entrySet()) {
122        int lineId = entry.getKey();
123        int hits = entry.getValue();
124        int conditions = (Integer) ObjectUtils.defaultIfNull(conditionsByLine.get(lineId), 0);
125        int coveredConditions = (Integer) ObjectUtils.defaultIfNull(coveredConditionsByLine.get(lineId), 0);
126        Date date = datesByLine.get(lineId);
127        for (PeriodStruct struct : structs) {
128          struct.analyze(date, hits, conditions, coveredConditions);
129        }
130      }
131
132      return true;
133    }
134    return false;
135  }
136
137  private void reset() {
138    for (PeriodStruct struct : structs) {
139      struct.reset();
140    }
141  }
142
143  private void compute(DecoratorContext context) {
144    Measure newLines = new Measure(getNewLinesToCoverMetric());
145    Measure newUncoveredLines = new Measure(getNewUncoveredLinesMetric());
146    Measure newConditions = new Measure(getNewConditionsToCoverMetric());
147    Measure newUncoveredConditions = new Measure(getNewUncoveredConditionsMetric());
148
149    for (PeriodStruct struct : structs) {
150      newLines.setVariation(struct.index, (double) struct.newLines);
151      newUncoveredLines.setVariation(struct.index, (double) (struct.newLines - struct.newCoveredLines));
152      newConditions.setVariation(struct.index, (double) struct.newConditions);
153      newUncoveredConditions.setVariation(struct.index, (double) struct.newConditions - struct.newCoveredConditions);
154    }
155
156    context.saveMeasure(newLines);
157    context.saveMeasure(newUncoveredLines);
158    context.saveMeasure(newConditions);
159    context.saveMeasure(newUncoveredConditions);
160  }
161
162  private Map<Integer, Integer> parseCountByLine(Measure measure) {
163    if (measure != null && measure.hasData()) {
164      return KeyValueFormat.parseIntInt(measure.getData());
165    }
166    return Maps.newHashMap();
167  }
168
169  public static final class PeriodStruct {
170    int index;
171    Date date;
172    int newLines = 0, newCoveredLines = 0, newConditions = 0, newCoveredConditions = 0;
173
174    PeriodStruct(PastSnapshot pastSnapshot) {
175      this.index = pastSnapshot.getIndex();
176      this.date = pastSnapshot.getTargetDate();
177    }
178
179    PeriodStruct(int index, Date date) {
180      this.index = index;
181      this.date = date;
182    }
183
184    void reset() {
185      newLines = 0;
186      newCoveredLines = 0;
187      newConditions = 0;
188      newCoveredConditions = 0;
189    }
190
191    void analyze(Date lineDate, int hits, int conditions, int coveredConditions) {
192      if (lineDate == null) {
193        // TODO warning
194
195      } else if (date == null || lineDate.after(date)) {
196        // TODO test if string comparison is faster or not
197        addLine(hits > 0);
198        addConditions(conditions, coveredConditions);
199      }
200    }
201
202    void addLine(boolean covered) {
203      newLines += 1;
204      if (covered) {
205        newCoveredLines += 1;
206      }
207    }
208
209    void addConditions(int count, int countCovered) {
210      newConditions += count;
211      if (count > 0) {
212        newCoveredConditions += countCovered;
213      }
214    }
215  }
216}