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