001    /*
002     * SonarQube, open source software quality management tool.
003     * Copyright (C) 2008-2014 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * SonarQube 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     * SonarQube 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 License
017     * along with this program; if not, write to the Free Software Foundation,
018     * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019     */
020    
021    package org.sonar.batch.debt;
022    
023    import com.google.common.annotations.VisibleForTesting;
024    import com.google.common.collect.Lists;
025    import org.sonar.api.batch.Decorator;
026    import org.sonar.api.batch.DecoratorContext;
027    import org.sonar.api.batch.DependedUpon;
028    import org.sonar.api.batch.DependsUpon;
029    import org.sonar.api.batch.fs.FileSystem;
030    import org.sonar.api.batch.fs.InputFile;
031    import org.sonar.api.measures.CoreMetrics;
032    import org.sonar.api.measures.Measure;
033    import org.sonar.api.measures.Metric;
034    import org.sonar.api.resources.Project;
035    import org.sonar.api.resources.Resource;
036    import org.sonar.api.resources.ResourceUtils;
037    
038    import javax.annotation.Nullable;
039    
040    import java.util.Arrays;
041    import java.util.Collection;
042    import java.util.List;
043    
044    /**
045     * Decorator that computes Sqale Rating metric
046     */
047    public final class SqaleRatingDecorator implements Decorator {
048    
049      private final SqaleRatingSettings sqaleRatingSettings;
050      private final Metric[] metrics;
051      private final FileSystem fs;
052    
053      public SqaleRatingDecorator(SqaleRatingSettings sqaleRatingSettings, Metric[] metrics, FileSystem fs) {
054        this.sqaleRatingSettings = sqaleRatingSettings;
055        this.metrics = Arrays.copyOf(metrics, metrics.length);
056        this.fs = fs;
057      }
058    
059      @VisibleForTesting
060      SqaleRatingDecorator() {
061        this.sqaleRatingSettings = null;
062        this.metrics = null;
063        this.fs = null;
064      }
065    
066      public boolean shouldExecuteOnProject(Project project) {
067        return true;
068      }
069    
070      @DependsUpon
071      public List<Metric> dependsOnMetrics() {
072        return Lists.<Metric>newArrayList(CoreMetrics.TECHNICAL_DEBT,
073          // ncloc and complexity are the two possible metrics to be used to calculate the development cost
074          CoreMetrics.NCLOC, CoreMetrics.COMPLEXITY);
075      }
076    
077      @DependedUpon
078      public List<Metric> generatesMetrics() {
079        return Lists.<Metric>newArrayList(CoreMetrics.SQALE_RATING, CoreMetrics.DEVELOPMENT_COST, CoreMetrics.SQALE_DEBT_RATIO);
080      }
081    
082      public void decorate(Resource resource, DecoratorContext context) {
083        if (ResourceUtils.isPersistable(resource) && !ResourceUtils.isUnitTestClass(resource)) {
084          Long developmentCost = getDevelopmentCost(context);
085          context.saveMeasure(new Measure(CoreMetrics.DEVELOPMENT_COST, Long.toString(developmentCost)));
086    
087          long debt = getMeasureValue(context, CoreMetrics.TECHNICAL_DEBT);
088          double density = computeDensity(debt, developmentCost);
089          context.saveMeasure(CoreMetrics.SQALE_DEBT_RATIO, 100.0 * density);
090    
091          SqaleRatingGrid ratingGrid = new SqaleRatingGrid(sqaleRatingSettings.getRatingGrid());
092          context.saveMeasure(createRatingMeasure(ratingGrid.getRatingForDensity(density)));
093        }
094      }
095    
096      private Measure createRatingMeasure(int rating) {
097        return new Measure(CoreMetrics.SQALE_RATING).setIntValue(rating).setData(toRatingLetter(rating));
098      }
099    
100      static String toRatingLetter(@Nullable Integer rating) {
101        if (rating != null) {
102          return SqaleRatingGrid.SqaleRating.createForIndex(rating).name();
103        }
104        return null;
105      }
106    
107      private long getDevelopmentCost(DecoratorContext context) {
108        InputFile file = fs.inputFile(fs.predicates().hasRelativePath(context.getResource().getKey()));
109        if (file != null) {
110          String language = file.language();
111          return getMeasureValue(context, sqaleRatingSettings.getSizeMetric(language, metrics)) * sqaleRatingSettings.getDevCost(language);
112        } else {
113          Collection<Measure> childrenMeasures = context.getChildrenMeasures(CoreMetrics.DEVELOPMENT_COST);
114          Double sum = sum(childrenMeasures);
115          return sum.longValue();
116        }
117      }
118    
119      private static Double sum(@Nullable Collection<Measure> measures) {
120        if (measures == null) {
121          return 0d;
122        }
123        double sum = 0d;
124        for (Measure measure : measures) {
125          String data = measure.getData();
126          if (data != null) {
127            sum += Double.parseDouble(data);
128          }
129        }
130        return sum;
131      }
132    
133      private long getMeasureValue(DecoratorContext context, Metric metric) {
134        Measure measure = context.getMeasure(metric);
135        if (measure != null) {
136          return measure.getValue().longValue();
137        }
138        return 0;
139      }
140    
141      protected double computeDensity(double debt, double developmentCost) {
142        if (developmentCost != 0) {
143          return debt / developmentCost;
144        }
145        return 0;
146      }
147    
148    }