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 */ 020package org.sonar.api.measures; 021 022import com.google.common.base.Function; 023import com.google.common.collect.Lists; 024import java.io.Serializable; 025import java.util.Arrays; 026import java.util.List; 027import javax.annotation.CheckForNull; 028import javax.annotation.Nonnull; 029import javax.annotation.Nullable; 030import org.apache.commons.lang.builder.ReflectionToStringBuilder; 031import org.apache.commons.lang.builder.ToStringStyle; 032import org.sonar.api.batch.BatchSide; 033import org.sonar.api.batch.InstantiationStrategy; 034import org.sonar.api.server.ServerSide; 035 036import static com.google.common.base.Objects.firstNonNull; 037import static com.google.common.base.Preconditions.checkArgument; 038import static org.apache.commons.lang.StringUtils.isNotBlank; 039 040@BatchSide 041@InstantiationStrategy(InstantiationStrategy.PER_BATCH) 042@ServerSide 043public class Metric<G extends Serializable> implements Serializable, org.sonar.api.batch.measure.Metric<G> { 044 045 /** 046 * @since 5.3 047 */ 048 public static final int DEFAULT_DECIMAL_SCALE = 1; 049 050 /** 051 * The maximum supported value of scale for decimal metrics 052 * @since 5.3 053 */ 054 public static final int MAX_DECIMAL_SCALE = 5; 055 056 /** 057 * A metric bigger value means a degradation 058 */ 059 public static final int DIRECTION_WORST = -1; 060 /** 061 * A metric bigger value means an improvement 062 */ 063 public static final int DIRECTION_BETTER = 1; 064 /** 065 * The metric direction has no meaning 066 */ 067 public static final int DIRECTION_NONE = 0; 068 069 public enum ValueType { 070 INT(Integer.class), 071 FLOAT(Double.class), 072 PERCENT(Double.class), 073 BOOL(Boolean.class), 074 STRING(String.class), 075 MILLISEC(Long.class), 076 DATA(String.class), 077 LEVEL(Metric.Level.class), 078 DISTRIB(String.class), 079 RATING(Integer.class), 080 WORK_DUR(Long.class); 081 082 private final Class valueClass; 083 084 ValueType(Class valueClass) { 085 this.valueClass = valueClass; 086 } 087 088 private Class valueType() { 089 return valueClass; 090 } 091 092 public static String[] names() { 093 ValueType[] values = values(); 094 String[] names = new String[values.length]; 095 for (int i = 0; i < values.length; i += 1) { 096 names[i] = values[i].name(); 097 } 098 099 return names; 100 } 101 } 102 103 public enum Level { 104 OK("Green"), WARN("Orange"), ERROR("Red"); 105 106 private String colorName; 107 108 Level(String colorName) { 109 this.colorName = colorName; 110 } 111 112 public String getColorName() { 113 return colorName; 114 } 115 116 public static List<String> names() { 117 return Lists.transform(Arrays.asList(values()), new Function<Level, String>() { 118 @Nonnull 119 @Override 120 public String apply(@Nonnull Level level) { 121 return level.name(); 122 } 123 }); 124 } 125 } 126 127 private Integer id; 128 private transient Formula formula; 129 private String key; 130 private String description; 131 private ValueType type; 132 private Integer direction; 133 private String domain; 134 private String name; 135 private Boolean qualitative = Boolean.FALSE; 136 private Boolean userManaged = Boolean.FALSE; 137 private Boolean enabled = Boolean.TRUE; 138 private Double worstValue; 139 private Double bestValue; 140 private Boolean optimizedBestValue; 141 private Boolean hidden = Boolean.FALSE; 142 private Boolean deleteHistoricalData; 143 private Integer decimalScale; 144 145 private Metric(Builder builder) { 146 this.key = builder.key; 147 this.name = builder.name; 148 this.description = builder.description; 149 this.type = builder.type; 150 this.direction = builder.direction; 151 this.domain = builder.domain; 152 this.qualitative = builder.qualitative; 153 this.enabled = Boolean.TRUE; 154 this.worstValue = builder.worstValue; 155 this.optimizedBestValue = builder.optimizedBestValue; 156 this.bestValue = builder.bestValue; 157 this.hidden = builder.hidden; 158 this.formula = builder.formula; 159 this.userManaged = builder.userManaged; 160 this.deleteHistoricalData = builder.deleteHistoricalData; 161 this.decimalScale = builder.decimalScale; 162 } 163 164 /** 165 * Creates an empty metric 166 * 167 * @deprecated in 1.12. Use the {@link Builder} factory. 168 */ 169 @Deprecated 170 public Metric() { 171 } 172 173 /** 174 * Creates a metric based on its key. Shortcut to Metric(key, ValueType.INT) 175 * 176 * @param key the metric key 177 * @deprecated since 2.7 use the {@link Builder} factory. 178 */ 179 @Deprecated 180 public Metric(String key) { 181 this(key, ValueType.INT); 182 } 183 184 /** 185 * Creates a metric based on a key and a type. Shortcut to 186 * Metric(key, key, key, type, -1, Boolean.FALSE, null, false) 187 * 188 * @param key the key 189 * @param type the type 190 * @deprecated since 2.7 use the {@link Builder} factory. 191 */ 192 @Deprecated 193 public Metric(String key, ValueType type) { 194 this(key, key, key, type, -1, Boolean.FALSE, null, false); 195 } 196 197 /** 198 * @deprecated since 2.7 use the {@link Builder} factory. 199 */ 200 @Deprecated 201 public Metric(String key, String name, String description, ValueType type, Integer direction, Boolean qualitative, String domain) { 202 this(key, name, description, type, direction, qualitative, domain, false); 203 } 204 205 /** 206 * Creates a fully qualified metric. 207 * 208 * @param key the metric key 209 * @param name the metric name 210 * @param description the metric description 211 * @param type the metric type 212 * @param direction the metric direction 213 * @param qualitative whether the metric is qualitative 214 * @param domain the metric domain 215 * @param userManaged whether the metric is user managed 216 */ 217 private Metric(String key, String name, String description, ValueType type, Integer direction, Boolean qualitative, @Nullable String domain, 218 boolean userManaged) { 219 this.key = key; 220 this.description = description; 221 this.type = type; 222 this.direction = direction; 223 this.domain = domain; 224 this.name = name; 225 this.qualitative = qualitative; 226 this.userManaged = userManaged; 227 if (ValueType.PERCENT == this.type) { 228 this.bestValue = (direction == DIRECTION_BETTER) ? 100.0 : 0.0; 229 this.worstValue = (direction == DIRECTION_BETTER) ? 0.0 : 100.0; 230 this.decimalScale = DEFAULT_DECIMAL_SCALE; 231 } else if (ValueType.FLOAT == this.type) { 232 this.decimalScale = DEFAULT_DECIMAL_SCALE; 233 } 234 } 235 236 /** 237 * For internal use only 238 */ 239 public Integer getId() { 240 return id; 241 } 242 243 /** 244 * For internal use only 245 */ 246 public Metric<G> setId(@Nullable Integer id) { 247 this.id = id; 248 return this; 249 } 250 251 /** 252 * @return the metric formula 253 * @deprecated since 5.2 there's no more decorator on batch side, please use {@link org.sonar.api.ce.measure.MeasureComputer} instead 254 */ 255 @Deprecated 256 public Formula getFormula() { 257 return formula; 258 } 259 260 /** 261 * Sets the metric formula 262 * 263 * @param formula the formula 264 * @return this 265 * @deprecated since 5.2 there's no more decorator on batch side, please use {@link org.sonar.api.ce.measure.MeasureComputer} instead 266 */ 267 @Deprecated 268 public Metric<G> setFormula(Formula formula) { 269 this.formula = formula; 270 return this; 271 } 272 273 /** 274 * @return wether the metric is qualitative 275 */ 276 public Boolean getQualitative() { 277 return qualitative; 278 } 279 280 /** 281 * Sets whether the metric is qualitative 282 * 283 * @param qualitative whether the metric is qualitative 284 * @return this 285 */ 286 public Metric<G> setQualitative(Boolean qualitative) { 287 this.qualitative = qualitative; 288 return this; 289 } 290 291 /** 292 * @return the metric key 293 */ 294 public String getKey() { 295 return key; 296 } 297 298 /** 299 * Sets the metric key 300 * 301 * @param key the key 302 * @return this 303 */ 304 public Metric<G> setKey(String key) { 305 this.key = key; 306 return this; 307 } 308 309 /** 310 * @return the metric type 311 */ 312 public ValueType getType() { 313 return type; 314 } 315 316 /** 317 * Sets the metric type 318 * 319 * @param type the type 320 * @return this 321 */ 322 public Metric<G> setType(ValueType type) { 323 this.type = type; 324 return this; 325 } 326 327 /** 328 * @return the metric description 329 */ 330 @CheckForNull 331 public String getDescription() { 332 return description; 333 } 334 335 /** 336 * Sets the metric description 337 * 338 * @param description the description 339 * @return this 340 */ 341 public Metric<G> setDescription(@Nullable String description) { 342 this.description = description; 343 return this; 344 } 345 346 /** 347 * @return whether the metric is a managed by the users ("manual metric") 348 */ 349 public Boolean getUserManaged() { 350 return userManaged; 351 } 352 353 /** 354 * Sets whether the metric is managed by users ("manual metric") 355 * 356 * @param userManaged whether the metric is user managed 357 * @return this 358 */ 359 public Metric<G> setUserManaged(Boolean userManaged) { 360 this.userManaged = userManaged; 361 return this; 362 } 363 364 /** 365 * @return whether the metric is enabled 366 */ 367 public Boolean getEnabled() { 368 return enabled; 369 } 370 371 /** 372 * Sets whether the metric is enabled 373 * 374 * @param enabled whether the metric is enabled 375 * @return this 376 */ 377 public Metric<G> setEnabled(Boolean enabled) { 378 this.enabled = enabled; 379 return this; 380 } 381 382 /** 383 * @return the metric direction 384 */ 385 public Integer getDirection() { 386 return direction; 387 } 388 389 /** 390 * Sets the metric direction. 391 * 392 * @param direction the direction 393 */ 394 public Metric<G> setDirection(Integer direction) { 395 this.direction = direction; 396 return this; 397 } 398 399 /** 400 * @return the domain of the metric 401 */ 402 public String getDomain() { 403 return domain; 404 } 405 406 /** 407 * Sets the domain for the metric (General, Complexity...) 408 * 409 * @param domain the domain 410 * @return this 411 */ 412 public Metric<G> setDomain(String domain) { 413 this.domain = domain; 414 return this; 415 } 416 417 /** 418 * @return the metric name 419 */ 420 public String getName() { 421 return name; 422 } 423 424 /** 425 * Sets the metric name 426 * 427 * @param name the name 428 * @return this 429 */ 430 public Metric<G> setName(String name) { 431 this.name = name; 432 return this; 433 } 434 435 public Double getWorstValue() { 436 return worstValue; 437 } 438 439 @CheckForNull 440 public Double getBestValue() { 441 return bestValue; 442 } 443 444 /** 445 * @return this 446 */ 447 public Metric<G> setWorstValue(@Nullable Double d) { 448 this.worstValue = d; 449 return this; 450 } 451 452 /** 453 * @param bestValue the best value. It can be null. 454 * @return this 455 */ 456 public Metric<G> setBestValue(@Nullable Double bestValue) { 457 this.bestValue = bestValue; 458 return this; 459 } 460 461 /** 462 * @return whether the metric is of a numeric type (int, percentage...) 463 */ 464 public boolean isNumericType() { 465 return ValueType.INT.equals(type) 466 || ValueType.FLOAT.equals(type) 467 || ValueType.PERCENT.equals(type) 468 || ValueType.BOOL.equals(type) 469 || ValueType.MILLISEC.equals(type) 470 || ValueType.RATING.equals(type) 471 || ValueType.WORK_DUR.equals(type); 472 } 473 474 /** 475 * @return whether the metric is of type data 476 */ 477 public boolean isDataType() { 478 return ValueType.DATA.equals(type) || ValueType.DISTRIB.equals(type); 479 } 480 481 /** 482 * @return whether the metric is of type percentage 483 */ 484 public boolean isPercentageType() { 485 return ValueType.PERCENT.equals(type); 486 } 487 488 public Metric<G> setOptimizedBestValue(@Nullable Boolean b) { 489 this.optimizedBestValue = b; 490 return this; 491 } 492 493 /** 494 * @return null for manual metrics 495 */ 496 @CheckForNull 497 public Boolean isOptimizedBestValue() { 498 return optimizedBestValue; 499 } 500 501 public Boolean isHidden() { 502 return hidden; 503 } 504 505 public Metric<G> setHidden(Boolean hidden) { 506 this.hidden = hidden; 507 return this; 508 } 509 510 public Boolean getDeleteHistoricalData() { 511 return deleteHistoricalData; 512 } 513 514 /** 515 * Return the number scale if metric type is {@link ValueType#FLOAT}, else {@code null} 516 * @since 5.3 517 */ 518 @CheckForNull 519 public Integer getDecimalScale() { 520 return decimalScale; 521 } 522 523 @Override 524 public int hashCode() { 525 return key.hashCode(); 526 } 527 528 @Override 529 public boolean equals(Object obj) { 530 if (!(obj instanceof Metric)) { 531 return false; 532 } 533 if (this == obj) { 534 return true; 535 } 536 Metric other = (Metric) obj; 537 return key.equals(other.getKey()); 538 } 539 540 @Override 541 public String toString() { 542 return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString(); 543 } 544 545 /** 546 * Merge with fields from other metric. All fields are copied, except the id. 547 * 548 * @return this 549 */ 550 public Metric<G> merge(final Metric with) { 551 this.description = with.description; 552 this.domain = with.domain; 553 this.enabled = with.enabled; 554 this.qualitative = with.qualitative; 555 this.worstValue = with.worstValue; 556 this.bestValue = with.bestValue; 557 this.optimizedBestValue = with.optimizedBestValue; 558 this.direction = with.direction; 559 this.key = with.key; 560 this.type = with.type; 561 this.name = with.name; 562 this.userManaged = with.userManaged; 563 this.hidden = with.hidden; 564 this.deleteHistoricalData = with.deleteHistoricalData; 565 return this; 566 } 567 568 /** 569 * Metric.Builder is used to create metric definitions. It must be preferred to creating new instances of the Metric class directly. 570 * 571 * @since 2.7 572 */ 573 public static final class Builder { 574 private String key; 575 private Metric.ValueType type; 576 private String name; 577 private String description; 578 private Integer direction = DIRECTION_NONE; 579 private Boolean qualitative = Boolean.FALSE; 580 private String domain = null; 581 private Formula formula; 582 private Double worstValue; 583 private Double bestValue; 584 private boolean optimizedBestValue = false; 585 private boolean hidden = false; 586 private boolean userManaged = false; 587 private boolean deleteHistoricalData = false; 588 private Integer decimalScale = null; 589 590 /** 591 * Creates a new {@link Builder} object. 592 * 593 * @param key the metric key, should be unique among all metrics 594 * @param name the metric name 595 * @param type the metric type 596 */ 597 public Builder(String key, String name, ValueType type) { 598 checkArgument(isNotBlank(key), "Metric key can not be blank"); 599 checkArgument(isNotBlank(name), "Name of metric %s must be set", key); 600 checkArgument(type != null, "Type of metric %s must be set", key); 601 this.key = key; 602 this.name = name; 603 this.type = type; 604 } 605 606 /** 607 * Sets the metric description. 608 * 609 * @param d the description 610 * @return the builder 611 */ 612 public Builder setDescription(String d) { 613 this.description = d; 614 return this; 615 } 616 617 /** 618 * Sets the metric direction (used for numeric values only), which is used in the Web UI to show if the trend of a metric is good or not. 619 * <ul> 620 * <li>Metric.DIRECTION_WORST: indicates that an increase of the metric value is not a good thing (example: the complexity of a function)</li> 621 * <li>Metric.DIRECTION_BETTER: indicates that an increase of the metric value is a good thing (example: the code coverage of a function)</li> 622 * <li>Metric.DIRECTION_NONE: indicates that the variation of the metric value is neither good nor bad (example: number of files).</li> 623 * </ul> 624 * Metric.DIRECTION_NONE is the default value. 625 * 626 * @see Metric#DIRECTION_WORST 627 * @see Metric#DIRECTION_BETTER 628 * @see Metric#DIRECTION_NONE 629 * 630 * @param d the direction 631 * @return the builder 632 */ 633 public Builder setDirection(Integer d) { 634 this.direction = d; 635 return this; 636 } 637 638 /** 639 * Sets whether the metric is qualitative or not. Default value is false. 640 * <br/> 641 * If set to true, then variations of this metric will be highlighted in the Web UI (for instance, trend icons will be red or green instead of default grey). 642 * 643 * @param b Boolean.TRUE if the metric is qualitative 644 * @return the builder 645 */ 646 public Builder setQualitative(Boolean b) { 647 this.qualitative = b; 648 return this; 649 } 650 651 /** 652 * Sets the domain for the metric (General, Complexity...). This is used to group metrics in the Web UI. 653 * <br/> 654 * By default, the metric belongs to no specific domain. 655 * 656 * @param d the domain 657 * @return the builder 658 */ 659 public Builder setDomain(String d) { 660 this.domain = d; 661 return this; 662 } 663 664 /** 665 * Specifies the formula used by Sonar to automatically aggregate measures stored on files up to the project level. 666 * <br/> 667 * <br/> 668 * By default, no formula is defined, which means that it's up to a sensor/decorator to compute measures on appropriate levels. 669 * <br/> 670 * When a formula is set, sensors/decorators just need to store measures at a specific level and let Sonar run the formula to store 671 * measures on the remaining levels. 672 * 673 * @param f the formula 674 * @return the builder 675 * 676 * @deprecated since 5.2, it's no more possible to define a formula on a metric, please use {@link org.sonar.api.ce.measure.MeasureComputer} instead 677 */ 678 @Deprecated 679 public Builder setFormula(Formula f) { 680 this.formula = f; 681 return this; 682 } 683 684 /** 685 * Sets the worst value that the metric can get (example: 0.0 for code coverage). No worst value is set by default. 686 * 687 * @param d the worst value 688 * @return the builder 689 */ 690 public Builder setWorstValue(Double d) { 691 this.worstValue = d; 692 return this; 693 } 694 695 /** 696 * Sets the best value that the metric can get (example: 100.0 for code coverage). No best value is set by default. 697 * <br/> 698 * Resources would be hidden on drilldown page, if the value of measure equals to best value. 699 * 700 * @param d the best value 701 * @return the builder 702 */ 703 public Builder setBestValue(Double d) { 704 this.bestValue = d; 705 return this; 706 } 707 708 /** 709 * Specifies whether file-level measures that equal to the defined best value are stored or not. Default is false. 710 * <br/> 711 * Example with the metric that stores the number of violation ({@link CoreMetrics#VIOLATIONS}): 712 * if a file has no violation, then the value '0' won't be stored in the database. 713 * 714 * @param b true if the measures must not be stored when they equal to the best value 715 * @return the builder 716 */ 717 public Builder setOptimizedBestValue(boolean b) { 718 this.optimizedBestValue = b; 719 return this; 720 } 721 722 /** 723 * Sets whether the metric should be hidden in Web UI (e.g. in Time Machine). Default is false. 724 * 725 * @param b true if the metric should be hidden. 726 * @return the builder 727 */ 728 public Builder setHidden(boolean b) { 729 this.hidden = b; 730 return this; 731 } 732 733 /** 734 * Specifies whether this metric can be edited online in the "Manual measures" page. Default is false. 735 * 736 * @since 2.10 737 * 738 * @param b true if the metric can be edited online. 739 * @return the builder 740 */ 741 public Builder setUserManaged(boolean b) { 742 this.userManaged = b; 743 return this; 744 } 745 746 /** 747 * Specifies whether measures from the past can be automatically deleted to minimize database volume. 748 * <br/> 749 * By default, historical data are kept. 750 * 751 * @since 2.14 752 * 753 * @param b true if measures from the past can be deleted automatically. 754 * @return the builder 755 */ 756 public Builder setDeleteHistoricalData(boolean b) { 757 this.deleteHistoricalData = b; 758 return this; 759 } 760 761 /** 762 * Scale to be used if the metric has decimal type ({@link ValueType#FLOAT} or {@link ValueType#PERCENT}). 763 * Default is 1. It is not set (({@code null}) on non-decimal metrics. 764 * @since 5.3 765 */ 766 public Builder setDecimalScale(int scale) { 767 checkArgument(scale >= 0, "Scale of decimal metric %s must be positive: %d", key, scale); 768 checkArgument(scale <= MAX_DECIMAL_SCALE, "Scale of decimal metric [%s] must be less than or equal %s: %s", key, MAX_DECIMAL_SCALE, scale); 769 this.decimalScale = scale; 770 return this; 771 } 772 773 /** 774 * Creates a new metric definition based on the properties set on this metric builder. 775 * 776 * @return a new {@link Metric} object 777 */ 778 public <G extends Serializable> Metric<G> create() { 779 if (ValueType.PERCENT == this.type) { 780 this.bestValue = (direction == DIRECTION_BETTER) ? 100.0 : 0.0; 781 this.worstValue = (direction == DIRECTION_BETTER) ? 0.0 : 100.0; 782 this.decimalScale = firstNonNull(decimalScale, DEFAULT_DECIMAL_SCALE); 783 784 } else if (ValueType.FLOAT == this.type) { 785 this.decimalScale = firstNonNull(decimalScale, DEFAULT_DECIMAL_SCALE); 786 } 787 return new Metric<>(this); 788 } 789 } 790 791 @Override 792 public String key() { 793 return getKey(); 794 } 795 796 @Override 797 public Class<G> valueType() { 798 return getType().valueType(); 799 } 800}