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.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 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    }