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.Maps;
023    import org.apache.commons.lang.StringUtils;
024    import org.sonar.api.CoreProperties;
025    import org.sonar.api.batch.*;
026    import org.sonar.api.measures.*;
027    import org.sonar.api.resources.Project;
028    import org.sonar.api.resources.Qualifiers;
029    import org.sonar.api.resources.Resource;
030    import org.sonar.api.resources.Scopes;
031    import org.sonar.batch.components.PastMeasuresLoader;
032    import org.sonar.batch.components.PastSnapshot;
033    import org.sonar.batch.components.TimeMachineConfiguration;
034    
035    import java.util.Collection;
036    import java.util.List;
037    import java.util.Map;
038    
039    @DependedUpon(DecoratorBarriers.END_OF_TIME_MACHINE)
040    public 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 ruleId =  (measure instanceof RuleMeasure ? ((RuleMeasure)measure).getRule().getId() : null);
102    
103          Object[] pastMeasure = pastMeasuresByKey.get(new MeasureKey(metricId, characteristicId, ruleId));
104          if (updateVariation(measure, pastMeasure, index)) {
105            context.saveMeasure(measure);
106          }
107        }
108      }
109    
110      boolean updateVariation(Measure measure, Object[] pastMeasure, int index) {
111        if (pastMeasure != null && PastMeasuresLoader.hasValue(pastMeasure) && measure.getValue() != null) {
112          double variation = (measure.getValue().doubleValue() - PastMeasuresLoader.getValue(pastMeasure));
113          measure.setVariation(index, variation);
114          return true;
115        }
116        return false;
117      }
118    
119      @Override
120      public String toString() {
121        return getClass().getSimpleName();
122      }
123    
124      static final class MeasureKey {
125        int metricId;
126        Integer characteristicId;
127        Integer ruleId;
128    
129        MeasureKey(Object[] pastFields) {
130          metricId = PastMeasuresLoader.getMetricId(pastFields);
131          characteristicId = PastMeasuresLoader.getCharacteristicId(pastFields);
132          ruleId = PastMeasuresLoader.getRuleId(pastFields);
133        }
134    
135        MeasureKey(int metricId, Integer characteristicId, Integer ruleId) {
136          this.metricId = metricId;
137          this.characteristicId = characteristicId;
138          this.ruleId = ruleId;
139        }
140    
141        @Override
142        public boolean equals(Object o) {
143          if (this == o) {
144            return true;
145          }
146          if (o == null || getClass() != o.getClass()) {
147            return false;
148          }
149          MeasureKey that = (MeasureKey) o;
150          if (metricId != that.metricId) {
151            return false;
152          }
153          if (characteristicId != null ? !characteristicId.equals(that.characteristicId) : that.characteristicId != null) {
154            return false;
155          }
156          if (ruleId != null ? !ruleId.equals(that.ruleId) : that.ruleId != null) {
157            return false;
158          }
159          return true;
160        }
161    
162        @Override
163        public int hashCode() {
164          int result = metricId;
165          result = 31 * result + (characteristicId != null ? characteristicId.hashCode() : 0);
166          result = 31 * result + (ruleId != null ? ruleId.hashCode() : 0);
167          return result;
168        }
169      }
170    }