001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2009 SonarSource SA
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.api.measures;
021    
022    import org.apache.commons.collections.SortedBag;
023    import org.apache.commons.collections.Transformer;
024    import org.apache.commons.collections.bag.TransformedSortedBag;
025    import org.apache.commons.collections.bag.TreeBag;
026    import org.sonar.api.utils.KeyValueFormat;
027    import org.sonar.api.utils.SonarException;
028    
029    import java.util.Arrays;
030    import java.util.Map;
031    
032    /**
033     * Utility to build a distribution based on defined ranges
034     *
035     * <p>An example of usage : you wish to record the percentage of lines of code that belong to method
036     * with pre-defined ranges of complexity.</p>
037     *
038     * @since 1.10
039     */
040    public class RangeDistributionBuilder implements MeasureBuilder {
041    
042      private Metric metric;
043      private SortedBag countBag;
044      private boolean isEmpty = true;
045      private final Number[] bottomLimits;
046    
047      /**
048       * RangeDistributionBuilder for a metric and a defined range
049       * Each entry is initialize at zero
050       *
051       * @param metric          the metric to record the measure against
052       * @param bottomLimits    the bottom limits of ranges to be used
053       */
054      public RangeDistributionBuilder(Metric metric, Number[] bottomLimits) {
055        setMetric(metric);
056        this.bottomLimits = new Number[bottomLimits.length];
057        System.arraycopy(bottomLimits, 0, this.bottomLimits, 0, this.bottomLimits.length);
058        Arrays.sort(this.bottomLimits);
059        countBag = TransformedSortedBag.decorate(new TreeBag(), new RangeTransformer());
060        doClear();
061      }
062    
063      /**
064       * Gives the bottom limits of ranges used
065       *
066       * @return the bottom limits of defined range for the distribution
067       */
068      public Number[] getBottomLimits() {
069        return bottomLimits;
070      }
071    
072      /**
073       * Increments an entry by 1
074       *
075       * @param value the value to use to pick the entry to increment
076       * @return the current object
077       */
078      public RangeDistributionBuilder add(Number value) {
079        return add(value, 1);
080      }
081    
082      /**
083       * Increments an entry
084       *
085       * @param value the value to use to pick the entry to increment
086       * @param count the number by which to increment
087       * @return the current object
088       */
089      public RangeDistributionBuilder add(Number value, int count) {
090        if (value != null && greaterOrEqualsThan(value, bottomLimits[0])) {
091          this.countBag.add(value, count);
092          isEmpty = false;
093        }
094        return this;
095      }
096    
097      /**
098       * Adds an existing Distribution to the current one.
099       * It will create the entries if they don't exist.
100       * Can be used to add the values of children resources for example
101       *
102       * @param measure the measure to add to the current one
103       * @return the current object
104       */
105      public RangeDistributionBuilder add(Measure measure) {
106        if (measure != null && measure.getData() != null) {
107          Map<Double, Double> map = KeyValueFormat.parse(measure.getData(), new KeyValueFormat.DoubleNumbersPairTransformer());
108          for (Map.Entry<Double, Double> entry : map.entrySet()) {
109            add(entry.getKey(), entry.getValue().intValue());
110          }
111        }
112        return this;
113      }
114    
115      /**
116       * Resets all entries to zero
117       *
118       * @return the current object
119       */
120      public RangeDistributionBuilder clear() {
121        doClear();
122        return this;
123      }
124    
125      private void doClear() {
126        countBag.clear();
127        countBag.addAll(Arrays.asList(bottomLimits));
128        isEmpty = true;
129      }
130    
131      /**
132       * @return whether the current object is empty or not
133       */
134      public boolean isEmpty() {
135        return isEmpty;
136      }
137    
138      /**
139       * Shortcut for <code>build(true)</code>
140       * 
141       * @return the built measure
142       */
143      public Measure build() {
144        return build(true);
145      }
146    
147      /**
148       * Used to build a measure from the current object
149       *
150       * @param allowEmptyData should be built if current object is empty
151       * @return the built measure
152       */
153      public Measure build(boolean allowEmptyData) {
154        if (!isEmpty || allowEmptyData) {
155          return new Measure(metric, KeyValueFormat.format(countBag, -1));
156        }
157        return null;
158      }
159    
160      private class RangeTransformer implements Transformer {
161        public Object transform(Object o) {
162          Number n = (Number) o;
163          for (int i = bottomLimits.length - 1; i >= 0; i--) {
164            if (greaterOrEqualsThan(n, bottomLimits[i])) {
165              return bottomLimits[i];
166            }
167          }
168          return null;
169        }
170      }
171    
172      private static boolean greaterOrEqualsThan(Number n1, Number n2) {
173        return n1.doubleValue() >= n2.doubleValue();
174      }
175    
176      private void setMetric(Metric metric) {
177        if (metric == null || !metric.isDataType()) {
178          throw new SonarException("Metric is null or has unvalid type");
179        }
180        this.metric = metric;
181      }
182    }