/*******************************************************************************
* Copyright (c) 2009, 2016 itemis AG and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Fabian Steeg - intial Xtext generation (see bug #277380)
* Alexander Nyßen - initial implementation
* Tamas Miklossy - Add support for arrowType edge decorations (bug #477980)
* - Add support for polygon-based node shapes (bug #441352)
* - Add support for all dot attributes (bug #461506)
*
*******************************************************************************/
package org.eclipse.gef.dot.internal.language.validation;
import java.util.List;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.EList;
import org.eclipse.gef.dot.internal.DotAttributes;
import org.eclipse.gef.dot.internal.DotAttributes.Context;
import org.eclipse.gef.dot.internal.DotImport;
import org.eclipse.gef.dot.internal.language.dot.AttrList;
import org.eclipse.gef.dot.internal.language.dot.AttrStmt;
import org.eclipse.gef.dot.internal.language.dot.Attribute;
import org.eclipse.gef.dot.internal.language.dot.DotGraph;
import org.eclipse.gef.dot.internal.language.dot.DotPackage;
import org.eclipse.gef.dot.internal.language.dot.EdgeOp;
import org.eclipse.gef.dot.internal.language.dot.EdgeRhsNode;
import org.eclipse.gef.dot.internal.language.dot.EdgeRhsSubgraph;
import org.eclipse.gef.dot.internal.language.dot.GraphType;
import org.eclipse.gef.dot.internal.language.dot.NodeStmt;
import org.eclipse.gef.dot.internal.language.shape.PolygonBasedNodeShape;
import org.eclipse.gef.dot.internal.language.style.NodeStyle;
import org.eclipse.gef.dot.internal.language.terminals.ID;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.validation.Check;
import org.eclipse.xtext.validation.RangeBasedDiagnostic;
/**
* Provides DOT-specific validation rules.
*
* @author anyssen
*
*/
public class DotJavaValidator extends AbstractDotJavaValidator {
/**
* Checks that within an {@link Attribute} only valid attribute values are
* used (dependent on context, in which the attribute is specified).
*
* @param attribute
* The {@link Attribute} to validate.
*/
@Check
public void checkValidAttributeValue(final Attribute attribute) {
String attributeName = attribute.getName().toValue();
ID attributeValue = attribute.getValue();
List<Diagnostic> diagnostics = DotAttributes.validateAttributeRawValue(
DotAttributes.getContext(attribute), attributeName,
attributeValue);
List<INode> nodes = NodeModelUtils.findNodesForFeature(attribute,
DotPackage.Literals.ATTRIBUTE__VALUE);
if (nodes.size() != 1) {
System.err.println(
"Exact 1 node is expected for the attribute value: "
+ attributeValue + ", but got " + nodes.size());
return;
}
INode node = nodes.get(0);
int attributeValueStartOffset = node.getOffset();
if (attributeValue.getType() == ID.Type.HTML_STRING
|| attributeValue.getType() == ID.Type.QUOTED_STRING) {
// +1 is needed because of the < symbol (indicating the
// beginning of a html-like label) or " symbol (indicating the
// beginning of a quoted string)
attributeValueStartOffset++;
}
for (Diagnostic d : diagnostics) {
if (d instanceof RangeBasedDiagnostic) {
RangeBasedDiagnostic rangeBasedDiagnostic = (RangeBasedDiagnostic) d;
String message = rangeBasedDiagnostic.getMessage();
int length = rangeBasedDiagnostic.getLength();
String code = rangeBasedDiagnostic.getIssueCode();
String[] issueData = rangeBasedDiagnostic.getIssueData();
int offset = rangeBasedDiagnostic.getOffset()
+ attributeValueStartOffset;
switch (d.getSeverity()) {
case Diagnostic.ERROR:
getMessageAcceptor().acceptError(message, attribute, offset,
length, code, issueData);
break;
case Diagnostic.WARNING:
getMessageAcceptor().acceptWarning(message, attribute,
offset, length, code, issueData);
break;
case Diagnostic.INFO:
getMessageAcceptor().acceptError(message, attribute, offset,
length, code, issueData);
break;
}
} else {
switch (d.getSeverity()) {
case Diagnostic.ERROR:
getMessageAcceptor().acceptError(d.getMessage(), attribute,
DotPackage.Literals.ATTRIBUTE__VALUE,
INSIGNIFICANT_INDEX, attributeName,
attributeValue.toValue());
break;
case Diagnostic.WARNING:
getMessageAcceptor().acceptWarning(d.getMessage(),
attribute, DotPackage.Literals.ATTRIBUTE__VALUE,
INSIGNIFICANT_INDEX, attributeName,
attributeValue.toValue());
break;
case Diagnostic.INFO:
getMessageAcceptor().acceptInfo(d.getMessage(), attribute,
DotPackage.Literals.ATTRIBUTE__VALUE,
INSIGNIFICANT_INDEX, attributeName,
attributeValue.toValue());
break;
}
}
}
}
/**
* Ensures that the 'striped' node style is used only for
* rectangularly-shaped nodes ('box', 'rect', 'rectangle' and 'square').
*
* @param attribute
* The node style attribute to validate.
*/
@Check
public void checkValidCombinationOfNodeShapeAndStyle(Attribute attribute) {
if (DotAttributes.getContext(attribute) == Context.NODE
&& attribute.getName().toValue()
.equals(DotAttributes.STYLE__GCNE)
&& attribute.getValue().toValue()
.equals(NodeStyle.STRIPED.toString())) {
EList<AttrList> attributeList = null;
NodeStmt node = EcoreUtil2.getContainerOfType(attribute,
NodeStmt.class);
if (node != null) {
attributeList = node.getAttrLists();
} else {
AttrStmt attrStmt = EcoreUtil2.getContainerOfType(attribute,
AttrStmt.class);
if (attrStmt != null) {
attributeList = attrStmt.getAttrLists();
}
}
if (attributeList != null) {
// TODO: DotImport should not be referenced here
ID shapeValue = DotImport.getAttributeValue(attributeList,
DotAttributes.SHAPE__N);
// if the shape value is not explicitly set, use the default
// shape value for evaluation
if (shapeValue == null) {
shapeValue = ID.fromString(
PolygonBasedNodeShape.ELLIPSE.toString());
}
switch (PolygonBasedNodeShape.get(shapeValue.toValue())) {
case BOX:
case RECT:
case RECTANGLE:
case SQUARE:
break;
default:
error("The style 'striped' is only supported with clusters and rectangularly-shaped nodes, such as 'box', 'rect', 'rectangle', 'square'.",
DotPackage.eINSTANCE.getAttribute_Value());
}
}
}
}
/**
* Ensures that within {@link EdgeRhsNode}, '->' is used in directed
* graphs, while '--' is used in undirected graphs.
*
* @param edgeRhsNode
* The EdgeRhsNode to validate.
*/
@Check
public void checkEdgeOpCorrespondsToGraphType(EdgeRhsNode edgeRhsNode) {
checkEdgeOpCorrespondsToGraphType(edgeRhsNode.getOp(), EcoreUtil2
.getContainerOfType(edgeRhsNode, DotGraph.class).getType());
}
/**
* Ensures that within {@link EdgeRhsSubgraph} '->' is used in directed
* graphs, while '--' is used in undirected graphs.
*
* @param edgeRhsSubgraph
* The EdgeRhsSubgraph to validate.
*/
@Check
public void checkEdgeOpCorrespondsToGraphType(
EdgeRhsSubgraph edgeRhsSubgraph) {
checkEdgeOpCorrespondsToGraphType(edgeRhsSubgraph.getOp(), EcoreUtil2
.getContainerOfType(edgeRhsSubgraph, DotGraph.class).getType());
}
private void checkEdgeOpCorrespondsToGraphType(EdgeOp edgeOp,
GraphType graphType) {
boolean edgeDirected = edgeOp.equals(EdgeOp.DIRECTED);
boolean graphDirected = graphType.equals(GraphType.DIGRAPH);
if (graphDirected && !edgeDirected) {
error("EdgeOp '--' may only be used in undirected graphs.",
DotPackage.eINSTANCE.getEdgeRhs_Op());
} else if (!graphDirected && edgeDirected) {
error("EdgeOp '->' may only be used in directed graphs.",
DotPackage.eINSTANCE.getEdgeRhs_Op());
}
}
}