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 021package org.sonar.api.ce.measure; 022 023import com.google.common.collect.Multiset; 024import com.google.common.collect.TreeMultiset; 025import java.util.Arrays; 026import java.util.Collections; 027import java.util.Comparator; 028import java.util.Map; 029import java.util.Set; 030import java.util.TreeMap; 031import javax.annotation.CheckForNull; 032import org.sonar.api.utils.KeyValueFormat; 033 034/** 035 * Utility to build a distribution based on defined ranges 036 * <p/> 037 * <p>An example of usage : you wish to record the percentage of lines of code that belong to method 038 * with pre-defined ranges of complexity.</p> 039 * 040 * @since 5.2 041 */ 042public class RangeDistributionBuilder { 043 044 private Multiset<Number> distributionSet; 045 private boolean isEmpty = true; 046 private Number[] bottomLimits; 047 private boolean isValid = true; 048 049 public RangeDistributionBuilder() { 050 // Nothing to be done here, bottom limits will be automatically calculated when adding the first value 051 } 052 053 /** 054 * RangeDistributionBuilder for a defined range 055 * Each entry is initialized at zero 056 * 057 * @param bottomLimits the bottom limits of ranges to be used 058 */ 059 public RangeDistributionBuilder(Number[] bottomLimits) { 060 init(bottomLimits); 061 } 062 063 /** 064 * Increments an entry by 1 065 * 066 * @param value the value to use to pick the entry to increment 067 */ 068 public RangeDistributionBuilder add(Number value) { 069 return add(value, 1); 070 } 071 072 /** 073 * Increments an entry 074 * 075 * @param value the value to use to pick the entry to increment 076 * @param count the number by which to increment 077 */ 078 public RangeDistributionBuilder add(Number value, int count) { 079 if (greaterOrEqualsThan(value, bottomLimits[0])) { 080 addValue(value, count); 081 isEmpty = false; 082 } 083 return this; 084 } 085 086 /** 087 * Adds an existing Distribution to the current one. 088 * It will create the entries if they don't exist. 089 * Can be used to add the values of children resources for example 090 * <p/> 091 * The returned distribution will be invalidated in case the given value does not use the same bottom limits 092 * 093 * @param data the data to add to the current one 094 */ 095 public RangeDistributionBuilder add(String data) { 096 Map<Double, Double> map = KeyValueFormat.parse(data, KeyValueFormat.newDoubleConverter(), KeyValueFormat.newDoubleConverter()); 097 Number[] limits = map.keySet().toArray(new Number[map.size()]); 098 if (bottomLimits == null) { 099 init(limits); 100 101 } else if (!areSameLimits(bottomLimits, map.keySet())) { 102 isValid = false; 103 } 104 105 if (isValid) { 106 for (Map.Entry<Double, Double> entry : map.entrySet()) { 107 addLimitCount(entry.getKey(), entry.getValue().intValue()); 108 } 109 } 110 return this; 111 } 112 113 private void init(Number[] bottomLimits) { 114 this.bottomLimits = new Number[bottomLimits.length]; 115 System.arraycopy(bottomLimits, 0, this.bottomLimits, 0, this.bottomLimits.length); 116 Arrays.sort(this.bottomLimits); 117 changeDoublesToInts(); 118 distributionSet = TreeMultiset.create(NumberComparator.INSTANCE); 119 } 120 121 private void changeDoublesToInts() { 122 boolean onlyInts = true; 123 for (Number bottomLimit : bottomLimits) { 124 if (NumberComparator.INSTANCE.compare(bottomLimit.intValue(), bottomLimit.doubleValue()) != 0) { 125 onlyInts = false; 126 } 127 } 128 if (onlyInts) { 129 for (int i = 0; i < bottomLimits.length; i++) { 130 bottomLimits[i] = bottomLimits[i].intValue(); 131 } 132 } 133 } 134 135 private static boolean areSameLimits(Number[] bottomLimits, Set<Double> limits) { 136 if (limits.size() == bottomLimits.length) { 137 for (Number l : bottomLimits) { 138 if (!limits.contains(l.doubleValue())) { 139 return false; 140 } 141 } 142 return true; 143 } 144 return false; 145 } 146 147 private RangeDistributionBuilder addLimitCount(Number limit, int count) { 148 for (Number bottomLimit : bottomLimits) { 149 if (NumberComparator.INSTANCE.compare(bottomLimit.doubleValue(), limit.doubleValue()) == 0) { 150 addValue(limit, count); 151 isEmpty = false; 152 return this; 153 } 154 } 155 isValid = false; 156 return this; 157 } 158 159 private void addValue(Number value, int count) { 160 for (int i = bottomLimits.length - 1; i >= 0; i--) { 161 if (greaterOrEqualsThan(value, bottomLimits[i])) { 162 this.distributionSet.add(bottomLimits[i], count); 163 return; 164 } 165 } 166 } 167 168 /** 169 * @return whether the current object is empty or not 170 */ 171 public boolean isEmpty() { 172 return isEmpty; 173 } 174 175 /** 176 * Used to build a measure from the current object 177 * 178 * @return the built measure 179 */ 180 @CheckForNull 181 public String build() { 182 if (isValid) { 183 return KeyValueFormat.format(toMap()); 184 } 185 return null; 186 } 187 188 private Map<Number, Integer> toMap() { 189 if (bottomLimits == null || bottomLimits.length == 0) { 190 return Collections.emptyMap(); 191 } 192 Map<Number, Integer> map = new TreeMap<>(); 193 for (Number value : bottomLimits) { 194 map.put(value, distributionSet.count(value)); 195 } 196 return map; 197 } 198 199 private static boolean greaterOrEqualsThan(Number n1, Number n2) { 200 return NumberComparator.INSTANCE.compare(n1, n2) >= 0; 201 } 202 203 private enum NumberComparator implements Comparator<Number> { 204 INSTANCE; 205 206 @Override 207 public int compare(Number n1, Number n2) { 208 return ((Double) n1.doubleValue()).compareTo(n2.doubleValue()); 209 } 210 } 211 212}