diff --git a/com.avaloq.tools.ddk.xtext.test/src/com/avaloq/tools/ddk/xtext/resource/AbstractSelectorFragmentProviderTest.java b/com.avaloq.tools.ddk.xtext.test/src/com/avaloq/tools/ddk/xtext/resource/AbstractSelectorFragmentProviderTest.java index 1b8f16be9..5cbc25a9c 100644 --- a/com.avaloq.tools.ddk.xtext.test/src/com/avaloq/tools/ddk/xtext/resource/AbstractSelectorFragmentProviderTest.java +++ b/com.avaloq.tools.ddk.xtext.test/src/com/avaloq/tools/ddk/xtext/resource/AbstractSelectorFragmentProviderTest.java @@ -13,9 +13,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; +import java.util.List; + +import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EcoreFactory; +import org.eclipse.emf.ecore.EcorePackage; +import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.AbstractElement; import org.eclipse.xtext.AbstractRule; @@ -42,6 +49,9 @@ public class AbstractSelectorFragmentProviderTest { // CHECKSTYLE:ON + private static final String NAME_FEATURE = "name"; + private static final String CHILDREN_FEATURE = "children"; + @Inject private ParseHelper parseHelper; @@ -78,6 +88,85 @@ public void testEscapedSelectorValue() { assertFragmentMatchesAndResolves(grammar.eResource(), "/0/5(0='Foo')#0/2(3='foo.bar#')", grammar.getRules().get(0).getAlternatives()); } + @Test + public void testCaseSensitiveSelectorValue() { + EObject container = createContainer(true, "foo", "FOO"); + for (EObject child : children(container)) { + assertSame(child, resolve(container, segment(child))); + } + } + + @Test + public void testCaseInsensitiveSelectorValue() { + EObject container = createContainer(false, "foo"); + EObject child = children(container).get(0); + assertSame(child, resolve(container, segment(child).replace("'foo'", "'FOO'"))); + } + + @Test + public void testCaseInsensitiveSiblingsDifferingInCase() { + EObject container = createContainer(false, "foo", "FOO"); + for (EObject child : children(container)) { + assertSame(child, resolve(container, segment(child))); + } + } + + /** + * Creates a container whose children are named by the given names, using a dynamic model whose child class is case sensitive if requested. + */ + private static EObject createContainer(final boolean caseSensitive, final String... names) { + EcoreFactory factory = EcoreFactory.eINSTANCE; + EPackage ePackage = factory.createEPackage(); + ePackage.setName("test"); + ePackage.setNsURI("http://www.foo.com/test/" + caseSensitive); + EClass childClass = factory.createEClass(); + childClass.setName("Child"); + if (caseSensitive) { + // the provider only checks the name of the super type + EClass marker = factory.createEClass(); + marker.setName("ICaseSensitiveNamedElement"); + marker.setInterface(true); + marker.setAbstract(true); + ePackage.getEClassifiers().add(marker); + childClass.getESuperTypes().add(marker); + } + EAttribute name = factory.createEAttribute(); + name.setName(NAME_FEATURE); + name.setEType(EcorePackage.Literals.ESTRING); + childClass.getEStructuralFeatures().add(name); + EClass containerClass = factory.createEClass(); + containerClass.setName("Container"); + EReference childrenReference = factory.createEReference(); + childrenReference.setName(CHILDREN_FEATURE); + childrenReference.setEType(childClass); + childrenReference.setContainment(true); + childrenReference.setUpperBound(-1); + containerClass.getEStructuralFeatures().add(childrenReference); + ePackage.getEClassifiers().add(childClass); + ePackage.getEClassifiers().add(containerClass); + + EObject container = EcoreUtil.create(containerClass); + for (String childName : names) { + EObject child = EcoreUtil.create(childClass); + child.eSet(name, childName); + children(container).add(child); + } + return container; + } + + @SuppressWarnings("unchecked") + private static List children(final EObject container) { + return (List) container.eGet(container.eClass().getEStructuralFeature(CHILDREN_FEATURE)); + } + + private static String segment(final EObject child) { + return new TestSelectorFragmentProvider().nameSegment(child); + } + + private static EObject resolve(final EObject container, final String segment) { + return new TestSelectorFragmentProvider().getEObjectFromSegment(container, segment); + } + private void assertFragmentMatchesAndResolves(final Resource res, final String expectedFragment, final EObject obj) { String fragment = res.getURIFragment(obj); assertEquals(expectedFragment, fragment); @@ -121,6 +210,13 @@ public boolean appendFragmentSegment(final EObject object, final StringBuilder b return super.appendFragmentSegment(object, builder); } + // the test runs in another bundle, so protected methods are only reachable from this subclass + String nameSegment(final EObject obj) { + StringBuilder builder = new StringBuilder(); + computeSelectorFragmentSegment(obj, obj.eClass().getEStructuralFeature(NAME_FEATURE), false, builder); + return builder.toString(); + } + private boolean appendFragmentSegment(final Grammar obj, final StringBuilder builder) { return computeSelectorFragmentSegment(obj, XtextPackage.Literals.GRAMMAR__NAME, true, builder); } diff --git a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/AbstractSelectorFragmentProvider.java b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/AbstractSelectorFragmentProvider.java index 658c431b8..7dc34966c 100644 --- a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/AbstractSelectorFragmentProvider.java +++ b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/AbstractSelectorFragmentProvider.java @@ -88,8 +88,9 @@ protected boolean computeSelectorFragmentSegment(final EObject obj, final EStruc int selectorIndex = 0; final int objectIndex = containmentList.indexOf(obj); for (int i = 0; i < objectIndex; i++) { - final Object value = containmentList.get(i).eGet(selectorFeature); - if ((selectorValue == null && value == null) || (selectorValue != null && selectorValue.equals(value))) { + final EObject sibling = containmentList.get(i); + final Object value = sibling.eGet(selectorFeature); + if (selectorValue == null ? value == null : value != null && matches(sibling, value.toString(), selectorValue.toString())) { selectorIndex++; } } @@ -133,6 +134,21 @@ private static Boolean isCaseSensitive(final EClass eClass) { return caseSensitive; } + /** + * Returns whether the given selector value text matches the expected value, ignoring case unless the object's class is case sensitive. + * + * @param object + * object whose selector value is matched + * @param text + * text of the object's selector value + * @param expected + * expected selector value, may be {@code null} + * @return {@code true} if the values match + */ + private static boolean matches(final EObject object, final String text, final String expected) { + return text.equalsIgnoreCase(expected) && (!isCaseSensitive(object.eClass()) || text.equals(expected)); + } + @Override @SuppressWarnings({"unchecked", "PMD.NPathComplexity"}) public EObject getEObjectFromSegment(final EObject container, final String segment) { @@ -165,8 +181,7 @@ public EObject getEObjectFromSegment(final EObject container, final String segme return object; } } else { - String text = value.toString(); - if (text.equalsIgnoreCase(matchedValue) && (!isCaseSensitive(object.eClass()) || text.equals(matchedValue)) && index++ == matchedIndex) { + if (matches(object, value.toString(), matchedValue) && index++ == matchedIndex) { return object; } }