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.CheckForNull;
030    import javax.annotation.Nullable;
031    import javax.persistence.Column;
032    import javax.persistence.Entity;
033    import javax.persistence.EnumType;
034    import javax.persistence.Enumerated;
035    import javax.persistence.GeneratedValue;
036    import javax.persistence.Id;
037    import javax.persistence.Table;
038    import javax.persistence.Transient;
039    
040    import java.io.Serializable;
041    
042    /**
043     * This class represents the definition of a metric in Sonar.
044     *
045     * @since 1.10
046     */
047    @Table(name = "metrics")
048    @Entity(name = "Metric")
049    @InstantiationStrategy(InstantiationStrategy.PER_BATCH)
050    public class Metric<G extends Serializable> implements ServerExtension, BatchExtension, Serializable, org.sonar.api.batch.measure.Metric<G> {
051    
052      /**
053       * A metric bigger value means a degradation
054       */
055      public static final int DIRECTION_WORST = -1;
056      /**
057       * A metric bigger value means an improvement
058       */
059      public static final int DIRECTION_BETTER = 1;
060      /**
061       * The metric direction has no meaning
062       */
063      public static final int DIRECTION_NONE = 0;
064    
065      public enum ValueType {
066        INT(Integer.class),
067        FLOAT(Double.class),
068        PERCENT(Double.class),
069        BOOL(Boolean.class),
070        STRING(String.class),
071        MILLISEC(Integer.class),
072        DATA(String.class),
073        LEVEL(Metric.Level.class),
074        DISTRIB(String.class),
075        RATING(String.class),
076        WORK_DUR(Long.class);
077    
078        private final Class valueClass;
079    
080        private ValueType(Class valueClass) {
081          this.valueClass = valueClass;
082        }
083    
084        private Class valueType() {
085          return valueClass;
086        }
087    
088      }
089    
090      public enum Level {
091        OK("Green"), WARN("Orange"), ERROR("Red");
092    
093        private String colorName;
094    
095        Level(String colorName) {
096          this.colorName = colorName;
097        }
098    
099        public String getColorName() {
100          return colorName;
101        }
102      }
103    
104      public enum Origin {
105        JAV, GUI, WS
106      }
107    
108      @Id
109      @Column(name = "id")
110      @GeneratedValue
111      private Integer id;
112    
113      @Transient
114      private transient Formula formula;
115    
116      @Column(name = "name", updatable = false, nullable = false, length = 64)
117      private String key;
118    
119      @Column(name = "description", updatable = true, nullable = true, length = 255)
120      private String description;
121    
122      @Column(name = "val_type", updatable = true, nullable = true)
123      @Enumerated(EnumType.STRING)
124      private ValueType type;
125    
126      @Column(name = "direction", updatable = true, nullable = true)
127      private Integer direction;
128    
129      @Column(name = "domain", updatable = true, nullable = true, length = 60)
130      private String domain;
131    
132      @Column(name = "short_name", updatable = true, nullable = true, length = 64)
133      private String name;
134    
135      @Column(name = "qualitative", updatable = true, nullable = true)
136      private Boolean qualitative = Boolean.FALSE;
137    
138      @Column(name = "user_managed", updatable = true, nullable = true)
139      private Boolean userManaged = Boolean.FALSE;
140    
141      @Column(name = "enabled", updatable = true, nullable = true)
142      private Boolean enabled = Boolean.TRUE;
143    
144      @Column(name = "origin", updatable = true, nullable = true, length = 3)
145      @Enumerated(EnumType.STRING)
146      private Origin origin = Origin.JAV;
147    
148      @Column(name = "worst_value", updatable = true, nullable = true, precision = 30, scale = 20)
149      private Double worstValue;
150    
151      @Column(name = "best_value", updatable = true, nullable = true, precision = 30, scale = 20)
152      private Double bestValue;
153    
154      @Column(name = "optimized_best_value", updatable = true, nullable = true)
155      private Boolean optimizedBestValue;
156    
157      @Column(name = "hidden", updatable = true, nullable = true)
158      private Boolean hidden = Boolean.FALSE;
159    
160      @Column(name = "delete_historical_data", updatable = true, nullable = true)
161      private Boolean deleteHistoricalData;
162    
163      private Metric(Builder builder) {
164        this.key = builder.key;
165        this.name = builder.name;
166        this.description = builder.description;
167        this.type = builder.type;
168        this.direction = builder.direction;
169        this.domain = builder.domain;
170        this.qualitative = builder.qualitative;
171        this.enabled = Boolean.TRUE;
172        this.worstValue = builder.worstValue;
173        this.optimizedBestValue = builder.optimizedBestValue;
174        this.bestValue = builder.bestValue;
175        this.hidden = builder.hidden;
176        this.formula = builder.formula;
177        this.userManaged = builder.userManaged;
178        this.deleteHistoricalData = builder.deleteHistoricalData;
179      }
180    
181      /**
182       * Creates an empty metric. Required for Hibernate.
183       *
184       * @deprecated in 1.12. Use the {@link Builder} factory.
185       */
186      @Deprecated
187      public Metric() {
188      }
189    
190      /**
191       * Creates a metric based on its key. Shortcut to Metric(key, ValueType.INT)
192       *
193       * @param key the metric key
194       * @deprecated since 2.7 use the {@link Builder} factory.
195       */
196      @Deprecated
197      public Metric(String key) {
198        this(key, ValueType.INT);
199      }
200    
201      /**
202       * Creates a metric based on a key and a type. Shortcut to
203       * Metric(key, key, key, type, -1, Boolean.FALSE, null, false)
204       *
205       * @param key  the key
206       * @param type the type
207       * @deprecated since 2.7 use the {@link Builder} factory.
208       */
209      @Deprecated
210      public Metric(String key, ValueType type) {
211        this(key, key, key, type, -1, Boolean.FALSE, null, false);
212      }
213    
214      /**
215       * @deprecated since 2.7 use the {@link Builder} factory.
216       */
217      @Deprecated
218      public Metric(String key, String name, String description, ValueType type, Integer direction, Boolean qualitative, String domain) {
219        this(key, name, description, type, direction, qualitative, domain, false);
220      }
221    
222      /**
223       * Creates a fully qualified metric. This defaults some values:
224       * <ul>
225       * <li>origin : Origin.JAV</li>
226       * </ul>
227       *
228       * @param key         the metric key
229       * @param name        the metric name
230       * @param description the metric description
231       * @param type        the metric type
232       * @param direction   the metric direction
233       * @param qualitative whether the metric is qualitative
234       * @param domain      the metric domain
235       * @param userManaged whether the metric is user managed
236       */
237      private Metric(String key, String name, String description, ValueType type, Integer direction, Boolean qualitative, @Nullable String domain,
238        boolean userManaged) {
239        this.key = key;
240        this.description = description;
241        this.type = type;
242        this.direction = direction;
243        this.domain = domain;
244        this.name = name;
245        this.qualitative = qualitative;
246        this.userManaged = userManaged;
247        this.origin = Origin.JAV;
248        if (ValueType.PERCENT.equals(this.type)) {
249          this.bestValue = (direction == DIRECTION_BETTER ? 100.0 : 0.0);
250          this.worstValue = (direction == DIRECTION_BETTER ? 0.0 : 100.0);
251        }
252      }
253    
254      /**
255       * For internal use only
256       */
257      public Integer getId() {
258        return id;
259      }
260    
261      /**
262       * For internal use only
263       */
264      public Metric setId(@Nullable Integer id) {
265        this.id = id;
266        return this;
267      }
268    
269      /**
270       * @return the metric formula
271       */
272      public Formula getFormula() {
273        return formula;
274      }
275    
276      /**
277       * Sets the metric formula
278       *
279       * @param formula the formula
280       * @return this
281       */
282      public Metric setFormula(Formula formula) {
283        this.formula = formula;
284        return this;
285      }
286    
287      /**
288       * @return wether the metric is qualitative
289       */
290      public Boolean getQualitative() {
291        return qualitative;
292      }
293    
294      /**
295       * Sets whether the metric is qualitative
296       *
297       * @param qualitative whether the metric is qualitative
298       * @return this
299       */
300      public Metric setQualitative(Boolean qualitative) {
301        this.qualitative = qualitative;
302        return this;
303      }
304    
305      /**
306       * @return the metric key
307       */
308      public String getKey() {
309        return key;
310      }
311    
312      /**
313       * Sets the metric key
314       *
315       * @param key the key
316       * @return this
317       */
318      public Metric setKey(String key) {
319        this.key = key;
320        return this;
321      }
322    
323      /**
324       * @return the metric type
325       */
326      public ValueType getType() {
327        return type;
328      }
329    
330      /**
331       * Sets the metric type
332       *
333       * @param type the type
334       * @return this
335       */
336      public Metric setType(ValueType type) {
337        this.type = type;
338        return this;
339      }
340    
341      /**
342       * @return the metric description
343       */
344      @CheckForNull
345      public String getDescription() {
346        return description;
347      }
348    
349      /**
350       * Sets the metric description
351       *
352       * @param description the description
353       * @return this
354       */
355      public Metric setDescription(@Nullable String description) {
356        this.description = description;
357        return this;
358      }
359    
360      /**
361       * @return whether the metric is a managed by the users ("manual metric")
362       */
363      public Boolean getUserManaged() {
364        return userManaged;
365      }
366    
367      /**
368       * Sets whether the metric is managed by users ("manual metric")
369       *
370       * @param userManaged whether the metric is user managed
371       * @return this
372       */
373      public Metric setUserManaged(Boolean userManaged) {
374        this.userManaged = userManaged;
375        return this;
376      }
377    
378      /**
379       * @return whether the metric is enabled
380       */
381      public Boolean getEnabled() {
382        return enabled;
383      }
384    
385      /**
386       * Sets whether the metric is enabled
387       *
388       * @param enabled whether the metric is enabled
389       * @return this
390       */
391      public Metric setEnabled(Boolean enabled) {
392        this.enabled = enabled;
393        return this;
394      }
395    
396      /**
397       * @return the metric direction
398       */
399      public Integer getDirection() {
400        return direction;
401      }
402    
403      /**
404       * Sets the metric direction.
405       *
406       * @param direction the direction
407       */
408      public Metric setDirection(Integer direction) {
409        this.direction = direction;
410        return this;
411      }
412    
413      /**
414       * @return the domain of the metric
415       */
416      public String getDomain() {
417        return domain;
418      }
419    
420      /**
421       * Sets the domain for the metric (General, Complexity...)
422       *
423       * @param domain the domain
424       * @return this
425       */
426      public Metric setDomain(String domain) {
427        this.domain = domain;
428        return this;
429      }
430    
431      /**
432       * @return the metric name
433       */
434      public String getName() {
435        return name;
436      }
437    
438      /**
439       * Sets the metric name
440       *
441       * @param name the name
442       * @return this
443       */
444      public Metric setName(String name) {
445        this.name = name;
446        return this;
447      }
448    
449      /**
450       * @return the origin of the metric - Internal use only
451       */
452      public Origin getOrigin() {
453        return origin;
454      }
455    
456      /**
457       * Set the origin of the metric - Internal use only
458       *
459       * @param origin the origin
460       * @return this
461       */
462      public Metric setOrigin(Origin origin) {
463        this.origin = origin;
464        return this;
465      }
466    
467      public Double getWorstValue() {
468        return worstValue;
469      }
470    
471      @CheckForNull
472      public Double getBestValue() {
473        return bestValue;
474      }
475    
476      /**
477       * @return this
478       */
479      public Metric setWorstValue(@Nullable Double d) {
480        this.worstValue = d;
481        return this;
482      }
483    
484      /**
485       * @param bestValue the best value. It can be null.
486       * @return this
487       */
488      public Metric setBestValue(@Nullable Double bestValue) {
489        this.bestValue = bestValue;
490        return this;
491      }
492    
493      /**
494       * @return whether the metric is of a numeric type (int, percentage...)
495       */
496      public boolean isNumericType() {
497        return ValueType.INT.equals(type)
498          || ValueType.FLOAT.equals(type)
499          || ValueType.PERCENT.equals(type)
500          || ValueType.BOOL.equals(type)
501          || ValueType.MILLISEC.equals(type)
502          || ValueType.RATING.equals(type)
503          || ValueType.WORK_DUR.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(@Nullable Boolean b) {
521        this.optimizedBestValue = b;
522        return this;
523      }
524    
525      /**
526       * @return null for manual metrics
527       */
528      @CheckForNull
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 <G extends Serializable> Metric<G> 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<G>(this);
803        }
804      }
805    
806      @Override
807      public String key() {
808        return getKey();
809      }
810    
811      @Override
812      public Class<G> valueType() {
813        return getType().valueType();
814      }
815    }