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