mirror of
https://github.com/sigmasternchen/xtext-core
synced 2025-03-15 08:18:55 +00:00
This change introduces an explicit validation context that is used by the INamesAreUniqueValidationHelper. This allows to fine tune the scope of the validation in various ways. Default contexts are available to validation uniqueness in the current container, along the chain of visible containers or local to the current resource (the default). Local unique name validation can be implemented now based on the LocalUniqueNameContext. closes #1466
This commit is contained in:
parent
aa01d49a98
commit
7b5900fc5a
12 changed files with 1286 additions and 113 deletions
|
@ -1,5 +1,5 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009 itemis AG (http://www.itemis.eu) and others.
|
||||
* Copyright (c) 2009, 2020 itemis AG (http://www.itemis.eu) and others.
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
|
@ -8,6 +8,7 @@
|
|||
*******************************************************************************/
|
||||
package org.eclipse.xtext.validation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.emf.ecore.EAttribute;
|
||||
|
@ -26,7 +27,9 @@ import org.eclipse.xtext.resource.IEObjectDescription;
|
|||
import org.eclipse.xtext.scoping.Scopes;
|
||||
import org.eclipse.xtext.service.OperationCanceledError;
|
||||
import org.eclipse.xtext.util.CancelIndicator;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runners.MethodSorters;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
@ -35,6 +38,7 @@ import com.google.common.collect.Lists;
|
|||
/**
|
||||
* @author Sebastian Zarnekow - Initial contribution and API
|
||||
*/
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class NamesAreUniqueValidationHelperTest extends AbstractValidationMessageAcceptingTestCase implements CancelIndicator {
|
||||
|
||||
private NamesAreUniqueValidationHelper helper;
|
||||
|
@ -58,6 +62,7 @@ public class NamesAreUniqueValidationHelperTest extends AbstractValidationMessag
|
|||
expected = Lists.newArrayList();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test public void testCancel_01() {
|
||||
maxCallCount = 1;
|
||||
try {
|
||||
|
@ -73,6 +78,7 @@ public class NamesAreUniqueValidationHelperTest extends AbstractValidationMessag
|
|||
assertEquals(maxCallCount, callCount);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test public void testCancel_02() {
|
||||
maxCallCount = 0;
|
||||
helper.checkUniqueNames(
|
||||
|
@ -84,6 +90,7 @@ public class NamesAreUniqueValidationHelperTest extends AbstractValidationMessag
|
|||
assertEquals(2, callCount);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test public void testCancel_03() {
|
||||
maxCallCount = 0;
|
||||
helper.checkUniqueNames(
|
||||
|
@ -95,6 +102,7 @@ public class NamesAreUniqueValidationHelperTest extends AbstractValidationMessag
|
|||
assertEquals(0, callCount);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test public void testCreatedErrors_01() {
|
||||
maxCallCount = 0;
|
||||
ImmutableList<EClass> classes = ImmutableList.of(
|
||||
|
@ -112,6 +120,23 @@ public class NamesAreUniqueValidationHelperTest extends AbstractValidationMessag
|
|||
assertTrue(expected.isEmpty());
|
||||
}
|
||||
|
||||
@Test public void testCreatedErrors_01_context() {
|
||||
maxCallCount = 0;
|
||||
ImmutableList<EClass> classes = ImmutableList.of(
|
||||
createEClass(),
|
||||
createEClass()
|
||||
);
|
||||
for(EClass clazz: classes) {
|
||||
clazz.setName("Same");
|
||||
}
|
||||
expected.addAll(classes);
|
||||
helper.checkUniqueNames(
|
||||
new LocalUniqueNameContext(classes, this), this);
|
||||
assertEquals(classes.size(), callCount);
|
||||
assertTrue(expected.isEmpty());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test public void testCreatedErrors_02() {
|
||||
maxCallCount = 0;
|
||||
ImmutableList<EClassifier> classifiers = ImmutableList.of(
|
||||
|
@ -129,6 +154,23 @@ public class NamesAreUniqueValidationHelperTest extends AbstractValidationMessag
|
|||
assertTrue(expected.isEmpty());
|
||||
}
|
||||
|
||||
@Test public void testCreatedErrors_02_context() {
|
||||
maxCallCount = 0;
|
||||
ImmutableList<EClassifier> classifiers = ImmutableList.of(
|
||||
createEClass(),
|
||||
createEDataType()
|
||||
);
|
||||
for(EClassifier classifier: classifiers) {
|
||||
classifier.setName("Same");
|
||||
}
|
||||
expected.addAll(classifiers);
|
||||
helper.checkUniqueNames(
|
||||
new LocalUniqueNameContext(classifiers, this), this);
|
||||
assertEquals(classifiers.size(), callCount);
|
||||
assertTrue(expected.isEmpty());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test public void testCreatedErrors_03() {
|
||||
maxCallCount = 0;
|
||||
ImmutableList<ENamedElement> elements = ImmutableList.of(
|
||||
|
@ -147,6 +189,24 @@ public class NamesAreUniqueValidationHelperTest extends AbstractValidationMessag
|
|||
assertTrue(expected.isEmpty());
|
||||
}
|
||||
|
||||
@Test public void testCreatedErrors_03_context() {
|
||||
maxCallCount = 0;
|
||||
ImmutableList<ENamedElement> elements = ImmutableList.of(
|
||||
createEClass(),
|
||||
createEDataType(),
|
||||
createEPackage()
|
||||
);
|
||||
for(ENamedElement classifier: elements) {
|
||||
classifier.setName("Same");
|
||||
}
|
||||
expected.addAll(elements.subList(0, 2));
|
||||
helper.checkUniqueNames(
|
||||
new LocalUniqueNameContext(elements, this), this);
|
||||
assertEquals(elements.size(), callCount);
|
||||
assertTrue(expected.isEmpty());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test public void testCreatedErrors_04() {
|
||||
maxCallCount = 0;
|
||||
ImmutableList<ENamedElement> elements = ImmutableList.of(
|
||||
|
@ -166,6 +226,25 @@ public class NamesAreUniqueValidationHelperTest extends AbstractValidationMessag
|
|||
assertTrue(expected.isEmpty());
|
||||
}
|
||||
|
||||
@Test public void testCreatedErrors_04_context() {
|
||||
maxCallCount = 0;
|
||||
ImmutableList<ENamedElement> elements = ImmutableList.of(
|
||||
createEClass(),
|
||||
createEDataType(),
|
||||
createEPackage(),
|
||||
createEPackage()
|
||||
);
|
||||
for(ENamedElement classifier: elements) {
|
||||
classifier.setName("Same");
|
||||
}
|
||||
expected.addAll(elements);
|
||||
helper.checkUniqueNames(
|
||||
new LocalUniqueNameContext(elements, this), this);
|
||||
assertEquals(elements.size(), callCount);
|
||||
assertTrue(expected.isEmpty());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test public void testCreatedErrors_05() {
|
||||
maxCallCount = 0;
|
||||
ImmutableList<ENamedElement> elements = ImmutableList.of(
|
||||
|
@ -185,6 +264,25 @@ public class NamesAreUniqueValidationHelperTest extends AbstractValidationMessag
|
|||
assertTrue(expected.isEmpty());
|
||||
}
|
||||
|
||||
@Test public void testCreatedErrors_05_context() {
|
||||
maxCallCount = 0;
|
||||
ImmutableList<ENamedElement> elements = ImmutableList.of(
|
||||
createEPackage(),
|
||||
createEDataType(),
|
||||
createEPackage()
|
||||
);
|
||||
for(ENamedElement classifier: elements) {
|
||||
classifier.setName("Same");
|
||||
}
|
||||
expected.add(elements.get(0));
|
||||
expected.add(elements.get(2));
|
||||
helper.checkUniqueNames(
|
||||
new LocalUniqueNameContext(elements, this), this);
|
||||
assertEquals(elements.size(), callCount);
|
||||
assertTrue(expected.isEmpty());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test public void testCreatedErrors_06() {
|
||||
maxCallCount = 1;
|
||||
ImmutableList<ENamedElement> elements = ImmutableList.of(
|
||||
|
@ -205,6 +303,26 @@ public class NamesAreUniqueValidationHelperTest extends AbstractValidationMessag
|
|||
assertEquals(1, callCount);
|
||||
}
|
||||
|
||||
@Test public void testCreatedErrors_06_context() {
|
||||
maxCallCount = 1;
|
||||
ImmutableList<ENamedElement> elements = ImmutableList.of(
|
||||
createEPackage(),
|
||||
createEDataType(),
|
||||
createEPackage()
|
||||
);
|
||||
for(ENamedElement classifier: elements) {
|
||||
classifier.setName("Same");
|
||||
}
|
||||
try {
|
||||
helper.checkUniqueNames(
|
||||
new LocalUniqueNameContext(elements, this), this);
|
||||
fail("cancellation expected");
|
||||
} catch (OperationCanceledError e) {
|
||||
}
|
||||
assertEquals(1, callCount);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test public void testCreatedErrors_07() {
|
||||
maxCallCount = 0;
|
||||
ImmutableList<ENamedElement> elements = ImmutableList.of(
|
||||
|
@ -224,6 +342,122 @@ public class NamesAreUniqueValidationHelperTest extends AbstractValidationMessag
|
|||
assertTrue(expected.isEmpty());
|
||||
}
|
||||
|
||||
@Test public void testCreatedErrors_07_context() {
|
||||
maxCallCount = 0;
|
||||
ImmutableList<ENamedElement> elements = ImmutableList.of(
|
||||
createEPackage(),
|
||||
createEDataType(),
|
||||
EcoreFactory.eINSTANCE.createEEnumLiteral()
|
||||
);
|
||||
for(ENamedElement classifier: elements) {
|
||||
classifier.setName("Same");
|
||||
}
|
||||
expected.add(elements.get(0));
|
||||
expected.add(elements.get(2));
|
||||
helper.checkUniqueNames(
|
||||
new LocalUniqueNameContext(elements, this), this);
|
||||
assertEquals(elements.size(), callCount);
|
||||
assertTrue(expected.isEmpty());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test public void testManyUnique() {
|
||||
List<ENamedElement> elements = new ArrayList<>();
|
||||
for(int i = 0; i < 1_000_000; i++) {
|
||||
EClass c = createEClass();
|
||||
c.setName("i" + i);
|
||||
elements.add(c);
|
||||
}
|
||||
maxCallCount = 0;
|
||||
helper.checkUniqueNames(
|
||||
Scopes.scopedElementsFor(elements),
|
||||
this, this);
|
||||
}
|
||||
|
||||
@Test public void testManyUnique_context() {
|
||||
List<ENamedElement> elements = new ArrayList<>();
|
||||
for(int i = 0; i < 1_000_000; i++) {
|
||||
EClass c = createEClass();
|
||||
c.setName("i" + i);
|
||||
elements.add(c);
|
||||
}
|
||||
maxCallCount = 0;
|
||||
helper.checkUniqueNames(
|
||||
new LocalUniqueNameContext(elements, this), this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test public void testManyOneDup() {
|
||||
List<ENamedElement> elements = new ArrayList<>();
|
||||
for(int i = 0; i < 100_000; i++) {
|
||||
EClass c = createEClass();
|
||||
if (i == 99_999) {
|
||||
c.setName("i1234");
|
||||
} else {
|
||||
c.setName("i" + i);
|
||||
}
|
||||
elements.add(c);
|
||||
}
|
||||
maxCallCount = 0;
|
||||
expected.add(elements.get(1_234));
|
||||
expected.add(elements.get(99_999));
|
||||
helper.checkUniqueNames(
|
||||
Scopes.scopedElementsFor(elements),
|
||||
this, this);
|
||||
}
|
||||
|
||||
@Test public void testManyOneDup_context() {
|
||||
List<ENamedElement> elements = new ArrayList<>();
|
||||
for(int i = 0; i < 100_000; i++) {
|
||||
EClass c = createEClass();
|
||||
if (i == 99_999) {
|
||||
c.setName("i1234");
|
||||
} else {
|
||||
c.setName("i" + i);
|
||||
}
|
||||
elements.add(c);
|
||||
}
|
||||
maxCallCount = 0;
|
||||
expected.add(elements.get(1_234));
|
||||
expected.add(elements.get(99_999));
|
||||
helper.checkUniqueNames(
|
||||
new LocalUniqueNameContext(elements, this), this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test public void testManyManyDup() {
|
||||
List<ENamedElement> elements = new ArrayList<>();
|
||||
for(int i = 0, j = 0; i < 100_000; i++) {
|
||||
if (i % 100 == 0) {
|
||||
j++;
|
||||
}
|
||||
EClass c = createEClass();
|
||||
c.setName("i" + j);
|
||||
elements.add(c);
|
||||
}
|
||||
maxCallCount = 0;
|
||||
expected.addAll(elements);
|
||||
helper.checkUniqueNames(
|
||||
Scopes.scopedElementsFor(elements),
|
||||
this, this);
|
||||
}
|
||||
|
||||
@Test public void testManyManyDup_context() {
|
||||
List<ENamedElement> elements = new ArrayList<>();
|
||||
for(int i = 0, j = 0; i < 100_000; i++) {
|
||||
if (i % 100 == 0) {
|
||||
j++;
|
||||
}
|
||||
EClass c = createEClass();
|
||||
c.setName("i" + j);
|
||||
elements.add(c);
|
||||
}
|
||||
maxCallCount = 0;
|
||||
expected.addAll(elements);
|
||||
helper.checkUniqueNames(
|
||||
new LocalUniqueNameContext(elements, this), this);
|
||||
}
|
||||
|
||||
@Test public void testErrorMessage_01() {
|
||||
EClass eClass = createEClass();
|
||||
eClass.setName("EClassName");
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009 itemis AG (http://www.itemis.eu) and others.
|
||||
* Copyright (c) 2009, 2020 itemis AG (http://www.itemis.eu) and others.
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
|
@ -46,6 +46,7 @@ public class NamesAreUniqueValidatorTest extends AbstractXtextTests implements I
|
|||
private Map<Object, Object> context;
|
||||
private Resource resource;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009 itemis AG (http://www.itemis.eu) and others.
|
||||
* Copyright (c) 2009, 2020 itemis AG (http://www.itemis.eu) and others.
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
|
@ -25,6 +25,8 @@ import org.eclipse.xtext.resource.IResourceDescription.Delta;
|
|||
import org.eclipse.xtext.resource.IResourceDescriptions;
|
||||
import org.eclipse.xtext.util.IResourceScopeCache;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
@ -53,6 +55,9 @@ public class DefaultResourceDescriptionManager implements IResourceDescription.M
|
|||
@Inject
|
||||
private DescriptionUtils descriptionUtils;
|
||||
|
||||
@Inject(optional = true)
|
||||
private ImmutableList<IsAffectedExtension> isAffectedExtensions = ImmutableList.of();
|
||||
|
||||
private static final String CACHE_KEY = DefaultResourceDescriptionManager.class.getName() + "#getResourceDescription";
|
||||
|
||||
@Override
|
||||
|
@ -113,44 +118,63 @@ public class DefaultResourceDescriptionManager implements IResourceDescription.M
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public boolean isAffected(Collection<Delta> deltas, IResourceDescription candidate, IResourceDescriptions context) {
|
||||
Set<URI> outgoingReferences = descriptionUtils.collectOutgoingReferences(candidate);
|
||||
if (!outgoingReferences.isEmpty()) {
|
||||
for (IResourceDescription.Delta delta : deltas)
|
||||
if (hasChanges(delta, candidate) && outgoingReferences.contains(delta.getUri()))
|
||||
return true;
|
||||
}
|
||||
// this is a tradeoff - we could either check whether a given delta uri is contained
|
||||
// in a reachable container and check for intersecting names afterwards, or we can do
|
||||
// the other way round
|
||||
// unfortunately there is no way to decide reliably which algorithm scales better
|
||||
// note that this method is called for each description so we have something like a
|
||||
// number of deltas x number of resources which is not really nice
|
||||
List<IContainer> containers = null;
|
||||
Collection<QualifiedName> importedNames = getImportedNames(candidate);
|
||||
for (IResourceDescription.Delta delta : deltas) {
|
||||
if (hasChanges(delta, candidate)) {
|
||||
// not a java resource - delta's resource should be contained in a visible container
|
||||
// as long as we did not delete the resource
|
||||
URI uri = delta.getUri();
|
||||
if ((uri.isPlatform() || uri.isArchive()) && delta.getNew() != null) {
|
||||
if (containers == null)
|
||||
containers = containerManager.getVisibleContainers(candidate, context);
|
||||
boolean descriptionIsContained = false;
|
||||
for(int i = 0; i < containers.size() && !descriptionIsContained; i++) {
|
||||
descriptionIsContained = containers.get(i).hasResourceDescription(uri);
|
||||
}
|
||||
if (!descriptionIsContained)
|
||||
return false;
|
||||
}
|
||||
if (isAffected(importedNames, delta.getNew()) || isAffected(importedNames, delta.getOld())) {
|
||||
Set<URI> outgoingReferences = descriptionUtils.collectOutgoingReferences(candidate);
|
||||
if (!outgoingReferences.isEmpty()) {
|
||||
for (IResourceDescription.Delta delta : deltas)
|
||||
if (hasChanges(delta, candidate) && outgoingReferences.contains(delta.getUri()))
|
||||
return true;
|
||||
}
|
||||
// this is a tradeoff - we could either check whether a given delta uri is contained
|
||||
// in a reachable container and check for intersecting names afterwards, or we can do
|
||||
// the other way round
|
||||
// unfortunately there is no way to decide reliably which algorithm scales better
|
||||
// note that this method is called for each description so we have something like a
|
||||
// number of deltas x number of resources which is not really nice
|
||||
List<IContainer> containers = null;
|
||||
Collection<QualifiedName> importedNames = getImportedNames(candidate);
|
||||
if (!importedNames.isEmpty()) {
|
||||
for (IResourceDescription.Delta delta : deltas) {
|
||||
if (hasChanges(delta, candidate)) {
|
||||
// not a java resource - delta's resource should be contained in a visible container
|
||||
// as long as we did not delete the resource
|
||||
URI uri = delta.getUri();
|
||||
if ((uri.isPlatform() || uri.isArchive()) && delta.getNew() != null) {
|
||||
if (containers == null)
|
||||
containers = containerManager.getVisibleContainers(candidate, context);
|
||||
boolean descriptionIsContained = false;
|
||||
for (int i = 0; i < containers.size() && !descriptionIsContained; i++) {
|
||||
descriptionIsContained = containers.get(i).hasResourceDescription(uri);
|
||||
}
|
||||
if (!descriptionIsContained) {
|
||||
return isAffectedByExtensions(deltas, candidate, context);
|
||||
}
|
||||
}
|
||||
if (isAffected(importedNames, delta.getNew()) || isAffected(importedNames, delta.getOld())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return isAffectedByExtensions(deltas, candidate, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query all registered extensions.
|
||||
*
|
||||
* @since 2.22
|
||||
*/
|
||||
@Beta
|
||||
protected boolean isAffectedByExtensions(Collection<Delta> deltas, IResourceDescription candidate,
|
||||
IResourceDescriptions context) {
|
||||
for (int i = 0; i < isAffectedExtensions.size(); i++) {
|
||||
if (isAffectedExtensions.get(i).isAffected(deltas, candidate, context)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given delta is considered to have changed from the candidate's perspective. By default this will just call
|
||||
|
@ -164,9 +188,9 @@ public class DefaultResourceDescriptionManager implements IResourceDescription.M
|
|||
|
||||
protected boolean isAffected(Collection<QualifiedName> importedNames, IResourceDescription description) {
|
||||
if (description != null) {
|
||||
for (IEObjectDescription desc : description.getExportedObjects())
|
||||
if (importedNames.contains(desc.getName().toLowerCase()))
|
||||
return true;
|
||||
for (IEObjectDescription desc : description.getExportedObjects())
|
||||
if (importedNames.contains(desc.getName().toLowerCase()))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2020 Sebastian Zarnekow and others.
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.resource.impl;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.xtext.resource.IResourceDescription;
|
||||
import org.eclipse.xtext.resource.IResourceDescriptions;
|
||||
import org.eclipse.xtext.resource.IResourceDescription.Delta;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.inject.Binding;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.TypeLiteral;
|
||||
|
||||
/**
|
||||
* An extension that can be bound to mark resources as affected
|
||||
* by a change. For example, if a validation depends on resources that are not
|
||||
* directly linked but which need to be considered when they have been changed.
|
||||
*
|
||||
* Multiple such extensions can be bound by means of unique Guice binding annotations.
|
||||
*
|
||||
* @since 2.22
|
||||
*/
|
||||
@Beta
|
||||
public interface IsAffectedExtension {
|
||||
|
||||
static class AllIsAffectedExtensions implements Provider<ImmutableList<IsAffectedExtension>> {
|
||||
|
||||
@Inject
|
||||
protected Injector injector;
|
||||
|
||||
@Override
|
||||
public ImmutableList<IsAffectedExtension> get() {
|
||||
ImmutableList.Builder<IsAffectedExtension> result = ImmutableList.builder();
|
||||
List<Binding<IsAffectedExtension>> bindings = injector
|
||||
.findBindingsByType(TypeLiteral.get(IsAffectedExtension.class));
|
||||
for (Binding<IsAffectedExtension> binding : bindings) {
|
||||
IsAffectedExtension extension = binding.getProvider().get();
|
||||
if (extension != null) {
|
||||
result.add(extension);
|
||||
}
|
||||
}
|
||||
return result.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Answers true if the given candidate is impacted by the given delta beyond the default
|
||||
* linking semantics. If the default answered true, an extension is never asked, e.g. it cannot
|
||||
* contradict the default implementation or other extensions by turning their yes into a no.
|
||||
*/
|
||||
boolean isAffected(Collection<Delta> deltas, IResourceDescription candidate, IResourceDescriptions context);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2008 itemis AG (http://www.itemis.eu) and others.
|
||||
* Copyright (c) 2008, 2020 itemis AG (http://www.itemis.eu) and others.
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
|
@ -45,6 +45,8 @@ import org.eclipse.xtext.resource.SynchronizedXtextResourceSet;
|
|||
import org.eclipse.xtext.resource.XtextResource;
|
||||
import org.eclipse.xtext.resource.XtextResourceFactory;
|
||||
import org.eclipse.xtext.resource.XtextResourceSet;
|
||||
import org.eclipse.xtext.resource.impl.IsAffectedExtension;
|
||||
import org.eclipse.xtext.resource.impl.IsAffectedExtension.AllIsAffectedExtensions;
|
||||
import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider;
|
||||
import org.eclipse.xtext.resource.impl.ResourceSetBasedResourceDescriptions;
|
||||
import org.eclipse.xtext.resource.impl.SimpleResourceDescriptionsBasedContainerManager;
|
||||
|
@ -59,10 +61,14 @@ import org.eclipse.xtext.serializer.sequencer.ISemanticSequencer;
|
|||
import org.eclipse.xtext.serializer.tokens.SerializerScopeProviderBinding;
|
||||
import org.eclipse.xtext.validation.CancelableDiagnostician;
|
||||
import org.eclipse.xtext.validation.IConcreteSyntaxValidator;
|
||||
import org.eclipse.xtext.validation.INamesAreUniqueValidationHelper;
|
||||
import org.eclipse.xtext.validation.impl.ConcreteSyntaxValidator;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.inject.Binder;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.name.Names;
|
||||
|
||||
/**
|
||||
|
@ -241,4 +247,13 @@ public abstract class DefaultRuntimeModule extends AbstractGenericModule {
|
|||
public void configureUseIndexFragmentsForLazyLinking(com.google.inject.Binder binder) {
|
||||
binder.bind(Boolean.TYPE).annotatedWith(Names.named(LazyURIEncoder.USE_INDEXED_FRAGMENTS_BINDING)).toInstance(Boolean.TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see IsAffectedExtension
|
||||
* @since 2.22
|
||||
*/
|
||||
public void configureIsAffectedExtensions(Binder binder) {
|
||||
binder.bind(new TypeLiteral<ImmutableList<IsAffectedExtension>>() {}).toProvider(AllIsAffectedExtensions.class);
|
||||
binder.bind(Key.get(IsAffectedExtension.class, Names.named("IsAffectedExtension.UniqueNames"))).to(INamesAreUniqueValidationHelper.ContextProvider.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2008, 2019 itemis AG (http://www.itemis.eu) and others.
|
||||
* Copyright (c) 2008, 2020 itemis AG (http://www.itemis.eu) and others.
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
|
@ -25,6 +25,7 @@ import org.eclipse.emf.ecore.EClass;
|
|||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.eclipse.emf.ecore.EStructuralFeature;
|
||||
import org.eclipse.xtext.diagnostics.Severity;
|
||||
import org.eclipse.xtext.util.CancelIndicator;
|
||||
import org.eclipse.xtext.util.Exceptions;
|
||||
import org.eclipse.xtext.util.SimpleCache;
|
||||
|
||||
|
@ -294,6 +295,23 @@ public abstract class AbstractDeclarativeValidator extends AbstractInjectableVal
|
|||
protected Map<Object, Object> getContext() {
|
||||
return state.get().context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a cancel indicator that is valid for the current validation run.
|
||||
*
|
||||
* @since 2.22
|
||||
*/
|
||||
protected CancelIndicator getCancelIndicator() {
|
||||
Map<Object, Object> context = getContext();
|
||||
if (context == null) {
|
||||
return CancelIndicator.NullImpl;
|
||||
}
|
||||
CancelIndicator result = (CancelIndicator) context.get(CancelableDiagnostician.CANCEL_INDICATOR);
|
||||
if (result == null) {
|
||||
return CancelIndicator.NullImpl;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final boolean internalValidate(EClass class1, EObject object, DiagnosticChain diagnostics,
|
||||
|
|
|
@ -0,0 +1,323 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2020 Sebastian Zarnekow and others.
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.validation;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.emf.ecore.EClass;
|
||||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.eclipse.emf.ecore.EcorePackage;
|
||||
import org.eclipse.emf.ecore.resource.Resource;
|
||||
import org.eclipse.emf.ecore.resource.ResourceSet;
|
||||
import org.eclipse.xtext.resource.IContainer;
|
||||
import org.eclipse.xtext.resource.IEObjectDescription;
|
||||
import org.eclipse.xtext.resource.IResourceDescription;
|
||||
import org.eclipse.xtext.resource.IResourceDescriptions;
|
||||
import org.eclipse.xtext.resource.IResourceDescriptionsProvider;
|
||||
import org.eclipse.xtext.resource.IResourceServiceProvider;
|
||||
import org.eclipse.xtext.resource.ISelectable;
|
||||
import org.eclipse.xtext.resource.impl.AbstractCompoundSelectable;
|
||||
import org.eclipse.xtext.scoping.ICaseInsensitivityHelper;
|
||||
import org.eclipse.xtext.scoping.impl.CaseInsensitivityHelper;
|
||||
import org.eclipse.xtext.util.CancelIndicator;
|
||||
import org.eclipse.xtext.validation.INamesAreUniqueValidationHelper.Context;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Standard implementation of a {@link INamesAreUniqueValidationHelper.Context}.
|
||||
*
|
||||
* @author Sebastian Zarnekow - Initial contribution and API
|
||||
* @since 2.22
|
||||
*/
|
||||
@Beta
|
||||
public class DefaultUniqueNameContext implements INamesAreUniqueValidationHelper.Context {
|
||||
|
||||
/**
|
||||
* Base class for {@link INamesAreUniqueValidationHelper.ContextProvider}.
|
||||
*/
|
||||
public static abstract class BaseContextProvider implements INamesAreUniqueValidationHelper.ContextProvider {
|
||||
@Inject
|
||||
private IResourceServiceProvider.Registry resourceServiceProviderRegistry = IResourceServiceProvider.Registry.INSTANCE;
|
||||
@Inject
|
||||
private ICaseInsensitivityHelper caseInsensitivityHelper = new CaseInsensitivityHelper();
|
||||
|
||||
protected IResourceServiceProvider getResourceServiceProvider(Resource r) {
|
||||
return resourceServiceProviderRegistry.getResourceServiceProvider(r.getURI());
|
||||
}
|
||||
|
||||
protected IResourceDescription getResourceDescription(Resource resource) {
|
||||
IResourceDescription.Manager manager = getResourceDescriptionManager(resource);
|
||||
if (manager != null) {
|
||||
return manager.getResourceDescription(resource);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected IResourceDescription.Manager getResourceDescriptionManager(Resource resource) {
|
||||
IResourceServiceProvider resourceServiceProvider = getResourceServiceProvider(resource);
|
||||
if (resourceServiceProvider == null) {
|
||||
return null;
|
||||
}
|
||||
return resourceServiceProvider.getResourceDescriptionManager();
|
||||
}
|
||||
|
||||
protected ICaseInsensitivityHelper getCaseInsensitivityHelper() {
|
||||
return caseInsensitivityHelper;
|
||||
}
|
||||
|
||||
protected void setResourceServiceProviderRegistry(
|
||||
IResourceServiceProvider.Registry resourceServiceProviderRegistry) {
|
||||
this.resourceServiceProviderRegistry = resourceServiceProviderRegistry;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for non-local unique name validation context providers.
|
||||
*/
|
||||
public static abstract class BaseGlobalContextProvider extends BaseContextProvider {
|
||||
@Inject
|
||||
private IResourceDescriptionsProvider indexAccess;
|
||||
|
||||
protected IResourceDescriptions getIndex(Resource resource) {
|
||||
ResourceSet resourceSet = resource.getResourceSet();
|
||||
if (resourceSet == null) {
|
||||
return null;
|
||||
}
|
||||
return indexAccess.getResourceDescriptions(resourceSet);
|
||||
}
|
||||
|
||||
protected boolean intersects(IResourceDescription left, IResourceDescription right, boolean caseSensitive) {
|
||||
for (IEObjectDescription description : left.getExportedObjects()) {
|
||||
Iterable<IEObjectDescription> exportedObjects = right.getExportedObjects(EcorePackage.Literals.EOBJECT,
|
||||
description.getName(), !caseSensitive);
|
||||
if (!Iterables.isEmpty(exportedObjects)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isAffected(IResourceDescription.Delta delta, IResourceDescription candidate,
|
||||
boolean caseSensitive) {
|
||||
if (candidate.getURI().equals(delta.getUri())) {
|
||||
return false;
|
||||
}
|
||||
if (delta.getNew() != null && intersects(delta.getNew(), candidate, caseSensitive)) {
|
||||
return true;
|
||||
}
|
||||
if (delta.getOld() != null && intersects(delta.getOld(), candidate, caseSensitive)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Provide a context for the entire index.
|
||||
* </p><p>
|
||||
* Drawback: If a resoure in a project A introduces a duplication with a resource in project B, an incremental build
|
||||
* of A may fail to notify B, if B is a dependency of A. Triggering a clean build in B will subsequently create the
|
||||
* validation problem / fix the validation problem there, too but in Eclipse, the incremental build will not provide
|
||||
* the same level of consistency.
|
||||
* </p>
|
||||
*/
|
||||
@Singleton
|
||||
public static class Global extends BaseGlobalContextProvider {
|
||||
|
||||
@Override
|
||||
public Context tryGetContext(Resource resource, CancelIndicator cancelIndicator) {
|
||||
IResourceDescriptions index = getIndex(resource);
|
||||
if (index == null) {
|
||||
return null;
|
||||
}
|
||||
IResourceDescription description = getResourceDescription(resource);
|
||||
if (description != null) {
|
||||
return new DefaultUniqueNameContext(description, index, getCaseInsensitivityHelper(), cancelIndicator);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAffected(Collection<IResourceDescription.Delta> deltas, IResourceDescription candidate,
|
||||
IResourceDescriptions context) {
|
||||
for (IResourceDescription.Delta delta : deltas) {
|
||||
if (isAffected(delta, candidate, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a context for current {@link IContainer}.
|
||||
*/
|
||||
@Singleton
|
||||
public static class Container extends BaseGlobalContextProvider {
|
||||
|
||||
@Inject
|
||||
private IContainer.Manager containerManager;
|
||||
|
||||
@Override
|
||||
public Context tryGetContext(Resource resource, CancelIndicator cancelIndicator) {
|
||||
IResourceDescriptions index = getIndex(resource);
|
||||
if (index == null) {
|
||||
return null;
|
||||
}
|
||||
IResourceDescription description = getResourceDescription(resource);
|
||||
if (description == null) {
|
||||
return null;
|
||||
}
|
||||
IContainer container = containerManager.getContainer(description, index);
|
||||
return new DefaultUniqueNameContext(description, container, getCaseInsensitivityHelper(), cancelIndicator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAffected(Collection<IResourceDescription.Delta> deltas, IResourceDescription candidate,
|
||||
IResourceDescriptions context) {
|
||||
IContainer container = containerManager.getContainer(candidate, context);
|
||||
for (IResourceDescription.Delta delta : deltas) {
|
||||
if (delta.getNew() == null) {
|
||||
if (intersects(delta.getOld(), candidate, true)) {
|
||||
return true;
|
||||
}
|
||||
} else if (container.getResourceDescription(delta.getUri()) != null) {
|
||||
if (isAffected(delta, candidate, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a context for all visible {@link IContainer containers}.
|
||||
*/
|
||||
@Singleton
|
||||
public static class VisibleContainers extends BaseGlobalContextProvider {
|
||||
|
||||
public static class Selectable extends AbstractCompoundSelectable {
|
||||
|
||||
private final List<IContainer> containers;
|
||||
|
||||
public Selectable(List<IContainer> containers) {
|
||||
this.containers = containers;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterable<? extends ISelectable> getSelectables() {
|
||||
return containers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Inject
|
||||
private IContainer.Manager containerManager;
|
||||
|
||||
@Override
|
||||
public Context tryGetContext(Resource resource, CancelIndicator cancelIndicator) {
|
||||
IResourceDescriptions index = getIndex(resource);
|
||||
if (index == null) {
|
||||
return null;
|
||||
}
|
||||
IResourceDescription description = getResourceDescription(resource);
|
||||
if (description == null) {
|
||||
return null;
|
||||
}
|
||||
List<IContainer> containers = containerManager.getVisibleContainers(description, index);
|
||||
return new DefaultUniqueNameContext(description, new Selectable(containers), getCaseInsensitivityHelper(),
|
||||
cancelIndicator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAffected(Collection<IResourceDescription.Delta> deltas, IResourceDescription candidate,
|
||||
IResourceDescriptions context) {
|
||||
List<IContainer> containers = containerManager.getVisibleContainers(candidate, context);
|
||||
for (IResourceDescription.Delta delta : deltas) {
|
||||
if (delta.getNew() == null) {
|
||||
if (intersects(delta.getOld(), candidate, true)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
containers: for (IContainer container : containers) {
|
||||
if (container.getResourceDescription(delta.getUri()) != null) {
|
||||
if (isAffected(delta, candidate, true)) {
|
||||
return true;
|
||||
}
|
||||
break containers;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A case sensitive validation context that ensures unique names among the exported names per single resource.
|
||||
*/
|
||||
@Singleton
|
||||
public static class ExportedFromResource extends BaseContextProvider {
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public Context tryGetContext(Resource resource, CancelIndicator cancelIndicator) {
|
||||
IResourceDescription description = getResourceDescription(resource);
|
||||
if (description != null) {
|
||||
return new UniqueInResourceContext(description, cancelIndicator);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final IResourceDescription resourceDescription;
|
||||
private final ISelectable validationScope;
|
||||
private final CancelIndicator cancelIndicator;
|
||||
private final ICaseInsensitivityHelper caseInsensitivityHelper;
|
||||
|
||||
public DefaultUniqueNameContext(IResourceDescription resourceDescription, ISelectable validationScope,
|
||||
ICaseInsensitivityHelper caseInsensitivityHelper, CancelIndicator cancelIndicator) {
|
||||
this.resourceDescription = resourceDescription;
|
||||
this.validationScope = validationScope;
|
||||
this.caseInsensitivityHelper = caseInsensitivityHelper;
|
||||
this.cancelIndicator = cancelIndicator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ISelectable getValidationScope(IEObjectDescription description, EClass clusterType) {
|
||||
return validationScope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCaseSensitive(EObject object, EClass clusterType) {
|
||||
return !caseInsensitivityHelper.isIgnoreCase(object.eContainmentFeature());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<IEObjectDescription> getObjectsToValidate() {
|
||||
return resourceDescription.getExportedObjects();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CancelIndicator cancelIndicator() {
|
||||
return cancelIndicator;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009 itemis AG (http://www.itemis.eu) and others.
|
||||
* Copyright (c) 2009, 2020 itemis AG (http://www.itemis.eu) and others.
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
|
@ -8,29 +8,157 @@
|
|||
*******************************************************************************/
|
||||
package org.eclipse.xtext.validation;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.eclipse.emf.ecore.EClass;
|
||||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.eclipse.emf.ecore.resource.Resource;
|
||||
import org.eclipse.xtext.resource.IEObjectDescription;
|
||||
import org.eclipse.xtext.resource.IResourceDescription;
|
||||
import org.eclipse.xtext.resource.IResourceDescriptions;
|
||||
import org.eclipse.xtext.resource.ISelectable;
|
||||
import org.eclipse.xtext.resource.IResourceDescription.Delta;
|
||||
import org.eclipse.xtext.resource.impl.IsAffectedExtension;
|
||||
import org.eclipse.xtext.scoping.ICaseInsensitivityHelper;
|
||||
import org.eclipse.xtext.util.CancelIndicator;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.inject.ImplementedBy;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
/**
|
||||
* The helper can be used to validate unique names locally or across resource boundaries.
|
||||
*
|
||||
* @see LocalUniqueNameContext
|
||||
* @author Sebastian Zarnekow - Initial contribution and API
|
||||
*/
|
||||
@ImplementedBy(NamesAreUniqueValidationHelper.class)
|
||||
public interface INamesAreUniqueValidationHelper {
|
||||
|
||||
/**
|
||||
* Create errors for objects that have the same name. Objects, that do not belong to
|
||||
* the same cluster will not get any errors.
|
||||
* @see INamesAreUniqueValidationHelper#checkUniqueNames(Iterable, CancelIndicator, ValidationMessageAcceptor)
|
||||
* Provide the context information for the unique name validation.
|
||||
*
|
||||
* Registered by default as a {@link IsAffectedExtension} as {@link Named @Named("IsAffectedExtension.UniqueNames")}.
|
||||
*
|
||||
* @since 2.22
|
||||
*/
|
||||
void checkUniqueNames(Iterable<IEObjectDescription> descriptions, ValidationMessageAcceptor acceptor);
|
||||
@Beta
|
||||
@ImplementedBy(DefaultUniqueNameContext.ExportedFromResource.class)
|
||||
interface ContextProvider extends IsAffectedExtension {
|
||||
|
||||
/**
|
||||
* Obtain the context of the unique name validation for the given resource.
|
||||
*
|
||||
* May return null.
|
||||
*/
|
||||
Context tryGetContext(Resource resource, CancelIndicator cancelIndicator);
|
||||
|
||||
@Override
|
||||
default boolean isAffected(Collection<Delta> deltas, IResourceDescription candidate,
|
||||
IResourceDescriptions context) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create errors for objects that have the same name. Objects, that do not belong to
|
||||
* the same cluster will not get any errors. The cancel indicator may be used to interrupt
|
||||
* the validation.
|
||||
* <p>
|
||||
* Provides context information to the validation that allows to introspect the scope in which the validation for
|
||||
* unique names is supposed to happen.
|
||||
* </p>
|
||||
* <p>
|
||||
* The term <code>clusterType</code> is used to denote a {@link EClass (super-)type} of the current object under
|
||||
* validation. When checking the name for uniqueness, all objects that are instances of the given cluster type are
|
||||
* considered. This can be used to define different namespaces. For example, a language that has the concepts of
|
||||
* <code>Fields</code>, <code>Procedures</code> and <code>Functions</code> may provide 2 different cluster types.
|
||||
* <ol>
|
||||
* <li>All fields must have a unique name.</li>
|
||||
* <li>The executable procedure and functions use the same namespace and may not have duplicate names.</li>
|
||||
* </ol>
|
||||
* This would allow a field to use the same name as a procedure or function but raise an issue if a procedure has the same
|
||||
* name as a function.
|
||||
* </p>
|
||||
*
|
||||
* @since 2.22
|
||||
*/
|
||||
@Beta
|
||||
interface Context {
|
||||
/**
|
||||
* <p>
|
||||
* Returns an {@link ISelectable} that can be queried for elements with a given name to find duplicates.
|
||||
* </p>
|
||||
* <p>
|
||||
* Depending on the type of objects that have to have unique names, different selectables may be returned. Some
|
||||
* objects must be unique per project, others must be globally unique or only unique per file. This API allows
|
||||
* to fine tune the scope of the validation.
|
||||
* </p>
|
||||
*
|
||||
* @param description
|
||||
* the description of the validated object.
|
||||
* @param clusterType
|
||||
* the root type of the validated type hierarchy.
|
||||
* @return the validation scope.
|
||||
*/
|
||||
ISelectable getValidationScope(IEObjectDescription description, EClass clusterType);
|
||||
|
||||
/**
|
||||
* Returns the objects that should be checked for uniqueness in the context of their {@link #getValidationScope(EClass)}.
|
||||
*/
|
||||
Iterable<IEObjectDescription> getObjectsToValidate();
|
||||
|
||||
/**
|
||||
* The cancel-indicator that shall be used.
|
||||
*/
|
||||
CancelIndicator cancelIndicator();
|
||||
|
||||
/**
|
||||
* Answers whether the names in the given cluster must be treated in a case
|
||||
* sensistive or insensitive manner.
|
||||
*
|
||||
* @see ICaseInsensitivityHelper
|
||||
*/
|
||||
default boolean isCaseSensitive(EObject object, EClass clusterType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Context implementations that do know from their construction if they will or won't
|
||||
* contain any duplicates, can override {@link #isUnique()} to provide a more efficient means
|
||||
* to validate all {@link #getObjectsToValidate() candidates} at once.
|
||||
*/
|
||||
default boolean isUnique() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create errors for objects that have the same name. Objects that do not belong to
|
||||
* the same cluster will not get any errors.
|
||||
* @see INamesAreUniqueValidationHelper#checkUniqueNames(Iterable, CancelIndicator, ValidationMessageAcceptor)
|
||||
*
|
||||
* @deprecated Implementations should adhere to the context provided via {@link #checkUniqueNames(Iterable, Context, ValidationMessageAcceptor)}
|
||||
*/
|
||||
@Deprecated
|
||||
default void checkUniqueNames(Iterable<IEObjectDescription> descriptions, ValidationMessageAcceptor acceptor) {
|
||||
checkUniqueNames(descriptions, (CancelIndicator) null, acceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create errors for objects that have the same name. Objects that do not belong to
|
||||
* the same cluster will not get any errors. The cancel indicator may be used to abort
|
||||
* the validation.
|
||||
*
|
||||
* @deprecated Implementations should adhere to the context provided via {@link #checkUniqueNames(Iterable, Context, ValidationMessageAcceptor)}
|
||||
*/
|
||||
@Deprecated
|
||||
void checkUniqueNames(Iterable<IEObjectDescription> descriptions, CancelIndicator cancelIndicator, ValidationMessageAcceptor acceptor);
|
||||
|
||||
/**
|
||||
* Create errors for objects that have the same name according to the given context.
|
||||
*
|
||||
* @since 2.22
|
||||
*/
|
||||
default void checkUniqueNames(Context context, ValidationMessageAcceptor acceptor) {
|
||||
checkUniqueNames(context.getObjectsToValidate(), context.cancelIndicator(), acceptor);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2020 Sebastian Zarnekow and others.
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.validation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.emf.ecore.EClass;
|
||||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.eclipse.emf.ecore.EStructuralFeature;
|
||||
import org.eclipse.emf.ecore.EcorePackage;
|
||||
import org.eclipse.xtext.EcoreUtil2;
|
||||
import org.eclipse.xtext.naming.QualifiedName;
|
||||
import org.eclipse.xtext.resource.EObjectDescription;
|
||||
import org.eclipse.xtext.resource.IEObjectDescription;
|
||||
import org.eclipse.xtext.resource.ISelectable;
|
||||
import org.eclipse.xtext.util.CancelIndicator;
|
||||
import org.eclipse.xtext.validation.INamesAreUniqueValidationHelper.Context;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
|
||||
/**
|
||||
* <p>A case-sensitive, local validation context to check unique names in the contents of
|
||||
* a container {@link EObject} or within a list.
|
||||
* </p>
|
||||
* Sample usage in a validator:
|
||||
*
|
||||
* <pre>
|
||||
* class MyDslValidator extends AbstractDeclarativeValidator {
|
||||
* @Inject
|
||||
* private INamesAreUniqueValidationHelper helper;
|
||||
*
|
||||
* @Check
|
||||
* public void checkUniqueNames(Model model) {
|
||||
* helper.checkUniqueNames(new LocalUniqueNameContext(model, getCancelIndicator()), this);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @see INamesAreUniqueValidationHelper#checkUniqueNames(Context, ValidationMessageAcceptor)
|
||||
*
|
||||
* @since 2.22
|
||||
* @author Sebastian Zarnekow - Initial contribution and API
|
||||
*/
|
||||
@Beta
|
||||
public class LocalUniqueNameContext implements INamesAreUniqueValidationHelper.Context, ISelectable {
|
||||
|
||||
private final List<IEObjectDescription> objectsToValidate;
|
||||
private final Map<String, Object> index;
|
||||
private final CancelIndicator ci;
|
||||
|
||||
public LocalUniqueNameContext(EObject container, Function<EObject, String> nameFunction, CancelIndicator ci) {
|
||||
this(container, false, nameFunction, ci);
|
||||
}
|
||||
|
||||
public LocalUniqueNameContext(EObject container, CancelIndicator ci) {
|
||||
this(container, LocalUniqueNameContext::tryGetName, ci);
|
||||
}
|
||||
|
||||
public LocalUniqueNameContext(EObject container, boolean deep, CancelIndicator ci) {
|
||||
this(container, deep, LocalUniqueNameContext::tryGetName, ci);
|
||||
}
|
||||
|
||||
public LocalUniqueNameContext(EObject container, boolean deep, Function<EObject, String> nameFunction, CancelIndicator ci) {
|
||||
this(deep ? () -> container.eAllContents() : container.eContents(), nameFunction, ci);
|
||||
}
|
||||
|
||||
public <T extends EObject> LocalUniqueNameContext(Iterable<T> objects, Function<T, String> nameFunction, CancelIndicator ci) {
|
||||
Map<String, Object> index = new HashMap<>();
|
||||
List<IEObjectDescription> objectsToValidate = new ArrayList<>();
|
||||
for (T t : objects) {
|
||||
String name = nameFunction.apply(t);
|
||||
if (name != null) {
|
||||
IEObjectDescription description = EObjectDescription.create(name, t);
|
||||
objectsToValidate.add(description);
|
||||
index.merge(name, description, (p, n)->{
|
||||
if (p instanceof List<?>) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Object> list = (List<Object>) p;
|
||||
list.add(n);
|
||||
return list;
|
||||
}
|
||||
List<Object> list = new ArrayList<>();
|
||||
list.add(p);
|
||||
list.add(n);
|
||||
return list;
|
||||
});
|
||||
}
|
||||
}
|
||||
this.objectsToValidate = objectsToValidate;
|
||||
this.index = index;
|
||||
this.ci = ci;
|
||||
}
|
||||
|
||||
public LocalUniqueNameContext(List<? extends EObject> objects, CancelIndicator ci) {
|
||||
this(objects, LocalUniqueNameContext::tryGetName, ci);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnique() {
|
||||
return objectsToValidate.size() == index.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isCaseSensitive(EObject candidate, EClass clusterType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ISelectable getValidationScope(IEObjectDescription description, EClass clusterType) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IEObjectDescription> getObjectsToValidate() {
|
||||
return objectsToValidate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CancelIndicator cancelIndicator() {
|
||||
return ci;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<IEObjectDescription> getExportedObjects() {
|
||||
return getObjectsToValidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return index.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<IEObjectDescription> getExportedObjects(EClass type, QualifiedName name, boolean ignoreCase) {
|
||||
Preconditions.checkArgument(!ignoreCase);
|
||||
Object result = index.get(name.getFirstSegment());
|
||||
if (result == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (result instanceof IEObjectDescription) {
|
||||
if (EcoreUtil2.isAssignableFrom(type, ((IEObjectDescription) result).getEClass())) {
|
||||
return Collections.singletonList((IEObjectDescription)result);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
List<IEObjectDescription> casted = (List<IEObjectDescription>) result;
|
||||
if (type == EcorePackage.Literals.EOBJECT) {
|
||||
return casted;
|
||||
}
|
||||
return FluentIterable.from(casted).filter(it -> EcoreUtil2.isAssignableFrom(type, it.getEClass()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<IEObjectDescription> getExportedObjectsByObject(EObject object) {
|
||||
return FluentIterable.from(objectsToValidate).filter(it -> object == it.getEObjectOrProxy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<IEObjectDescription> getExportedObjectsByType(EClass type) {
|
||||
return FluentIterable.from(objectsToValidate).filter(it -> EcoreUtil2.isAssignableFrom(type, it.getEClass()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the value of an EAttribute 'name', if present. Returns null if
|
||||
* <ul>
|
||||
* <li>the given object is null,<li>
|
||||
* <li>does not have an attribute 'name' of type String, or</li>
|
||||
* <li>the value of the attribute 'name' itself is null.</li>
|
||||
* </ul>
|
||||
*/
|
||||
private static String tryGetName(EObject obj) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
EStructuralFeature name = obj.eClass().getEStructuralFeature("name");
|
||||
if (name != null && name.getEType() == EcorePackage.Literals.ESTRING) {
|
||||
return (String) obj.eGet(name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009 itemis AG (http://www.itemis.eu) and others.
|
||||
* Copyright (c) 2009, 2020 itemis AG (http://www.itemis.eu) and others.
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
|
@ -8,11 +8,8 @@
|
|||
*******************************************************************************/
|
||||
package org.eclipse.xtext.validation;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.eclipse.xtext.util.CancelIndicator;
|
||||
import org.eclipse.xtext.util.SimpleAttributeResolver;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
@ -24,62 +21,152 @@ import org.eclipse.emf.ecore.EStructuralFeature;
|
|||
import org.eclipse.emf.ecore.EcorePackage;
|
||||
import org.eclipse.xtext.naming.QualifiedName;
|
||||
import org.eclipse.xtext.resource.IEObjectDescription;
|
||||
import org.eclipse.xtext.resource.ISelectable;
|
||||
import org.eclipse.xtext.service.OperationCanceledManager;
|
||||
import org.eclipse.xtext.util.CancelIndicator;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
/**
|
||||
* @author Sebastian Zarnekow - Initial contribution and API
|
||||
*/
|
||||
@Singleton
|
||||
public class NamesAreUniqueValidationHelper implements INamesAreUniqueValidationHelper {
|
||||
|
||||
private ImmutableSet<EClass> clusterTypes = getClusterTypes();
|
||||
|
||||
@Inject
|
||||
|
||||
@Inject
|
||||
private OperationCanceledManager operationCanceledManager = new OperationCanceledManager();
|
||||
|
||||
|
||||
/**
|
||||
* <p>Initialize the set of clustering types. A type is considered to be clustering
|
||||
* if any instance of that type has to have a unique name when
|
||||
* it is transformed to an {@link org.eclipse.xtext.resource.IEObjectDescription}.
|
||||
* Instances that do not belong to the same cluster may have the same exported name.</p>
|
||||
* <p>A clustering type will often be some kind of root type in a type hierarchy.</p>
|
||||
* <p>
|
||||
* Initialize the set of clustering types. A type is considered to be clustering if any instance of that type has to
|
||||
* have a unique name when it is transformed to an {@link org.eclipse.xtext.resource.IEObjectDescription}. Instances
|
||||
* that do not belong to the same cluster may have the same exported name.
|
||||
* </p>
|
||||
* <p>
|
||||
* A clustering type will often be some kind of root type in a type hierarchy.
|
||||
* </p>
|
||||
*/
|
||||
protected ImmutableSet<EClass> getClusterTypes() {
|
||||
return ImmutableSet.of(EcorePackage.Literals.EOBJECT);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated Since Xtext 2.22 implementations should adhere to the context provided via
|
||||
* {@link #checkUniqueNames(Iterable, Context, ValidationMessageAcceptor)}.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public void checkUniqueNames(Iterable<IEObjectDescription> descriptions,
|
||||
ValidationMessageAcceptor acceptor) {
|
||||
checkUniqueNames(descriptions, null, acceptor);
|
||||
public void checkUniqueNames(Iterable<IEObjectDescription> descriptions, ValidationMessageAcceptor acceptor) {
|
||||
checkUniqueNames(descriptions, (CancelIndicator) null, acceptor);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* {@inheritDoc}
|
||||
* </p>
|
||||
* The cancel indicator will be queried everytime a description has been processed.
|
||||
* It should provide a fast answer about its canceled state.
|
||||
* The cancel indicator will be queried everytime a description has been processed. It should provide a fast answer
|
||||
* about its canceled state.
|
||||
*
|
||||
* @deprecated Since Xtext 2.22 implementations should adhere to the context provided via
|
||||
* {@link #checkUniqueNames(Iterable, Context, ValidationMessageAcceptor)}.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public void checkUniqueNames(Iterable<IEObjectDescription> descriptions,
|
||||
CancelIndicator cancelIndicator,
|
||||
public void checkUniqueNames(Iterable<IEObjectDescription> descriptions, CancelIndicator cancelIndicator,
|
||||
ValidationMessageAcceptor acceptor) {
|
||||
Iterator<IEObjectDescription> iter = descriptions.iterator();
|
||||
if (!iter.hasNext())
|
||||
return;
|
||||
Map<EClass, Map<QualifiedName, IEObjectDescription>> clusterToNames = Maps.newHashMap();
|
||||
while(iter.hasNext()) {
|
||||
while (iter.hasNext()) {
|
||||
IEObjectDescription description = iter.next();
|
||||
checkDescriptionForDuplicatedName(description, clusterToNames, acceptor);
|
||||
operationCanceledManager.checkCanceled(cancelIndicator);
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkDescriptionForDuplicatedName(
|
||||
IEObjectDescription description,
|
||||
|
||||
/**
|
||||
* @since 2.22
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void checkUniqueNames(Context context, ValidationMessageAcceptor acceptor) {
|
||||
if (!NamesAreUniqueValidationHelper.class.equals(getClass()) && context instanceof UniqueInResourceContext) {
|
||||
checkUniqueNames(context.getObjectsToValidate(), context.cancelIndicator(), acceptor);
|
||||
} else {
|
||||
doCheckUniqueNames(context, acceptor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.22
|
||||
*/
|
||||
protected void doCheckUniqueNames(Context context, ValidationMessageAcceptor acceptor) {
|
||||
CancelIndicator cancelIndicator = context.cancelIndicator();
|
||||
for (IEObjectDescription description : context.getObjectsToValidate()) {
|
||||
operationCanceledManager.checkCanceled(cancelIndicator);
|
||||
doCheckUniqueIn(description, context, acceptor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.22
|
||||
*/
|
||||
protected void doCheckUniqueIn(IEObjectDescription description, Context context,
|
||||
ValidationMessageAcceptor acceptor) {
|
||||
EObject object = description.getEObjectOrProxy();
|
||||
Preconditions.checkArgument(!object.eIsProxy());
|
||||
|
||||
EClass clusterType = getClusterType(description);
|
||||
if (clusterType == null) {
|
||||
return;
|
||||
}
|
||||
ISelectable validationScope = context.getValidationScope(description, clusterType);
|
||||
if (validationScope.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
boolean caseSensitive = context.isCaseSensitive(object, clusterType);
|
||||
Iterable<IEObjectDescription> sameNames = validationScope.getExportedObjects(clusterType, description.getName(),
|
||||
!caseSensitive);
|
||||
if (sameNames instanceof Collection<?>) {
|
||||
if (((Collection<?>) sameNames).size() <= 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (IEObjectDescription candidate : sameNames) {
|
||||
EObject otherObject = candidate.getEObjectOrProxy();
|
||||
if (object != otherObject && getAssociatedClusterType(candidate.getEClass()) == clusterType
|
||||
&& !otherObject.eIsProxy() || !candidate.getEObjectURI().equals(description.getEObjectURI())) {
|
||||
if (isDuplicate(description, candidate)) {
|
||||
createDuplicateNameError(description, clusterType, acceptor);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with two descriptions that have the same name. May be specialized to consider further information from the
|
||||
* user data.
|
||||
*
|
||||
* @since 2.22
|
||||
*/
|
||||
protected boolean isDuplicate(IEObjectDescription description, IEObjectDescription candidate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use
|
||||
* {@link #doCheckUniqueIn(IEObjectDescription, org.eclipse.xtext.validation.INamesAreUniqueValidationHelper.Context, ValidationMessageAcceptor)}
|
||||
* instead.
|
||||
*/
|
||||
@Deprecated
|
||||
protected void checkDescriptionForDuplicatedName(IEObjectDescription description,
|
||||
Map<EClass, Map<QualifiedName, IEObjectDescription>> clusterTypeToName,
|
||||
ValidationMessageAcceptor acceptor) {
|
||||
EObject object = description.getEObjectOrProxy();
|
||||
|
@ -104,20 +191,17 @@ public class NamesAreUniqueValidationHelper implements INamesAreUniqueValidation
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void createDuplicateNameError(IEObjectDescription description, EClass clusterType, ValidationMessageAcceptor acceptor) {
|
||||
|
||||
protected void createDuplicateNameError(IEObjectDescription description, EClass clusterType,
|
||||
ValidationMessageAcceptor acceptor) {
|
||||
EObject object = description.getEObjectOrProxy();
|
||||
EStructuralFeature feature = getNameFeature(object);
|
||||
acceptor.acceptError(
|
||||
getDuplicateNameErrorMessage(description, clusterType, feature),
|
||||
object,
|
||||
feature,
|
||||
ValidationMessageAcceptor.INSIGNIFICANT_INDEX,
|
||||
getErrorCode());
|
||||
acceptor.acceptError(getDuplicateNameErrorMessage(description, clusterType, feature), object, feature,
|
||||
ValidationMessageAcceptor.INSIGNIFICANT_INDEX, getErrorCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>null</code>. Clients may override if they desire to attach an error code to the created errors.
|
||||
* Returns <code>null</code>. Clients may override if they desire to attach an error code to the created errors.
|
||||
*/
|
||||
protected String getErrorCode() {
|
||||
// TODO use built-in codes to allow generic quickfixes
|
||||
|
@ -125,22 +209,24 @@ public class NamesAreUniqueValidationHelper implements INamesAreUniqueValidation
|
|||
}
|
||||
|
||||
/**
|
||||
* Build the error message for duplicated names. The default implementation will provider error messages
|
||||
* of this form:
|
||||
* Build the error message for duplicated names. The default implementation will provide error messages of this
|
||||
* form:
|
||||
* <ul>
|
||||
* <li>Duplicate Entity 'Sample'</li>
|
||||
* <li>Duplicate Property 'Sample' in Entity 'EntityName'</li>
|
||||
* </ul>
|
||||
* If the container information will be helpful to locate the error or to understand the error
|
||||
* it will be used, otherwise only the simple format will be build. Clients may override different
|
||||
* methods that influence the error message.
|
||||
* If the container information will be helpful to locate the error or to understand the error it will be used,
|
||||
* otherwise only the simple format will be built. Clients may override different methods that influence the error
|
||||
* message.
|
||||
*
|
||||
* @see #getNameFeature(EObject)
|
||||
* @see #getTypeLabel(EClass)
|
||||
* @see #getContainerForErrorMessage(EObject)
|
||||
* @see #isContainerInformationHelpful(IEObjectDescription, String)
|
||||
* @see #isContainerInformationHelpful(IEObjectDescription, EObject, String, EStructuralFeature)
|
||||
*/
|
||||
public String getDuplicateNameErrorMessage(IEObjectDescription description, EClass clusterType, EStructuralFeature feature) {
|
||||
public String getDuplicateNameErrorMessage(IEObjectDescription description, EClass clusterType,
|
||||
EStructuralFeature feature) {
|
||||
EObject object = description.getEObjectOrProxy();
|
||||
String shortName = String.valueOf(feature != null ? object.eGet(feature) : "<unnamed>");
|
||||
StringBuilder result = new StringBuilder(64);
|
||||
|
@ -166,12 +252,12 @@ public class NamesAreUniqueValidationHelper implements INamesAreUniqueValidation
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
protected boolean isContainerInformationHelpful(IEObjectDescription description, EObject container,
|
||||
protected boolean isContainerInformationHelpful(IEObjectDescription description, EObject container,
|
||||
String containerTypeLabel, EStructuralFeature containerNameFeature) {
|
||||
return containerTypeLabel != null && containerNameFeature != null;
|
||||
}
|
||||
|
@ -188,16 +274,28 @@ public class NamesAreUniqueValidationHelper implements INamesAreUniqueValidation
|
|||
String name = eClass.getName();
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
protected EStructuralFeature getNameFeature(EObject object) {
|
||||
return SimpleAttributeResolver.NAME_RESOLVER.getAttribute(object);
|
||||
return object.eClass().getEStructuralFeature("name");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the type that describes the set of instances that should have unique names.
|
||||
* The default information will return the topmost type or the first super type that is contained
|
||||
* in the set of cluster types ({@link NamesAreUniqueValidationHelper#getClusterTypes()}).
|
||||
* Only the first super type will be taken into account when walking the hierarchy.
|
||||
* @see #getAssociatedClusterType(EClass)
|
||||
* @since 2.22
|
||||
* @return the cluster type or <code>null</code> if the given description does not participate in the unique name
|
||||
* validation.
|
||||
*/
|
||||
protected EClass getClusterType(IEObjectDescription description) {
|
||||
return getAssociatedClusterType(description.getEClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type that describes the set of instances that should have unique names. The default information will
|
||||
* return the topmost type or the first super type that is contained in the set of cluster types
|
||||
* ({@link NamesAreUniqueValidationHelper#getClusterTypes()}). Only the first super type will be taken into account
|
||||
* when walking the hierarchy.
|
||||
*
|
||||
* Return <code>null</code> if objects of the given type are not subject to validation.
|
||||
*/
|
||||
protected EClass getAssociatedClusterType(EClass eClass) {
|
||||
if (clusterTypes.contains(eClass))
|
||||
|
@ -207,5 +305,5 @@ public class NamesAreUniqueValidationHelper implements INamesAreUniqueValidation
|
|||
return eClass;
|
||||
return getAssociatedClusterType(superTypes.get(0));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009 itemis AG (http://www.itemis.eu) and others.
|
||||
* Copyright (c) 2009, 2020 itemis AG (http://www.itemis.eu) and others.
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
|
@ -12,11 +12,13 @@ import java.util.Map;
|
|||
|
||||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.eclipse.emf.ecore.resource.Resource;
|
||||
import org.eclipse.xtext.resource.IEObjectDescription;
|
||||
import org.eclipse.xtext.resource.IResourceDescription;
|
||||
import org.eclipse.xtext.resource.IResourceServiceProvider;
|
||||
import org.eclipse.xtext.util.CancelIndicator;
|
||||
import org.eclipse.xtext.validation.DefaultUniqueNameContext.BaseContextProvider;
|
||||
import org.eclipse.xtext.validation.INamesAreUniqueValidationHelper.Context;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
/**
|
||||
|
@ -34,11 +36,18 @@ import com.google.inject.Inject;
|
|||
*/
|
||||
public class NamesAreUniqueValidator extends AbstractDeclarativeValidator {
|
||||
|
||||
/**
|
||||
* @deprecated locally unused since 2.22
|
||||
*/
|
||||
@Deprecated
|
||||
@Inject
|
||||
private IResourceServiceProvider.Registry resourceServiceProviderRegistry = IResourceServiceProvider.Registry.INSTANCE;
|
||||
|
||||
@Inject
|
||||
private INamesAreUniqueValidationHelper helper;
|
||||
|
||||
@Inject
|
||||
private INamesAreUniqueValidationHelper.ContextProvider contextProvider = new DefaultUniqueNameContext.ExportedFromResource();
|
||||
|
||||
@Override
|
||||
public void register(EValidatorRegistrar registrar) {
|
||||
|
@ -47,6 +56,10 @@ public class NamesAreUniqueValidator extends AbstractDeclarativeValidator {
|
|||
|
||||
@Check
|
||||
public void checkUniqueNamesInResourceOf(EObject eObject) {
|
||||
if (eObject.eContainer() != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Object, Object> context = getContext();
|
||||
Resource resource = eObject.eResource();
|
||||
if (resource==null)
|
||||
|
@ -62,18 +75,18 @@ public class NamesAreUniqueValidator extends AbstractDeclarativeValidator {
|
|||
}
|
||||
|
||||
public void doCheckUniqueNames(Resource resource, CancelIndicator cancelIndicator) {
|
||||
final IResourceServiceProvider resourceServiceProvider = resourceServiceProviderRegistry.getResourceServiceProvider(resource.getURI());
|
||||
if (resourceServiceProvider==null)
|
||||
return;
|
||||
IResourceDescription.Manager manager = resourceServiceProvider.getResourceDescriptionManager();
|
||||
if (manager != null) {
|
||||
IResourceDescription description = manager.getResourceDescription(resource);
|
||||
if (description != null) {
|
||||
Iterable<IEObjectDescription> descriptions = description.getExportedObjects();
|
||||
helper.checkUniqueNames(descriptions, cancelIndicator, this);
|
||||
}
|
||||
Context validationContext = getValidationContext(resource, cancelIndicator);
|
||||
if (validationContext != null) {
|
||||
helper.checkUniqueNames(validationContext, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.22
|
||||
*/
|
||||
protected INamesAreUniqueValidationHelper.Context getValidationContext(Resource resource, CancelIndicator cancelIndicator) {
|
||||
return contextProvider.tryGetContext(resource, cancelIndicator);
|
||||
}
|
||||
|
||||
public void setHelper(INamesAreUniqueValidationHelper helper) {
|
||||
this.helper = helper;
|
||||
|
@ -83,12 +96,37 @@ public class NamesAreUniqueValidator extends AbstractDeclarativeValidator {
|
|||
return helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated locally unused since 2.22
|
||||
*/
|
||||
@Deprecated
|
||||
public void setResourceServiceProviderRegistry(IResourceServiceProvider.Registry resourceDescriptionManagerRegistry) {
|
||||
this.resourceServiceProviderRegistry = resourceDescriptionManagerRegistry;
|
||||
if (contextProvider instanceof BaseContextProvider) {
|
||||
((BaseContextProvider) contextProvider).setResourceServiceProviderRegistry(resourceDescriptionManagerRegistry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated locally unused since 2.22
|
||||
*/
|
||||
@Deprecated
|
||||
public IResourceServiceProvider.Registry getResourceServiceProviderRegistry() {
|
||||
return resourceServiceProviderRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.22
|
||||
*/
|
||||
public INamesAreUniqueValidationHelper.ContextProvider getContextProvider() {
|
||||
return contextProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.22
|
||||
*/
|
||||
public void setContextProvider(INamesAreUniqueValidationHelper.ContextProvider contextProvider) {
|
||||
this.contextProvider = Preconditions.checkNotNull(contextProvider);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2020 Sebastian Zarnekow and others.
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.validation;
|
||||
|
||||
import org.eclipse.xtext.resource.IResourceDescription;
|
||||
import org.eclipse.xtext.scoping.impl.CaseInsensitivityHelper;
|
||||
import org.eclipse.xtext.util.CancelIndicator;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Legacy case-sensitive, unique name validation context that ensures the exported names to be unique per resource.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is used to detect specializations that require fallback to the behavior prior to Xtext 2.22.
|
||||
* </p>
|
||||
*
|
||||
* @author Sebastian Zarnekow - Initial contribution and API
|
||||
* @deprecated Can be replaced by direct usage of the {@link DefaultUniqueNameContext}.
|
||||
* @since 2.22
|
||||
*/
|
||||
@Deprecated
|
||||
public class UniqueInResourceContext extends DefaultUniqueNameContext {
|
||||
|
||||
public UniqueInResourceContext(IResourceDescription resourceDescription, CancelIndicator ci) {
|
||||
super(resourceDescription, resourceDescription, new CaseInsensitivityHelper(), ci);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue