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