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 }