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     */
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    
028    import 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")
037    public 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    }