/*******************************************************************************
* Copyright (C) Yutaka Matsuno 2010-2012 All rights reserved.
*******************************************************************************/
package net.dependableos.dcase.diagram.common.validator;
import static net.dependableos.dcase.diagram.common.constant.SystemDefinitionConst.COLLECTION_INITIAL_CAPACITY;
import static net.dependableos.dcase.diagram.common.util.ModelUtil.STRING_EMPTY;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.dependableos.dcase.BasicLink;
import net.dependableos.dcase.BasicNode;
import net.dependableos.dcase.diagram.common.exception.DcaseValidatorException;
import net.dependableos.dcase.diagram.common.model.AttributeType;
import net.dependableos.dcase.diagram.common.model.LinkInfo;
import net.dependableos.dcase.diagram.common.model.NodeInfo;
import net.dependableos.dcase.diagram.common.model.NodeType;
import net.dependableos.dcase.diagram.common.util.LinkManager;
import net.dependableos.dcase.diagram.common.util.MessageTypeImpl;
import net.dependableos.dcase.diagram.common.util.Messages;
import net.dependableos.dcase.diagram.common.util.ModelUtil;
import org.eclipse.osgi.util.NLS;
/**
* A validator for the D-Case.
*/
public class DcaseValidator {
/**
* the regular expression pattern for URL.
*/
private static final Pattern URL_PATTERN = Pattern.compile(
"((http://|https://){1}[\\w\\.\\-/:\\#\\?\\=\\&\\;\\%\\~\\+]+)|" //$NON-NLS-1$
+ "(file://{1}[\\w\\.\\-/:\\#\\?\\=\\&\\;\\%\\~\\+\\" //$NON-NLS-1$
+ System.getProperties().getProperty("file.separator") + "]+)", //$NON-NLS-1$ //$NON-NLS-2$
Pattern.CASE_INSENSITIVE);
/**
* the link manager.
*/
private LinkManager linkManager = null;
/**
* Allocates a DcaseValidator object and initializes to validate
* a D-Case that represents the specified link manager.
*
* @param linkManager LinkManager
*/
public DcaseValidator(LinkManager linkManager) {
this.linkManager = linkManager;
}
/**
* Validates the attributes of a node.
* It throws a DcaseValidatorException if invalid attribute is detected.
*
* @param node a node.
*/
public void validateNodeAttribute(BasicNode node) {
NodeInfo nodeInfo = ModelUtil.createNodeInfo(node);
validateAttribute(nodeInfo.getAttributeMap(), Messages.DcaseValidator_0);
}
/**
* Validates the attributes of a link.
* It throws a DcaseValidatorException if invalid attribute is detected.
*
* @param link a link.
*/
public void validateLinkAttribute(BasicLink link) {
LinkInfo linkInfo = ModelUtil.createLinkInfo(link);
validateAttribute(linkInfo.getAttributeMap(), Messages.DcaseValidator_1);
}
/**
* Validates the attributes of a node or a link.
* It throws a DcaseValidatorException if invalid attribute is detected.
*
* @param attributeMap the attributes
* @param objectType the string that represents the object type.
*/
private void validateAttribute(Map<AttributeType, Object> attributeMap,
String objectType) {
String objectName = (String) attributeMap.get(AttributeType.NAME);
for (Map.Entry<AttributeType, Object> attribute : attributeMap
.entrySet()) {
AttributeType attributeType = attribute.getKey();
String attributeName = attributeType.toString().toLowerCase();
switch (attributeType.getRuleType()) {
case STRING_NOT_NULL:
String strNotNullValue = (String) attribute.getValue();
if (strNotNullValue == null) {
throw new DcaseValidatorException(NLS.bind(
Messages.DcaseValidator_4,
new Object[] { attributeName, objectType,
objectName }),
MessageTypeImpl.VALIDATION_ERROR);
}
break;
case STRING_NOT_EMPTY:
String strNotEmptyValue = (String) attribute.getValue();
if (strNotEmptyValue == null
|| strNotEmptyValue.equals(STRING_EMPTY)) {
throw new DcaseValidatorException(NLS.bind(
Messages.DcaseValidator_5,
new Object[] { attributeName, objectType,
objectName }),
MessageTypeImpl.VALIDATION_ERROR);
}
break;
case STRING_URL:
String strUrlValue = (String) attribute.getValue();
if (strUrlValue != null
&& !strUrlValue.equals(STRING_EMPTY)) {
Matcher matcher = URL_PATTERN.matcher(strUrlValue);
if (!matcher.matches()) {
throw new DcaseValidatorException(NLS.bind(
Messages.DcaseValidator_6,
new Object[] {
attributeName,
objectType,
objectName }),
MessageTypeImpl.VALIDATION_ERROR);
}
}
break;
case INTEGER_ONE_OR_MORE:
Integer intOneOrMoreValue = (Integer) attribute.getValue();
if (intOneOrMoreValue.intValue() < 1) {
throw new DcaseValidatorException(NLS.bind(
Messages.DcaseValidator_7,
new Object[] { attributeName,
objectType, objectName }),
MessageTypeImpl.VALIDATION_ERROR);
}
break;
case NO_RULE:
default:
break;
}
}
}
/**
* Validates whether the tree that start from specified node has any loop.
* It throws a DcaseValidatorException if the tree has any loop.
*
* @param topNode the top node.
* @return the list of the node IDs those has been tested.
*/
public Set<String> validateCyclicStatePart(NodeInfo topNode) {
if (topNode == null) {
return null;
}
// initializes the list of the node IDs those has been tested.
Set<String> checkedIdSet = new HashSet<String>(
COLLECTION_INITIAL_CAPACITY);
// initializes the list of the node IDs those are current subject of validation.
LinkedList<String> searchingIdList = new LinkedList<String>();
// initializes the list of the node IDs those are unchecked.
Map<String, List<String>> uncheckedLinkInfoMap = new HashMap<String, List<String>>(
COLLECTION_INITIAL_CAPACITY);
// initializes the start point to trace.
String topNodeId = (String) topNode.getAttribute(AttributeType.ID);
List<String> topNodetargetIdList = linkManager.getTarget(topNodeId);
if (topNodetargetIdList != null && !topNodetargetIdList.isEmpty()) {
uncheckedLinkInfoMap.put(topNodeId, ModelUtil
.duplicateList(topNodetargetIdList));
searchingIdList.addLast(topNodeId);
} else {
checkedIdSet.add(topNodeId);
}
// validates.
while (!searchingIdList.isEmpty()) {
String lastId = searchingIdList.getLast();
List<String> nextTargetIdList = uncheckedLinkInfoMap.get(lastId);
boolean existsNewTarget = false;
if (nextTargetIdList != null) {
for (String targetId : nextTargetIdList) {
// detects cyclic.
if (searchingIdList.contains(targetId)) {
searchingIdList.add(targetId);
// ignore the ID that is not a part of a cycle.
while (!searchingIdList.isEmpty()) {
String notLoopId = searchingIdList.getFirst();
if (notLoopId.equals(targetId)) {
break;
} else {
searchingIdList.removeFirst();
}
}
String cyclicInfo = getNodeListString(searchingIdList);
throw new DcaseValidatorException(NLS.bind(
Messages.DcaseValidator_8,
cyclicInfo), MessageTypeImpl.VALIDATION_ERROR);
}
// traces the node.
List<String> newTargetIdList = linkManager
.getTarget(targetId);
if (newTargetIdList != null && !newTargetIdList.isEmpty()) {
uncheckedLinkInfoMap.put(targetId, ModelUtil
.duplicateList(newTargetIdList));
if (!existsNewTarget) {
searchingIdList.addLast(targetId);
existsNewTarget = true;
}
} else {
checkedIdSet.add(targetId);
}
}
}
// traces the next tree.
if (!existsNewTarget) {
String checkedId = searchingIdList.removeLast();
checkedIdSet.add(checkedId);
if (!searchingIdList.isEmpty()) {
String recheckId = searchingIdList.getLast();
List<String> oldIdList = uncheckedLinkInfoMap
.get(recheckId);
oldIdList.remove(checkedId);
if (oldIdList.isEmpty()) {
uncheckedLinkInfoMap.remove(recheckId);
} else {
uncheckedLinkInfoMap.put(recheckId, oldIdList);
}
}
}
}
return checkedIdSet;
}
/**
* Returns the string that is the list of the node IDs joined with comma.
*
* @param idList the list of the node IDs
* @return the string that is the list of the node IDs joined with comma.
*/
private String getNodeListString(List<String> idList) {
StringBuilder cyclicNodeInfo = new StringBuilder();
int count = 0;
for (String id : idList) {
NodeInfo nodeInfo = linkManager.getNodeInfo(id);
String name = (String) nodeInfo.getAttribute(AttributeType.NAME);
if (count != 0) {
cyclicNodeInfo.append(","); //$NON-NLS-1$
}
cyclicNodeInfo.append(name);
count++;
}
return cyclicNodeInfo.toString();
}
/**
* Validates whether the argument node has any loop.
* It throws a DcaseValidatorException if the argument has any loop.
*/
public void validateCyclicStateAll() {
Set<String> allIdList = linkManager.getAllNodes();
while (!allIdList.isEmpty()) {
String topNodeId = null;
// searches top nodes.
for (String id : allIdList) {
List<String> sourceIdList = linkManager.getSource(id);
if (sourceIdList == null || sourceIdList.isEmpty()) {
topNodeId = id;
break;
}
}
// sets a node as a top if all nodes has a parent.
if (topNodeId == null) {
for (String notTopNodeId : allIdList) {
topNodeId = notTopNodeId;
break;
}
}
// checks the tree that start from a top node.
NodeInfo topNode = linkManager.getNodeInfo(topNodeId);
Set<String> checkedIdSet = validateCyclicStatePart(topNode);
allIdList.removeAll(checkedIdSet);
}
}
/**
* Validates links of the node.
* It throws a DcaseValidatorException if invalid connection is detected.
*
* @param node the node to validate.
*/
public void validateLink(BasicNode node) {
// creates the NodeInfo object.
NodeInfo nodeInfo = ModelUtil.createNodeInfo(node);
// gets the basic attributes.
String nodeId = (String) nodeInfo.getAttribute(AttributeType.ID);
String nodeName = (String) nodeInfo.getAttribute(AttributeType.NAME);
// gets the links.
List<String> targetIdList = linkManager.getTarget(nodeId);
List<String> sourceIdList = linkManager.getSource(nodeId);
// gets the connection rules.
NodeValidatorRule nodeValidatorRule = nodeInfo.getNodeType()
.getNodeValidatorRule();
// checks the connections(the multiplicity for children).
Map<NodeType, NodeMultiplicity> childMultiplicityMap = nodeValidatorRule
.getChildMultiplicity();
if (childMultiplicityMap != null) {
validateMultiplicity(childMultiplicityMap, targetIdList, nodeName,
Messages.DcaseValidator_2);
}
// checks the connections(the multiplicity for parents).
Map<NodeType, NodeMultiplicity> parentMultiplicityMap = nodeValidatorRule
.getParentMultiplicity();
if (parentMultiplicityMap != null) {
validateMultiplicity(parentMultiplicityMap, sourceIdList, nodeName,
Messages.DcaseValidator_3);
}
// checks the connections to children.
List<NodeConnectionRule> childConnectionRuleList = nodeValidatorRule
.getChildRule();
if (childConnectionRuleList != null) {
validateLinkRule(childConnectionRuleList, targetIdList, nodeName,
Messages.DcaseValidator_2);
}
// checks the connections to parents.
List<NodeConnectionRule> parentConnectionRuleList = nodeValidatorRule
.getParentRule();
if (parentConnectionRuleList != null) {
validateLinkRule(parentConnectionRuleList, sourceIdList, nodeName,
Messages.DcaseValidator_3);
}
}
/**
* Validates the connections (multiplicity).
* It throws a DcaseValidatorException if invalid connection is detected.
*
* @param multiplicityMap the multiplicity.
* @param idList a list of node IDs that connect with the node to validate.
* @param nodeName a node to validate.
* @param relationType the relation type (child or parent).
*/
private void validateMultiplicity(
Map<NodeType, NodeMultiplicity> multiplicityMap,
List<String> idList, String nodeName, String relationType) {
for (Map.Entry<NodeType, NodeMultiplicity> multiplicity : multiplicityMap
.entrySet()) {
NodeType nodeType = multiplicity.getKey();
NodeMultiplicity nodeMultiplicity = multiplicity.getValue();
// counts the nodes of specified type.
int nodeNum = countNode(idList, nodeType);
switch (nodeMultiplicity) {
case ZERO:
if (nodeNum != 0) {
throw new DcaseValidatorException(NLS.bind(
Messages.DcaseValidator_9,
new Object[] { nodeName, nodeType.getName(), relationType }),
MessageTypeImpl.VALIDATION_ERROR);
}
break;
case ONE:
if (nodeNum != 1) {
throw new DcaseValidatorException(NLS.bind(
Messages.DcaseValidator_10,
new Object[] { nodeName, nodeType.getName(), relationType }),
MessageTypeImpl.VALIDATION_ERROR);
}
break;
case ONE_OR_MORE:
if (nodeNum < 1) {
throw new DcaseValidatorException(NLS.bind(
Messages.DcaseValidator_11,
new Object[] { nodeName, nodeType.getName(), relationType }),
MessageTypeImpl.VALIDATION_ERROR);
}
break;
case ZERO_OR_ONE:
if (nodeNum > 1) {
throw new DcaseValidatorException(NLS.bind(
Messages.DcaseValidator_12,
new Object[] { nodeName, nodeType.getName(), relationType }),
MessageTypeImpl.VALIDATION_ERROR);
}
break;
case ZERO_OR_MORE:
default:
break;
}
}
}
/**
* Validates the connections.
* It throws a DcaseValidatorException if invalid connection is detected.
* @param connectionRuleList a list of rules
* @param idList a list of node IDs that connect with the node to validate.
* @param nodeName a node to validate.
* @param relationType the relation type (child or parent).
*/
private void validateLinkRule(
List<NodeConnectionRule> connectionRuleList,
List<String> idList, String nodeName, String relationType) {
for (NodeConnectionRule connectionRule : connectionRuleList) {
RuleOperator ruleOperator = connectionRule.getRuleOperator();
List<NodeType> nodeTypeList = connectionRule.getNodeList();
switch (ruleOperator) {
case AND:
for (NodeType nodeType : nodeTypeList) {
if (countNode(idList, nodeType) == 0) {
throw new DcaseValidatorException(NLS.bind(
Messages.DcaseValidator_13,
new Object[] { nodeName, relationType }),
MessageTypeImpl.VALIDATION_ERROR);
}
}
break;
case OR:
boolean isValidForOrRule = false;
for (NodeType nodeType : nodeTypeList) {
if (countNode(idList, nodeType) > 0) {
isValidForOrRule = true;
break;
}
}
if (!isValidForOrRule) {
throw new DcaseValidatorException(NLS.bind(
Messages.DcaseValidator_14,
new Object[] { nodeName, relationType }),
MessageTypeImpl.VALIDATION_ERROR);
}
break;
case NAND:
int nandNodeTypeCounter = 0;
for (NodeType nodeType : nodeTypeList) {
if (countNode(idList, nodeType) > 0) {
nandNodeTypeCounter++;
}
}
if (nandNodeTypeCounter > 1) {
throw new DcaseValidatorException(NLS.bind(
Messages.DcaseValidator_15,
new Object[] { nodeName, relationType }),
MessageTypeImpl.VALIDATION_ERROR);
}
break;
case XOR:
int xorNodeTypeCounter = 0;
for (NodeType nodeType : nodeTypeList) {
if (countNode(idList, nodeType) > 0) {
xorNodeTypeCounter++;
}
}
if (xorNodeTypeCounter != 1) {
throw new DcaseValidatorException(NLS.bind(
Messages.DcaseValidator_16,
new Object[] { nodeName, relationType }),
MessageTypeImpl.VALIDATION_ERROR);
}
break;
default:
break;
}
}
}
/**
* Returns the number of nodes of the specified type.
*
* @param idList the list of node IDs.
* @param nodeType the node type.
* @return the number of nodes.
*/
private int countNode(List<String> idList, NodeType nodeType) {
int count = 0;
if (idList == null) {
return count;
}
for (String id : idList) {
NodeInfo nodeInfo = linkManager.getNodeInfo(id);
if (nodeType.equals(nodeInfo.getNodeType())) {
count++;
}
}
return count;
}
}