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