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     * An utility to build a range distribution based on 2 criteria: one is measure, the other one is recorded.
034     *
035     * <p>An example of usage : you wish to save the number of lines that belong to methods that have complexity
036     * belonging to certain ranges.</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       *
050       * @param metric          the metric for the measure being built
051       * @param bottomLimits    the bottom limits for lookup
052       */
053      public RangeDistributionBuilder(Metric metric, Number[] bottomLimits) {
054        setMetric(metric);
055        this.bottomLimits = new Number[bottomLimits.length];
056        System.arraycopy(bottomLimits, 0, this.bottomLimits, 0, this.bottomLimits.length);
057        Arrays.sort(this.bottomLimits);
058        countBag = TransformedSortedBag.decorate(new TreeBag(), new RangeTransformer());
059        doClear();
060      }
061    
062      /**
063       * Gets the defined bottom limits
064       *
065       * @return the bottom limits of defined range for the distribution
066       */
067      public Number[] getBottomLimits() {
068        return bottomLimits;
069      }
070    
071      /**
072       * Add 1 to the range object belongs to
073       *
074       * @param object the Number to use to pick the range
075       * @return this
076       */
077      public RangeDistributionBuilder add(Number object) {
078        return add(object, 1);
079      }
080    
081      public RangeDistributionBuilder add(Number object, int count) {
082        if (object != null && greaterOrEqualsThan(object, bottomLimits[0])) {
083          this.countBag.add(object, count);
084          isEmpty = false;
085        }
086        return this;
087      }
088    
089      public RangeDistributionBuilder add(Measure measure) {
090        if (measure != null && measure.getData() != null) {
091          Map<Double, Double> map = KeyValueFormat.parse(measure.getData(), new KeyValueFormat.DoubleNumbersPairTransformer());
092          for (Map.Entry<Double, Double> entry : map.entrySet()) {
093            add(entry.getKey(), entry.getValue().intValue());
094          }
095        }
096        return this;
097      }
098    
099      public RangeDistributionBuilder clear() {
100        doClear();
101        return this;
102      }
103    
104      private void doClear() {
105        countBag.clear();
106        countBag.addAll(Arrays.asList(bottomLimits));
107        isEmpty = true;
108      }
109    
110      public boolean isEmpty() {
111        return isEmpty;
112      }
113    
114      public Measure build() {
115        return build(true);
116      }
117    
118      public Measure build(boolean allowEmptyData) {
119        if (!isEmpty || allowEmptyData) {
120          return new Measure(metric, KeyValueFormat.format(countBag, -1));
121        }
122        return null;
123      }
124    
125      private class RangeTransformer implements Transformer {
126        public Object transform(Object o) {
127          Number n = (Number) o;
128          for (int i = bottomLimits.length - 1; i >= 0; i--) {
129            if (greaterOrEqualsThan(n, bottomLimits[i])) {
130              return bottomLimits[i];
131            }
132          }
133          return null;
134        }
135      }
136    
137      private static boolean greaterOrEqualsThan(Number n1, Number n2) {
138        return n1.doubleValue() >= n2.doubleValue();
139      }
140    
141      private void setMetric(Metric metric) {
142        if (metric == null || !metric.isDataType()) {
143          throw new SonarException("Metric is null or has unvalid type");
144        }
145        this.metric = metric;
146      }
147    }