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 }