From 3564713078409adea416b24515a55d7eff666013 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sun, 5 Nov 2023 21:20:58 +0100 Subject: feat(language): validate unique names --- .../ProblemCrossrefProposalProvider.java | 2 +- .../language/resource/ReferenceCounter.java | 57 ------------------- .../language/validation/ProblemValidator.java | 66 +++++++++++++++++++--- .../language/validation/ReferenceCounter.java | 57 +++++++++++++++++++ 4 files changed, 116 insertions(+), 66 deletions(-) delete mode 100644 subprojects/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java create mode 100644 subprojects/language/src/main/java/tools/refinery/language/validation/ReferenceCounter.java diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java index ea90a82e..9bbce54b 100644 --- a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java +++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java @@ -18,7 +18,7 @@ import org.eclipse.xtext.resource.IEObjectDescription; import org.eclipse.xtext.scoping.IScope; import tools.refinery.language.model.problem.Problem; import tools.refinery.language.resource.ProblemResourceDescriptionStrategy; -import tools.refinery.language.resource.ReferenceCounter; +import tools.refinery.language.validation.ReferenceCounter; import tools.refinery.language.utils.ProblemUtil; import java.util.ArrayList; diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java deleted file mode 100644 index f1be55ee..00000000 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.language.resource; - -import java.util.HashMap; -import java.util.Map; - -import org.eclipse.emf.ecore.EObject; -import org.eclipse.xtext.util.IResourceScopeCache; -import org.eclipse.xtext.util.Tuples; - -import com.google.inject.Inject; -import com.google.inject.Singleton; - -import tools.refinery.language.model.problem.Problem; - -@Singleton -public class ReferenceCounter { - @Inject - private IResourceScopeCache cache = IResourceScopeCache.NullImpl.INSTANCE; - - public int countReferences(Problem problem, EObject eObject) { - var count = getReferenceCounts(problem).get(eObject); - if (count == null) { - return 0; - } - return count; - } - - protected Map getReferenceCounts(Problem problem) { - var resource = problem.eResource(); - if (resource == null) { - return doGetReferenceCounts(problem); - } - return cache.get(Tuples.create(problem, "referenceCounts"), resource, () -> doGetReferenceCounts(problem)); - } - - protected Map doGetReferenceCounts(Problem problem) { - var map = new HashMap(); - countCrossReferences(problem, map); - var iterator = problem.eAllContents(); - while (iterator.hasNext()) { - var eObject = iterator.next(); - countCrossReferences(eObject, map); - } - return map; - } - - protected void countCrossReferences(EObject eObject, Map map) { - for (var referencedObject : eObject.eCrossReferences()) { - map.compute(referencedObject, (key, currentValue) -> currentValue == null ? 1 : currentValue + 1); - } - } -} diff --git a/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java index 56a934cf..ef04726b 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java +++ b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java @@ -9,15 +9,17 @@ */ package tools.refinery.language.validation; +import com.google.inject.Inject; import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.validation.Check; - -import com.google.inject.Inject; - import tools.refinery.language.model.problem.*; -import tools.refinery.language.resource.ReferenceCounter; import tools.refinery.language.utils.ProblemUtil; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Set; + /** * This class contains custom validation rules. *

@@ -29,13 +31,15 @@ public class ProblemValidator extends AbstractProblemValidator { public static final String SINGLETON_VARIABLE_ISSUE = ISSUE_PREFIX + "SINGLETON_VARIABLE"; - public static final String NON_INDIVIDUAL_NODE_ISSUE = ISSUE_PREFIX + "NON_INDIVIDUAL_NODE"; + public static final String NODE_CONSTANT_ISSUE = ISSUE_PREFIX + "NODE_CONSTANT_ISSUE"; + + public static final String DUPLICATE_NAME_ISSUE = ISSUE_PREFIX + "DUPLICATE_NAME"; @Inject private ReferenceCounter referenceCounter; @Check - public void checkUniqueVariable(VariableOrNodeExpr expr) { + public void checkSingletonVariable(VariableOrNodeExpr expr) { var variableOrNode = expr.getVariableOrNode(); if (variableOrNode instanceof Variable variable && ProblemUtil.isImplicitVariable(variable) && !ProblemUtil.isSingletonVariable(variable)) { @@ -51,14 +55,60 @@ public class ProblemValidator extends AbstractProblemValidator { } @Check - public void checkNonUniqueNode(VariableOrNodeExpr expr) { + public void checkNodeConstants(VariableOrNodeExpr expr) { var variableOrNode = expr.getVariableOrNode(); if (variableOrNode instanceof Node node && !ProblemUtil.isIndividualNode(node)) { var name = node.getName(); var message = ("Only individuals can be referenced in predicates. " + "Mark '%s' as individual with the declaration 'indiv %s.'").formatted(name, name); error(message, expr, ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE, - INSIGNIFICANT_INDEX, NON_INDIVIDUAL_NODE_ISSUE); + INSIGNIFICANT_INDEX, NODE_CONSTANT_ISSUE); + } + } + + @Check + public void checkUniqueDeclarations(Problem problem) { + var relations = new ArrayList(); + var individuals = new ArrayList(); + for (var statement : problem.getStatements()) { + if (statement instanceof Relation relation) { + relations.add(relation); + } else if (statement instanceof IndividualDeclaration individualDeclaration) { + individuals.addAll(individualDeclaration.getNodes()); + } + } + checkUniqueSimpleNames(relations); + checkUniqueSimpleNames(individuals); + } + + @Check + public void checkUniqueFeatures(ClassDeclaration classDeclaration) { + checkUniqueSimpleNames(classDeclaration.getFeatureDeclarations()); + } + + @Check + public void checkUniqueLiterals(EnumDeclaration enumDeclaration) { + checkUniqueSimpleNames(enumDeclaration.getLiterals()); + } + + protected void checkUniqueSimpleNames(Iterable namedElements) { + var names = new LinkedHashMap>(); + for (var namedElement : namedElements) { + var name = namedElement.getName(); + var objectsWithName = names.computeIfAbsent(name, ignored -> new LinkedHashSet<>()); + objectsWithName.add(namedElement); + } + for (var entry : names.entrySet()) { + var objectsWithName = entry.getValue(); + if (objectsWithName.size() <= 1) { + continue; + } + var name = entry.getKey(); + var message = "Duplicate name '%s'.".formatted(name); + for (var namedElement : objectsWithName) { + acceptError(message, namedElement, ProblemPackage.Literals.NAMED_ELEMENT__NAME, 0, + DUPLICATE_NAME_ISSUE); + } } } } diff --git a/subprojects/language/src/main/java/tools/refinery/language/validation/ReferenceCounter.java b/subprojects/language/src/main/java/tools/refinery/language/validation/ReferenceCounter.java new file mode 100644 index 00000000..55cbd71d --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/validation/ReferenceCounter.java @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.language.validation; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.util.IResourceScopeCache; +import org.eclipse.xtext.util.Tuples; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import tools.refinery.language.model.problem.Problem; + +@Singleton +public class ReferenceCounter { + @Inject + private IResourceScopeCache cache = IResourceScopeCache.NullImpl.INSTANCE; + + public int countReferences(Problem problem, EObject eObject) { + var count = getReferenceCounts(problem).get(eObject); + if (count == null) { + return 0; + } + return count; + } + + protected Map getReferenceCounts(Problem problem) { + var resource = problem.eResource(); + if (resource == null) { + return doGetReferenceCounts(problem); + } + return cache.get(Tuples.create(problem, "referenceCounts"), resource, () -> doGetReferenceCounts(problem)); + } + + protected Map doGetReferenceCounts(Problem problem) { + var map = new HashMap(); + countCrossReferences(problem, map); + var iterator = problem.eAllContents(); + while (iterator.hasNext()) { + var eObject = iterator.next(); + countCrossReferences(eObject, map); + } + return map; + } + + protected void countCrossReferences(EObject eObject, Map map) { + for (var referencedObject : eObject.eCrossReferences()) { + map.compute(referencedObject, (key, currentValue) -> currentValue == null ? 1 : currentValue + 1); + } + } +} -- cgit v1.2.3-54-g00ecf