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 */
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 }