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