Test coverage report for RuleFilter.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.HashSet;
import com.sdmetrics.math.ExpressionNode;
import com.sdmetrics.math.ExpressionParser;
import com.sdmetrics.model.MetaModelElement;
/**
* Represents and evaluates a design rule filter expression.
* <p>
* A design rule can be assigned one or more "application areas", for example,
* rules aimed at the analysis phase, the design phase, rules for real time
* systems, etc. The rule filter selects rules based on their application areas.
*/
public class RuleFilter {
/** Operator tree of the filter expression. */
private ExpressionNode filterRoot;
/** Constructs a new empty rule filter that accepts all application areas. */
public RuleFilter() {
filterRoot = null;
}
/**
* Construct a new rule filter from a filter string.
*
* @param filter The rule filter string.
* @throws SDMetricsException The filter string could not be parsed or
* contains illegal operations.
*/
public RuleFilter(String filter) throws SDMetricsException {
if (filter != null && filter.length() > 0) {
ExpressionParser p = new ExpressionParser();
filterRoot = p.parseExpression(filter);
if (filterRoot == null) {
throw new SDMetricsException(null, null,
"Invalid design rule filter: " + filter + "\n"
+ p.getErrorInfo());
}
String problem = validateFilter(filterRoot);
if (problem.length() > 0) {
throw new SDMetricsException(null, null,
"Invalid design rule filter: " + filter + "\n"
+ problem);
}
}
}
/**
* Checks if the application area(s) of a design rule match this filter.
*
* @param rule The design rule to check.
* @return <code>true</code> if the design rule matches and should be
* checked according to this filter.
*/
public boolean match(Rule rule) {
return evalAppAreaMatch(rule.getApplicableAreas(), filterRoot);
}
/* Recursively evaluate the rule's operator tree to determine the match. */
private boolean evalAppAreaMatch(Collection<String> areas,
ExpressionNode filter) {
if (filter == null) {
return true; // no filter => accept everything
}
if (filter.isIdentifier()) {
if (areas == null) {
return true; // no phases specified => all phases are accepted!
}
return areas.contains(filter.getValue());
} else if (filter.isStringConstant()) {
if (areas == null) {
return false; // no phases specified => not accepted
}
return areas.contains(filter.getValue());
}
if ("!".equals(filter.getValue())) {
return !evalAppAreaMatch(areas, filter.getLeftNode());
}
if ("&".equals(filter.getValue())) {
return evalAppAreaMatch(areas, filter.getLeftNode())
&& evalAppAreaMatch(areas, filter.getRightNode());
}
return evalAppAreaMatch(areas, filter.getLeftNode())
|| evalAppAreaMatch(areas, filter.getRightNode());
}
/**
* Validates a rule filter expression.
*
* @param filter Operator tree of the rule filter string.
* @return Empty string if everything is OK, else a description of the
* problems found.
*/
private String validateFilter(ExpressionNode filter) {
if (filter == null) {
return "";
}
if (filter.isIdentifier() || filter.isStringConstant()) {
return "";
}
if (filter.isOperation()) {
if ("&".equals(filter.getValue()) || "|".equals(filter.getValue())) {
return validateFilter(filter.getLeftNode())
+ validateFilter(filter.getRightNode());
} else if ("!".equals(filter.getValue())) {
return validateFilter(filter.getLeftNode());
} else {
return "Operation '" + filter.getValue()
+ "' not allowed in filter expressions.";
}
}
return "Unexpected operand '" + filter.getValue() + "'.";
}
/**
* Checks the identifiers of the rule filter for plausibility.
* <p>
* An identifier is valid if it is explicitly defined as the application
* area of at least one rule. Otherwise, the identifier is suspect because
* it is not listed by any of the rules.
*
* @param metricStore Contains the definitions of the rules
* @return Empty string if all application areas of the filter string are
* valid, otherwise the name of a suspect application area in the
* filter string.
*/
public String checkIdentifiers(MetricStore metricStore) {
// collect explicitly defined application areas from all design rules
HashSet<String> explicitAppAreas = new HashSet<>();
for (MetaModelElement type : metricStore.getMetaModel()) {
for (Rule rule : metricStore.getRules(type)) {
if (rule.isEnabled() && rule.getApplicableAreas() != null) {
explicitAppAreas.addAll(rule.getApplicableAreas());
}
}
}
return checkIdentifiers(explicitAppAreas, filterRoot);
}
/* Recursively check the identifiers in the operator tree. */
private String checkIdentifiers(Collection<String> areas,
ExpressionNode node) {
if (node == null) {
return "";
}
if (node.isIdentifier() || node.isStringConstant()) {
String appArea = node.getValue();
return areas.contains(appArea) ? "" : appArea;
}
String result = checkIdentifiers(areas, node.getLeftNode());
if (result.length() == 0) {
result = checkIdentifiers(areas, node.getRightNode());
}
return result;
}
}