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