001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 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     */
020    package org.sonar.plugins.core.timemachine;
021    
022    import com.google.common.collect.Lists;
023    import com.google.common.collect.Maps;
024    import org.apache.commons.lang.ObjectUtils;
025    import org.sonar.api.batch.*;
026    import org.sonar.api.measures.CoreMetrics;
027    import org.sonar.api.measures.Measure;
028    import org.sonar.api.measures.Metric;
029    import org.sonar.api.resources.Project;
030    import org.sonar.api.resources.Qualifiers;
031    import org.sonar.api.resources.Resource;
032    import org.sonar.api.resources.Scopes;
033    import org.sonar.api.utils.KeyValueFormat;
034    import org.sonar.batch.components.PastSnapshot;
035    import org.sonar.batch.components.TimeMachineConfiguration;
036    import org.sonar.core.NotDryRun;
037    
038    import java.util.Arrays;
039    import java.util.Date;
040    import java.util.List;
041    import java.util.Map;
042    
043    /**
044     * @since 2.7
045     */
046    @NotDryRun
047    @DependedUpon(DecoratorBarriers.END_OF_TIME_MACHINE)
048    public 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    }