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.api.measures;
022    
023    import org.sonar.api.resources.ResourceUtils;
024    
025    import java.util.List;
026    
027    import static com.google.common.collect.Lists.newArrayList;
028    
029    /**
030     * Formula used to compute an average for a given metric A, which is the result of the sum of measures of this metric (A) divided by another metric (B).
031     * <p/>
032     * For example: to compute the metric "complexity by file", the main metric (A) is "complexity" and the other metric (B) is "file".
033     *
034     * @since 3.0
035     */
036    public class AverageFormula implements Formula {
037    
038      private Metric mainMetric;
039      private Metric byMetric;
040      private Metric fallbackMetric;
041    
042      /**
043       * This method should be private but it kep package-protected because of AverageComplexityFormula.
044       */
045      AverageFormula(Metric mainMetric, Metric byMetric) {
046        this.mainMetric = mainMetric;
047        this.byMetric = byMetric;
048      }
049    
050      /**
051       * Creates a new {@link AverageFormula} class.
052       *
053       * @param main The metric on which average should be calculated (ex.: "complexity")
054       * @param by   The metric used to divide the main metric to compute average (ex.: "file" for "complexity by file")
055       */
056      public static AverageFormula create(Metric main, Metric by) {
057        return new AverageFormula(main, by);
058      }
059    
060      /**
061       * Set a fallback metric if no measures found for the main metric.
062       *
063       * @param fallbackMetric The fallback metric
064       * @since 3.6
065       */
066      public AverageFormula setFallbackForMainMetric(Metric fallbackMetric) {
067        this.fallbackMetric = fallbackMetric;
068        return this;
069      }
070    
071      /**
072       * {@inheritDoc}
073       */
074      public List<Metric> dependsUponMetrics() {
075        return fallbackMetric != null ? newArrayList(mainMetric, fallbackMetric, byMetric) : newArrayList(mainMetric, byMetric);
076      }
077    
078      /**
079       * {@inheritDoc}
080       */
081      public Measure calculate(FormulaData data, FormulaContext context) {
082        if (!shouldDecorateResource(data, context)) {
083          return null;
084        }
085    
086        Measure result;
087        if (ResourceUtils.isFile(context.getResource())) {
088          result = calculateForFile(data, context);
089        } else {
090          result = calculateOnChildren(data, context);
091        }
092        return result;
093      }
094    
095      private Measure calculateOnChildren(FormulaData data, FormulaContext context) {
096        Measure result = null;
097    
098        double totalByMeasure = 0;
099        double totalMainMeasure = 0;
100        boolean hasApplicableChildren = false;
101    
102        for (FormulaData childrenData : data.getChildren()) {
103          Double fallbackMeasure = fallbackMetric != null ? MeasureUtils.getValue(childrenData.getMeasure(fallbackMetric), null) : null;
104          Double childrenByMeasure = MeasureUtils.getValue(childrenData.getMeasure(byMetric), null);
105          Double childrenMainMeasure = MeasureUtils.getValue(childrenData.getMeasure(mainMetric), fallbackMeasure);
106          if (childrenMainMeasure != null && childrenByMeasure != null && childrenByMeasure > 0.0) {
107            totalByMeasure += childrenByMeasure;
108            totalMainMeasure += childrenMainMeasure;
109            hasApplicableChildren = true;
110          }
111        }
112        if (hasApplicableChildren) {
113          result = new Measure(context.getTargetMetric(), totalMainMeasure / totalByMeasure);
114        }
115        return result;
116      }
117    
118      private Measure calculateForFile(FormulaData data, FormulaContext context) {
119        Measure result = null;
120    
121        Double fallbackMeasure = fallbackMetric != null ? MeasureUtils.getValue(data.getMeasure(fallbackMetric), null) : null;
122        Double byMeasure = MeasureUtils.getValue(data.getMeasure(byMetric), null);
123        Double mainMeasure = MeasureUtils.getValue(data.getMeasure(mainMetric), fallbackMeasure);
124        if (mainMeasure != null && byMeasure != null && byMeasure > 0.0) {
125          result = new Measure(context.getTargetMetric(), mainMeasure / byMeasure);
126        }
127    
128        return result;
129      }
130    
131      private boolean shouldDecorateResource(FormulaData data, FormulaContext context) {
132        return !MeasureUtils.hasValue(data.getMeasure(context.getTargetMetric()));
133      }
134    }