001    /*
002     * SonarQube, open source software quality management tool.
003     * Copyright (C) 2008-2013 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    package org.sonar.api.measures;
021    
022    import org.apache.commons.collections.SortedBag;
023    import org.apache.commons.collections.bag.TreeBag;
024    import org.apache.commons.lang.StringUtils;
025    import org.apache.commons.lang.math.NumberUtils;
026    import org.sonar.api.utils.KeyValueFormat;
027    import org.sonar.api.utils.SonarException;
028    
029    import java.util.Map;
030    
031    /**
032     * Utility to build a distribution based on discrete values
033     *
034     * <p>An example of usage : you wish to record the number of violations for each level of rules priority</p>
035     *
036     * @since 1.10
037     */
038    public class CountDistributionBuilder implements MeasureBuilder {
039    
040      private Metric metric;
041      // TODO to be replaced by com.google.common.collect.SortedMultiset while upgrading to Guava 11+
042      private SortedBag countBag;
043    
044      /**
045       * Creates an empty CountDistributionBuilder for a specified metric
046       *
047       * @param metric the metric
048       */
049      public CountDistributionBuilder(Metric metric) {
050        setMetric(metric);
051        this.countBag = new TreeBag();
052      }
053    
054      /**
055       * Increments an entry
056       *
057       * @param value the value that should be incremented
058       * @param count the number by which to increment
059       * @return the current object
060       */
061      public CountDistributionBuilder add(Object value, int count) {
062        if (count == 0) {
063          addZero(value);
064    
065        } else {
066          if (this.countBag.add(value, count)) {
067            this.countBag.add(value, 1);//hack
068          }
069        }
070        return this;
071      }
072    
073      /**
074       * Increments an entry by one
075       *
076       * @param value the value that should be incremented
077       * @return the current object
078       */
079      public CountDistributionBuilder add(Object value) {
080        return add(value, 1);
081      }
082    
083      /**
084       * Adds an entry without a zero count if it does not exist
085       *
086       * @param value the entry to be added
087       * @return the current object
088       */
089      public CountDistributionBuilder addZero(Object value) {
090        if (!countBag.contains(value)) {
091          countBag.add(value, 1);
092        }
093        return this;
094      }
095    
096      /**
097       * Adds an existing Distribution to the current one.
098       * It will create the entries if they don't exist.
099       * Can be used to add the values of children resources for example
100       *
101       * @param measure the measure to add to the current one
102       * @return the current object
103       */
104      public CountDistributionBuilder add(Measure measure) {
105        if (measure != null && measure.getData() != null) {
106          Map<String, String> map = KeyValueFormat.parse(measure.getData());
107          for (Map.Entry<String, String> entry : map.entrySet()) {
108            String key = entry.getKey();
109            int value = (StringUtils.isBlank(entry.getValue()) ? 0 : Integer.parseInt(entry.getValue()));
110            if (NumberUtils.isNumber(key)) {
111              add(NumberUtils.toInt(key), value);
112            } else {
113              add(key, value);
114            }
115          }
116        }
117        return this;
118      }
119    
120      /**
121       * @return whether the current object is empty or not
122       */
123      public boolean isEmpty() {
124        return countBag.isEmpty();
125      }
126    
127      /**
128       * Resets all entries to zero
129       *
130       * @return the current object
131       */
132      public CountDistributionBuilder clear() {
133        countBag.clear();
134        return this;
135      }
136    
137      /**
138       * Shortcut for <code>build(true)</code>
139       *
140       * @return the built measure
141       */
142      public Measure build() {
143        return build(true);
144      }
145    
146      /**
147       * Used to build a measure from the current object
148       *
149       * @param allowEmptyData should be built if current object is empty
150       * @return the built measure
151       */
152      public Measure build(boolean allowEmptyData) {
153        if (!isEmpty() || allowEmptyData) {
154          return new Measure(metric, KeyValueFormat.format(countBag, -1)); //-1 is a hack to include zero values
155        }
156        return null;
157      }
158    
159      private void setMetric(Metric metric) {
160        if (metric == null || !metric.isDataType()) {
161          throw new SonarException("Metric is null or has unvalid type");
162        }
163        this.metric = metric;
164      }
165    }