package de.ugoe.cs.swe.bnftools.validation; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Stack; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.parsetree.AbstractNode; import org.eclipse.xtext.parsetree.CompositeNode; import org.eclipse.xtext.parsetree.LeafNode; import org.eclipse.xtext.parsetree.NodeUtil; import org.eclipse.xtext.resource.IResourceDescriptions; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.resource.XtextResourceSet; import org.eclipse.xtext.validation.Check; import org.eclipse.xtext.validation.CheckType; import com.google.inject.Inject; import de.ugoe.cs.swe.bnftools.analysis.EbnfAnalysisUtils; import de.ugoe.cs.swe.bnftools.ebnf.BnfEntry; import de.ugoe.cs.swe.bnftools.ebnf.DefinitionList; import de.ugoe.cs.swe.bnftools.ebnf.EtsiBnf; import de.ugoe.cs.swe.bnftools.ebnf.ExtRule; import de.ugoe.cs.swe.bnftools.ebnf.Import; import de.ugoe.cs.swe.bnftools.ebnf.Rule; import de.ugoe.cs.swe.bnftools.ebnf.SingleDefinition; public class EbnfJavaValidator extends AbstractEbnfJavaValidator { @Inject IResourceDescriptions resourceDescriptions; public static final String ruleReferencedOneDescription = "The rule is only referenced by one other rule"; public static final String passthroughRuleDescription = "The rule is a passthrough rule"; public static final String unreferencedPassthroughRuleDescription = "The rule is an unreferenced passthrough rule"; public static final String unusedRuleDescription = "The rule is not referenced anywhere"; public static final String equalAlternativeDescription = "The rule contains equal alternatives"; public static final String duplicateRulesDescription = "The rule is a duplicate"; public static final String duplicateSubRulesDescription = "A part of rule is a duplicate"; public static final String packageConsistencyCheckRuleMissingDescription = "Rule has been removed or renamed in the updated grammar. The package rule is therefore now an additional rule instead of a replacement rule. The package rule might therefore be inconsistent with the updated core grammar."; public static final String packageConsistencyCheckRuleDifferentDescription = "Corresponding core grammar rule has been changed in updated grammar. The package rule might have become inconsistent with the updated core grammar."; public static final String packageConsistencyCheckCorrespondingRuleMissingDescription = "Corresponding rule is missing in core grammar. Rule has been manually altered in the delta grammar (delta grammar is outdated) or the /core and /update flags are somehow switched."; public static boolean checkReferencedOnlyOnce = false; public static boolean checkPassthroughRule = false; public static boolean checkUnusedRule = false; public static boolean checkEqualAlternative = false; public static boolean checkDuplicateRules = false; public static boolean checkSubruleDuplicates = false; public static boolean checkUpdatedGrammarConsistency = false; // ---------------------------------------------------------------------------------------------------- @Check(CheckType.EXPENSIVE) public void checkUpdateGrammarConsistency(EtsiBnf bnf) { if (!checkUpdatedGrammarConsistency) return; CompositeNode compNode = NodeUtil.getRootNode(bnf); XtextResourceSet set = new XtextResourceSet(); URI uri = compNode.getElement().eResource().getURI(); Iterable allNodes = NodeUtil.getAllContents(compNode); if(!bnf.getType().equals("/delta")) return; Resource coreRes = null; Resource updatedRes = null; EList imports = bnf.getImportSection().getImports(); for(int j=0; j cLeaves = removeWS(cNode.getLeafNodes()); EList uLeaves = removeWS(uNode.getLeafNodes()); if (cLeaves.size() != uLeaves.size()) return false; for(int i=0; i < cLeaves.size(); i++) { if(!(cLeaves.get(i).serialize().equals(uLeaves.get(i).serialize()))) return false; } return true; } private EList removeWS(EList leaves) { for(int i=0; i < leaves.size(); i++) { if(!(leaves.get(i).getGrammarElement() instanceof org.eclipse.xtext.impl.TerminalRuleImpl)) leaves.remove(i); } return leaves; } // ---------------------------------------------------------------------------------------------------- @Check(CheckType.EXPENSIVE) public void checkReferencedOnlyOnce(Rule rule) { if (!checkReferencedOnlyOnce) return; if (EbnfAnalysisUtils.isTokenRule(rule)) return; List references = EbnfAnalysisUtils.findReferences(rule, resourceDescriptions); if (references.size() == 1) { warning(EbnfJavaValidator.ruleReferencedOneDescription, 1, EbnfJavaValidator.ruleReferencedOneDescription); } } // ---------------------------------------------------------------------------------------------------- @Check(CheckType.EXPENSIVE) public void checkPassthroughRule(Rule rule) { if (!checkPassthroughRule) return; List references = EbnfAnalysisUtils.findReferences(rule, resourceDescriptions); if (EbnfAnalysisUtils.isPassthroughRule(rule)) { if (references.size() == 0) { warning( EbnfJavaValidator.unreferencedPassthroughRuleDescription, 1, EbnfJavaValidator.unreferencedPassthroughRuleDescription); } else { warning(EbnfJavaValidator.passthroughRuleDescription, 1, EbnfJavaValidator.passthroughRuleDescription); } } } // ---------------------------------------------------------------------------------------------------- @Check(CheckType.EXPENSIVE) public void checkUnusedRule(Rule rule) { if (!checkUnusedRule) return; List references = EbnfAnalysisUtils.findReferences(rule, resourceDescriptions); if ((references.size() == 0) && (rule.getRulenumber() != 1)) warning(EbnfJavaValidator.unusedRuleDescription, 1, EbnfJavaValidator.unusedRuleDescription); } // ---------------------------------------------------------------------------------------------------- private List collectDefinitionLists(CompositeNode o) { List list = new ArrayList(); for (int i = 0; i < o.getChildren().size(); i++) { AbstractNode child = o.getChildren().get(i); if (child.getElement() instanceof DefinitionList) { list.add((DefinitionList) child.getElement()); } if (child instanceof CompositeNode) { list.addAll(collectDefinitionLists((CompositeNode) child)); } } return list; } // ---------------------------------------------------------------------------------------------------- @Check(CheckType.EXPENSIVE) public void checkEqualAlternative(Rule rule) { if (!checkEqualAlternative) return; // TODO: is not per rule, but global! // quadratic! CompositeNode startNode = NodeUtil.getNodeAdapter(rule).getParserNode(); List definitionLists = collectDefinitionLists(startNode); for (int k = 0; k < definitionLists.size(); k++) { EList singleDefinitions = definitionLists.get(k) .getSingleDefinition(); for (int i = 0; i < singleDefinitions.size(); i++) { for (int j = 0; j < singleDefinitions.size(); j++) { if (i == j) continue; SingleDefinition upper = singleDefinitions.get(i); SingleDefinition lower = singleDefinitions.get(j); CompositeNode upperNode = NodeUtil.getNodeAdapter(upper) .getParserNode(); CompositeNode lowerNode = NodeUtil.getNodeAdapter(lower) .getParserNode(); String upperString = upperNode.serialize().trim() .replaceAll("[ \t\n\r]", ""); String lowerString = lowerNode.serialize().trim() .replaceAll("[ \t\n\r]", ""); if (lowerString.equals(upperString)) warning(EbnfJavaValidator.equalAlternativeDescription, 1, EbnfJavaValidator.equalAlternativeDescription); } } } } // ---------------------------------------------------------------------------------------------------- @Check(CheckType.EXPENSIVE) public void checkDuplicateRules(Rule rule) { if (!checkDuplicateRules) return; EtsiBnf etsiBnfNode = (EtsiBnf) rule.eContainer().eContainer(); CompositeNode definitionList = NodeUtil.getNodeAdapter(rule.getDefinitionList()).getParserNode(); String rightHandSideText = definitionList.serialize().trim() .replaceAll("[ \t\n\r]", ""); for (int i = 0; i < etsiBnfNode.getBnfEntry().size(); i++) { BnfEntry currentNode = etsiBnfNode.getBnfEntry().get(i); if (currentNode == null) continue; if (currentNode.getRule() == null) continue; if (currentNode.getRule() == rule) continue; Rule currentRule = currentNode.getRule(); CompositeNode currentRuleDefinitionList = NodeUtil.getNodeAdapter(currentRule.getDefinitionList()).getParserNode(); String currentRuleRightHandSideText = currentRuleDefinitionList.serialize().trim() .replaceAll("[ \t\n\r]", ""); if (currentRuleRightHandSideText.equals(rightHandSideText)) { String description = EbnfJavaValidator.duplicateRulesDescription + " with rule \"" + currentRule.getName() + "\" (Line " + NodeUtil.getNodeAdapter(currentRule).getParserNode().getLine() + ")"; warning(description, 1, description); } } } // ---------------------------------------------------------------------------------------------------- @Check(CheckType.EXPENSIVE) public void checkSubruleDuplicates(EtsiBnf bnf) { if (!checkSubruleDuplicates) return; HashMap dupesMap = new HashMap(); Set taggedEntries = new HashSet(); // build dupes map for (int i=0; i < bnf.getBnfEntry().size(); i++) { BnfEntry currentBnfEntry = bnf.getBnfEntry().get(i); if ((currentBnfEntry.getRule() != null) && (currentBnfEntry.getRule().getDefinitionList() != null)) { CompositeNode ruleParserNode = NodeUtil.getNodeAdapter(currentBnfEntry.getRule().getDefinitionList()).getParserNode(); String ruleText = ruleParserNode.serialize().trim(); String processedRuleText = textSpacer(ruleText); String trimmedRuleText = processedRuleText.replaceAll("[\t\n\r]", " ").replaceAll("[ ]+", " "); String[] parts = trimmedRuleText.split(" "); for (int j=0; j <= parts.length; j++) { for (int k=j; k <= parts.length; k++) { if (isBalancedPairs(parts, j, k) && (k-j > 1)) { String balancedSubString = createString(parts, j, k); DuplicateEntry mapEntry = dupesMap.get(balancedSubString); if (mapEntry == null) { mapEntry = new DuplicateEntry(); mapEntry.setSnippet(balancedSubString); mapEntry.addRule(currentBnfEntry.getRule()); dupesMap.put(balancedSubString, mapEntry); } else { mapEntry.addRule(currentBnfEntry.getRule()); dupesMap.put(balancedSubString, mapEntry); taggedEntries.add(balancedSubString); } } } } } } // create warnings Iterator it = taggedEntries.iterator(); while (it.hasNext()) { String entry = it.next(); DuplicateEntry dupeEntry = dupesMap.get(entry); if (dupeEntry.getRules().size() < 2) continue; String warningText = ""; warningText += entry + " is duplicated in the following rules: "; for (int i=0; i < dupeEntry.getRules().size(); i++) { Rule rule = dupeEntry.getRules().get(i); CompositeNode parserNode = NodeUtil.getNodeAdapter(rule).getParserNode(); if (rule.getRulenumber() > 0) { String ruleNumber = Integer.toString(rule.getRulenumber()).replaceAll("[ \t\n\r]", ""); warningText += ruleNumber; } warningText += " (Line "; warningText += parserNode.getLine(); warningText += ")"; if (i+1 < dupeEntry.getRules().size()) warningText += ", "; } for (int i=0; i < dupeEntry.getRules().size(); i++) { Rule rule = dupeEntry.getRules().get(i); CompositeNode parserNode = NodeUtil.getNodeAdapter(rule).getParserNode(); warning(warningText, parserNode.getElement(), 1); } } } private String textSpacer(String ruleText) { ruleText = ruleText + " "; String processedRuleText = ruleText.replaceAll("([^\"])\\(([^\"])", "$1 ( $2"); // ( case 1 processedRuleText = processedRuleText.replaceAll("\\(([^\"])", " ( $1"); // ( case 2 processedRuleText = processedRuleText.replaceAll("([^\"])\\(\"", "$1 ( \""); // ( case 3 processedRuleText = processedRuleText.replaceAll("([^\"])\\)([^\"])", "$1 ) $2"); // ) case 1 processedRuleText = processedRuleText.replaceAll("\\)([^\"])", " ) $1"); // ) case 2 processedRuleText = processedRuleText.replaceAll("\"\\)([^\"])", "\" ) $1"); // ) case 3 processedRuleText = processedRuleText.replaceAll("([^\"])\\{([^\"])", "$1 { $2"); // { case 1 processedRuleText = processedRuleText.replaceAll("\\{([^\"])", " { $1"); // { case 2 processedRuleText = processedRuleText.replaceAll("([^\"])\\{\"", "$1 { \""); // { case 3 processedRuleText = processedRuleText.replaceAll("([^\"])\\}([^\\+\"])", "$1 } $2"); // } case 1 processedRuleText = processedRuleText.replaceAll("\\}([^\\+\"])", " } $1"); // } case 2 processedRuleText = processedRuleText.replaceAll("\"\\}([^\\+\"])", "\" } $1"); // } case 3 processedRuleText = processedRuleText.replaceAll("([^\"])\\}\\+([^\"])", "$1 }\\+ $2"); // }+ case 1 processedRuleText = processedRuleText.replaceAll("\\}\\+([^\"])", " }\\+ $1 "); // }+ case 2 processedRuleText = processedRuleText.replaceAll("\"\\}\\+([^\"])", "\" }\\+ $1 "); // }+ case 3 processedRuleText = processedRuleText.replaceAll("([^\"])\\[([^\"])", "$1 [ $2"); // [ case 1 processedRuleText = processedRuleText.replaceAll("\\[([^\"])", " [ $1"); // [ case 2 processedRuleText = processedRuleText.replaceAll("([^\"])\\[\"", "$1 [ \""); // [ case 3 processedRuleText = processedRuleText.replaceAll("([^\"])\\]([^\"])", "$1 ] $2"); // ] case 1 processedRuleText = processedRuleText.replaceAll("\\]([^\"])", " ] $1"); // ] case 2 processedRuleText = processedRuleText.replaceAll("\"\\]([^\"])", "\" ] $1"); // ] case 3 return processedRuleText; } private boolean isBalancedPairs(String[] parts, int start, int end) { Stack pairStack = new Stack(); if (start == end) return false; if (parts[start].contains("|")) return false; if (parts[start].contains("}")) return false; if (parts[start].contains("}+")) return false; if (parts[start].contains("]")) return false; if (parts[end-1].contains("|")) return false; for (int i=start; i < end; i++) { String currentPart = parts[i]; if (currentPart.equals("(") || currentPart.equals("{") || currentPart.equals("[")) { pairStack.push(currentPart); } if (!pairStack.isEmpty()) { if (currentPart.equals(")") && pairStack.peek().equals("(")) { pairStack.pop(); } else if (currentPart.equals("}") && pairStack.peek().equals("{")) { pairStack.pop(); } else if (currentPart.equals("}+") && pairStack.peek().equals("{")) { pairStack.pop(); } else if (currentPart.equals("]") && pairStack.peek().equals("[")) { pairStack.pop(); } } else { if (currentPart.equals(")") || currentPart.equals("}") || currentPart.equals("}+") || currentPart.equals("]")) { return false; } } } if (pairStack.empty()) return true; return false; } private String createString(String[] parts, int start, int end) { StringBuffer result = new StringBuffer(); for (int i=start; i < end; i++) { result.append(parts[i]); result.append(" "); } return result.toString().trim(); } }