1 /*******************************************************************************
2  * Copyright (c) 2009, 2015 Mountainminds GmbH & Co. KG and Contributors
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *    Marc R. Hoffmann - initial API and implementation
10  *
11  *******************************************************************************/
12 package org.jacoco.report.check;
13 
14 import java.math.BigDecimal;
15 import java.math.RoundingMode;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.Map;
19 
20 import org.jacoco.core.analysis.ICounter.CounterValue;
21 import org.jacoco.core.analysis.ICoverageNode;
22 import org.jacoco.core.analysis.ICoverageNode.CounterEntity;
23 
24 /**
25  * Descriptor for a limit which is given by a {@link Rule}.
26  */
27 public class Limit {
28 
29 	private static final Map<CounterValue, String> VALUE_NAMES;
30 	private static final Map<CounterEntity, String> ENTITY_NAMES;
31 
32 	static {
33 		final Map<CounterValue, String> values = new HashMap<CounterValue, String>();
values.put(CounterValue.TOTALCOUNT, "total count")34 		values.put(CounterValue.TOTALCOUNT, "total count");
values.put(CounterValue.MISSEDCOUNT, "missed count")35 		values.put(CounterValue.MISSEDCOUNT, "missed count");
values.put(CounterValue.COVEREDCOUNT, "covered count")36 		values.put(CounterValue.COVEREDCOUNT, "covered count");
values.put(CounterValue.MISSEDRATIO, "missed ratio")37 		values.put(CounterValue.MISSEDRATIO, "missed ratio");
values.put(CounterValue.COVEREDRATIO, "covered ratio")38 		values.put(CounterValue.COVEREDRATIO, "covered ratio");
39 		VALUE_NAMES = Collections.unmodifiableMap(values);
40 
41 		final Map<CounterEntity, String> entities = new HashMap<CounterEntity, String>();
entities.put(CounterEntity.INSTRUCTION, "instructions")42 		entities.put(CounterEntity.INSTRUCTION, "instructions");
entities.put(CounterEntity.BRANCH, "branches")43 		entities.put(CounterEntity.BRANCH, "branches");
entities.put(CounterEntity.COMPLEXITY, "complexity")44 		entities.put(CounterEntity.COMPLEXITY, "complexity");
entities.put(CounterEntity.LINE, "lines")45 		entities.put(CounterEntity.LINE, "lines");
entities.put(CounterEntity.METHOD, "methods")46 		entities.put(CounterEntity.METHOD, "methods");
entities.put(CounterEntity.CLASS, "classes")47 		entities.put(CounterEntity.CLASS, "classes");
48 		ENTITY_NAMES = Collections.unmodifiableMap(entities);
49 	}
50 
51 	private CounterEntity entity;
52 
53 	private CounterValue value;
54 
55 	private BigDecimal minimum;
56 
57 	private BigDecimal maximum;
58 
59 	/**
60 	 * Creates a new instance with the following defaults:
61 	 * <ul>
62 	 * <li>counter entity: {@link CounterEntity#INSTRUCTION}
63 	 * <li>counter value: {@link CounterValue#COVEREDRATIO}
64 	 * <li>minimum: no limit
65 	 * <li>maximum: no limit
66 	 * </ul>
67 	 */
Limit()68 	public Limit() {
69 		this.entity = CounterEntity.INSTRUCTION;
70 		this.value = CounterValue.COVEREDRATIO;
71 	}
72 
73 	/**
74 	 * @return the configured counter entity to check
75 	 */
getEntity()76 	public CounterEntity getEntity() {
77 		return entity;
78 	}
79 
80 	/**
81 	 * Sets the counter entity to check.
82 	 *
83 	 * @param entity
84 	 *            counter entity to check
85 	 * TODO: use CounterEntity directly once Maven 3 is required.
86 	 */
setCounter(final String entity)87 	public void setCounter(final String entity) {
88 		this.entity = CounterEntity.valueOf(entity);
89 	}
90 
91 	/**
92 	 * @return the configured value to check
93 	 */
getValue()94 	public CounterValue getValue() {
95 		return value;
96 	}
97 
98 	/**
99 	 * Sets the value to check.
100 	 *
101 	 * @param value
102 	 *            value to check
103 	 * TODO: use CounterValue directly once Maven 3 is required.
104 	 */
setValue(final String value)105 	public void setValue(final String value) {
106 		this.value = CounterValue.valueOf(value);
107 	}
108 
109 	/**
110 	 * @return configured minimum value, or <code>null</code> if no minimum is
111 	 *         given
112 	 */
getMinimum()113 	public String getMinimum() {
114 		return minimum == null ? null : minimum.toPlainString();
115 	}
116 
117 	/**
118 	 * Sets allowed minimum value as decimal string representation. The given
119 	 * precision is also considered in error messages. Coverage ratios are given
120 	 * in the range from 0.0 to 1.0.
121 	 *
122 	 * @param minimum
123 	 *            allowed minimum or <code>null</code>, if no minimum should be
124 	 *            checked
125 	 */
setMinimum(final String minimum)126 	public void setMinimum(final String minimum) {
127 		this.minimum = minimum == null ? null : new BigDecimal(minimum);
128 	}
129 
130 	/**
131 	 * @return configured maximum value, or <code>null</code> if no maximum is
132 	 *         given
133 	 */
getMaximum()134 	public String getMaximum() {
135 		return maximum == null ? null : maximum.toPlainString();
136 	}
137 
138 	/**
139 	 * Sets allowed maximum value as decimal string representation. The given
140 	 * precision is also considered in error messages. Coverage ratios are given
141 	 * in the range from 0.0 to 1.0.
142 	 *
143 	 * @param maximum
144 	 *            allowed maximum or <code>null</code>, if no maximum should be
145 	 *            checked
146 	 */
setMaximum(final String maximum)147 	public void setMaximum(final String maximum) {
148 		this.maximum = maximum == null ? null : new BigDecimal(maximum);
149 	}
150 
check(final ICoverageNode node)151 	String check(final ICoverageNode node) {
152 		final double d = node.getCounter(entity).getValue(value);
153 		if (Double.isNaN(d)) {
154 			return null;
155 		}
156 		final BigDecimal bd = BigDecimal.valueOf(d);
157 		if (minimum != null && minimum.compareTo(bd) > 0) {
158 			return message("minimum", bd, minimum, RoundingMode.FLOOR);
159 		}
160 		if (maximum != null && maximum.compareTo(bd) < 0) {
161 			return message("maximum", bd, maximum, RoundingMode.CEILING);
162 		}
163 		return null;
164 	}
165 
message(final String minmax, final BigDecimal v, final BigDecimal ref, final RoundingMode mode)166 	private String message(final String minmax, final BigDecimal v,
167 			final BigDecimal ref, final RoundingMode mode) {
168 		final BigDecimal rounded = v.setScale(ref.scale(), mode);
169 		return String.format("%s %s is %s, but expected %s is %s",
170 				ENTITY_NAMES.get(entity), VALUE_NAMES.get(value),
171 				rounded.toPlainString(), minmax, ref.toPlainString());
172 	}
173 
174 }
175