Test coverage report for RuleEngine.java - www.sdmetrics.com
/*
* SDMetrics Open Core for UML design measurement
* Copyright (c) Juergen Wuest
* To contact the author, see <http://www.sdmetrics.com/Contact.html>.
*
* This file is part of the SDMetrics Open Core.
*
* SDMetrics Open Core is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
* SDMetrics Open Core is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with SDMetrics Open Core. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.sdmetrics.metrics;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import com.sdmetrics.math.ExpressionNode;
import com.sdmetrics.math.ExpressionParser;
import com.sdmetrics.model.MetaModelElement;
import com.sdmetrics.model.ModelElement;
/**
* Checks the design rules for a model and reports violations.
*/
public class RuleEngine {
/**
* Prefix in the rule exemption information to indicate exemption of a
* certain design rule.
*/
private static final String EXEMPTIONPREFIX = "violates_";
/** Metrics engine to use for rule checking. */
private final MetricsEngine engine;
/** Cache to keep the rule calculation procedures for reuse. */
private final RuleProcedureCache ruleProcedures;
/** Element type that holds the rule exemption information. */
private final MetaModelElement exemptionType;
/**
* Expression that produces the rule exemption information for the exemption
* type element.
*/
private final ExpressionNode exemptionExpression;
/**
* Cache for rule procedures to store values that are expensive to
* calculate.
*/
private final HashMap<Object, Object> valueCache = new HashMap<>();
/**
* Creates a new rule engine.
*
* @param me Metrics engine to use for rule checking.
*/
public RuleEngine(MetricsEngine me) {
this.engine = me;
MetricStore store = me.getMetricStore();
ruleProcedures = store.getRuleProcedures();
exemptionType = store.getRuleExemptionType();
exemptionExpression = new ExpressionNode(store.getRuleExemptionTag());
}
/**
* Returns the metrics engine that this rule engine uses.
*
* @return Metrics Engine of this rule engine.
*/
public MetricsEngine getMetricsEngine() {
return engine;
}
/**
* Gets the names of the rules that a model element is allowed to violate.
* Searches through the tagged values or comments of the model element,
* looking for occurrences of the exemption prefix "_violates". Extracts the
* identifiers following each such occurrence. These define the names of the
* rules the element is allowed to violate. Returns the set of these names.
*
* @param element Element to retrieve rule exemptions for.
* @return The set of the names of the rules the model element is allowed to
* violate.
* @throws SDMetricsException The tagged values or comments could not be
* accessed.
*/
public Collection<String> collectExemptedRules(ModelElement element)
throws SDMetricsException {
Collection<ModelElement> ownedElements = element.getOwnedElements();
if (exemptionType == null || ownedElements == null) {
return Collections.emptySet();
}
HashSet<String> exemptRuleNames = new HashSet<>(2);
// find all tagged values or comments
for (ModelElement child : ownedElements) {
if (child.getType() != exemptionType) {
continue;
}
// Get the tag value or comment body string
String tag = String.valueOf(engine.evalExpression(child,
exemptionExpression, null));
// find all occurrences of "violates_" in the tag value
int index = tag.indexOf(EXEMPTIONPREFIX);
while (index >= 0) {
// extract the rule name following the prefix
index += EXEMPTIONPREFIX.length();
int ruleNameEndIndex = index;
while (ruleNameEndIndex < tag.length()
&& ExpressionParser.isIdentifierCharacter(tag
.charAt(ruleNameEndIndex))) {
ruleNameEndIndex++;
}
// add rule name to the set of exempted rules
exemptRuleNames.add(tag.substring(index, ruleNameEndIndex));
index = tag.indexOf(EXEMPTIONPREFIX, ruleNameEndIndex);
}
}
return exemptRuleNames;
}
/**
* Checks a design rule for a model element.
*
* @param element The model element to check.
* @param rule The rule to check.
* @return The list of detected rule violations.
* @throws SDMetricsException An error occurred checking the design rule.
*/
public List<RuleViolation> checkRule(ModelElement element, Rule rule)
throws SDMetricsException {
try {
// Obtain the procedure to check the rule
String procedureName = rule.getProcedureName();
RuleProcedure procedure = ruleProcedures
.getProcedure(procedureName);
// Perform the check, return the procedure for reuse
procedure.setRuleEngine(this);
procedure.checkRule(element, rule);
List<RuleViolation> result = procedure.getViolations();
ruleProcedures.returnProcedure(procedure);
if (result == null) {
return Collections.emptyList();
}
return result;
} catch (SDMetricsException ex) {
ex.fillInPerpetrators(element, rule);
throw ex;
} catch (RuntimeException ex) {
// wrap exceptions in an SDMetricsException so we know
// what rule/element is to blame
throw new SDMetricsException(element, rule, ex);
}
}
/**
* Gets the value cache for rule procedures to store values that are
* expensive to calculate.
* <p>
* If there is expensive data to calculate that can be reused across model
* elements, rule procedures may store them here.
*
* @return value cache for rule procedures
*/
Map<Object, Object> getValuesCache() {
return valueCache;
}
/**
* Clears the value cache for rule procedures.
* <p>
* The cache can be cleared any time. When you perform a comprehensive rule
* check (checking all rules for all model elements), a good strategy is to
* check all elements of one type, and clear the cache before proceeding
* with the next type.
*/
public void clearValuesCache() {
valueCache.clear();
}
}