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.Maps; 023import org.apache.commons.lang.StringUtils; 024import org.sonar.api.CoreProperties; 025import org.sonar.api.batch.*; 026import org.sonar.api.measures.*; 027import org.sonar.api.resources.Project; 028import org.sonar.api.resources.Qualifiers; 029import org.sonar.api.resources.Resource; 030import org.sonar.api.resources.Scopes; 031import org.sonar.batch.components.PastMeasuresLoader; 032import org.sonar.batch.components.PastSnapshot; 033import org.sonar.batch.components.TimeMachineConfiguration; 034 035import java.util.Collection; 036import java.util.List; 037import java.util.Map; 038 039@DependedUpon(DecoratorBarriers.END_OF_TIME_MACHINE) 040public class VariationDecorator implements Decorator { 041 042 private List<PastSnapshot> projectPastSnapshots; 043 private MetricFinder metricFinder; 044 private PastMeasuresLoader pastMeasuresLoader; 045 private final boolean enabledFileVariation; 046 047 048 public VariationDecorator(PastMeasuresLoader pastMeasuresLoader, MetricFinder metricFinder, TimeMachineConfiguration configuration) { 049 this(pastMeasuresLoader, metricFinder, configuration.getProjectPastSnapshots(), configuration.isFileVariationEnabled()); 050 } 051 052 VariationDecorator(PastMeasuresLoader pastMeasuresLoader, MetricFinder metricFinder, List<PastSnapshot> projectPastSnapshots, boolean enabledFileVariation) { 053 this.pastMeasuresLoader = pastMeasuresLoader; 054 this.projectPastSnapshots = projectPastSnapshots; 055 this.metricFinder = metricFinder; 056 this.enabledFileVariation = enabledFileVariation; 057 } 058 059 public boolean shouldExecuteOnProject(Project project) { 060 return project.isLatestAnalysis(); 061 } 062 063 @DependsUpon 064 public Collection<Metric> dependsUponMetrics() { 065 return pastMeasuresLoader.getMetrics(); 066 } 067 068 public void decorate(Resource resource, DecoratorContext context) { 069 for (PastSnapshot projectPastSnapshot : projectPastSnapshots) { 070 if (shouldComputeVariation(projectPastSnapshot.getMode(), resource)) { 071 computeVariation(resource, context, projectPastSnapshot); 072 } 073 } 074 } 075 076 boolean shouldComputeVariation(String variationMode, Resource resource) { 077 if (Scopes.FILE.equals(resource.getScope()) && !Qualifiers.UNIT_TEST_FILE.equals(resource.getQualifier())) { 078 return enabledFileVariation && StringUtils.equals(variationMode, CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS); 079 } 080 081 // measures on files are currently purged, so past measures are not available on files 082 return StringUtils.equals(Scopes.PROJECT, resource.getScope()) || StringUtils.equals(Scopes.DIRECTORY, resource.getScope()); 083 } 084 085 private void computeVariation(Resource resource, DecoratorContext context, PastSnapshot pastSnapshot) { 086 List<Object[]> pastMeasures = pastMeasuresLoader.getPastMeasures(resource, pastSnapshot); 087 compareWithPastMeasures(context, pastSnapshot.getIndex(), pastMeasures); 088 } 089 090 void compareWithPastMeasures(DecoratorContext context, int index, List<Object[]> pastMeasures) { 091 Map<MeasureKey, Object[]> pastMeasuresByKey = Maps.newHashMap(); 092 for (Object[] pastMeasure : pastMeasures) { 093 pastMeasuresByKey.put(new MeasureKey(pastMeasure), pastMeasure); 094 } 095 096 // for each measure, search equivalent past measure 097 for (Measure measure : context.getMeasures(MeasuresFilters.all())) { 098 // compare with past measure 099 Integer metricId = (measure.getMetric().getId() != null ? measure.getMetric().getId() : metricFinder.findByKey(measure.getMetric().getKey()).getId()); 100 Integer characteristicId = (measure.getCharacteristic() != null ? measure.getCharacteristic().getId() : null); 101 Integer personId = measure.getPersonId(); 102 Integer ruleId = (measure instanceof RuleMeasure ? ((RuleMeasure)measure).getRule().getId() : null); 103 104 Object[] pastMeasure = pastMeasuresByKey.get(new MeasureKey(metricId, characteristicId, personId, ruleId)); 105 if (updateVariation(measure, pastMeasure, index)) { 106 context.saveMeasure(measure); 107 } 108 } 109 } 110 111 boolean updateVariation(Measure measure, Object[] pastMeasure, int index) { 112 if (pastMeasure != null && PastMeasuresLoader.hasValue(pastMeasure) && measure.getValue() != null) { 113 double variation = (measure.getValue().doubleValue() - PastMeasuresLoader.getValue(pastMeasure)); 114 measure.setVariation(index, variation); 115 return true; 116 } 117 return false; 118 } 119 120 @Override 121 public String toString() { 122 return getClass().getSimpleName(); 123 } 124 125 static final class MeasureKey { 126 int metricId; 127 Integer characteristicId; 128 Integer personId; 129 Integer ruleId; 130 131 MeasureKey(Object[] pastFields) { 132 metricId = PastMeasuresLoader.getMetricId(pastFields); 133 characteristicId = PastMeasuresLoader.getCharacteristicId(pastFields); 134 personId = PastMeasuresLoader.getPersonId(pastFields); 135 ruleId = PastMeasuresLoader.getRuleId(pastFields); 136 } 137 138 MeasureKey(int metricId, Integer characteristicId, Integer personId, Integer ruleId) { 139 this.metricId = metricId; 140 this.characteristicId = characteristicId; 141 this.personId = personId; 142 this.ruleId = ruleId; 143 } 144 145 @Override 146 public boolean equals(Object o) { 147 if (this == o) { 148 return true; 149 } 150 if (o == null || getClass() != o.getClass()) { 151 return false; 152 } 153 MeasureKey that = (MeasureKey) o; 154 if (metricId != that.metricId) { 155 return false; 156 } 157 if (characteristicId != null ? !characteristicId.equals(that.characteristicId) : that.characteristicId != null) { 158 return false; 159 } 160 if (personId != null ? !personId.equals(that.personId) : that.personId != null) { 161 return false; 162 } 163 if (ruleId != null ? !ruleId.equals(that.ruleId) : that.ruleId != null) { 164 return false; 165 } 166 return true; 167 } 168 169 @Override 170 public int hashCode() { 171 int result = metricId; 172 result = 31 * result + (characteristicId != null ? characteristicId.hashCode() : 0); 173 result = 31 * result + (personId != null ? personId.hashCode() : 0); 174 result = 31 * result + (ruleId != null ? ruleId.hashCode() : 0); 175 return result; 176 } 177 } 178}