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}