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    }