+ */
+
+package org.omg.kerml.xtext.resource;
+
+import org.apache.log4j.Logger;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.xtext.naming.IQualifiedNameConverter;
+import org.eclipse.xtext.naming.QualifiedName;
+import org.eclipse.xtext.resource.EObjectDescription;
+import org.eclipse.xtext.resource.IEObjectDescription;
+import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy;
+import org.eclipse.xtext.util.IAcceptor;
+import org.omg.sysml.lang.sysml.Element;
+import org.omg.sysml.lang.sysml.Namespace;
+import org.omg.sysml.util.ElementUtil;
+
+import com.google.inject.Inject;
+
+/**
+ * A resource description strategy that exports the declaredShortName of a root
+ * Element, in addition to the declaredName-based qualified name exported by
+ * {@link org.omg.kerml.xtext.naming.KerMLQualifiedNameProvider}.
+ *
+ * Local (same-resource) name resolution matches both the memberName and the
+ * memberShortName of a Membership. Without the additional exported names, the first
+ * segment of a cross-resource qualified name could only be resolved by declaredName,
+ * so a reference such as P::Foo, where P is the short name of a root
+ * Package in another resource, would not resolve.
+ *
+ *
Short names are exported under the qualifier {@link #SHORT_NAME_QUALIFIER}, which keeps them
+ * in a separate key space from declaredName-based qualified names. This matters for
+ * two reasons. First, a short name can then never displace the declaredName of
+ * another root Element in the global index — which would otherwise depend on the order in which
+ * resources happen to be indexed, and would break references to, for example, a standard library
+ * package whose name a user had chosen as a short name. Second, the qualified names exported for
+ * short names have two segments, so they are excluded from the single-segment global scope used for
+ * enumeration, and do not appear as duplicate proposals. A short name is resolved explicitly by
+ * {@link org.omg.kerml.xtext.scoping.KerMLGlobalScope}, only after a declaredName
+ * lookup has failed.
+ *
+ *
Only root Elements are exported this way, because those are the only ones used to resolve the
+ * first segment of a cross-resource qualified name. Short names of nested Elements continue to be
+ * handled by {@link org.omg.kerml.xtext.scoping.KerMLScope}. Note that this includes root Elements
+ * that are not Packages, and root Elements that have a declaredShortName but no
+ * declaredName.
+ */
+public class KerMLResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy {
+
+ /**
+ * The first segment of the qualified name under which the declaredShortName of a
+ * root Element is exported. It is not a legal identifier, so it cannot collide with the first
+ * segment of a qualified name resolved from a model.
+ */
+ public static final String SHORT_NAME_QUALIFIER = "";
+
+ private static final Logger LOGGER = Logger.getLogger(KerMLResourceDescriptionStrategy.class);
+
+ @Inject
+ private IQualifiedNameConverter qualifiedNameConverter;
+
+ @Override
+ public boolean createEObjectDescriptions(EObject eObject, IAcceptor acceptor) {
+ boolean result = super.createEObjectDescriptions(eObject, acceptor);
+ try {
+ QualifiedName shortQualifiedName = getShortQualifiedName(eObject);
+ if (shortQualifiedName != null) {
+ acceptor.accept(EObjectDescription.create(shortQualifiedName, eObject));
+ result = true;
+ }
+ } catch (Exception exception) {
+ // As in the superclass, a failure for one Element must not fail the whole resource.
+ LOGGER.error(exception.getMessage(), exception);
+ }
+ return result;
+ }
+
+ /**
+ * Return the qualified name under which to export the declaredShortName of the
+ * given object, or null if it does not have one, or if it is not a root Element.
+ */
+ protected QualifiedName getShortQualifiedName(EObject eObject) {
+ if (!(eObject instanceof Element)) {
+ return null;
+ }
+ String shortName = ((Element)eObject).getDeclaredShortName();
+ if (shortName == null || shortName.isEmpty()) {
+ return null;
+ }
+ // A root Element is owned by the root Namespace of its resource, which is not, itself,
+ // owned by another Namespace.
+ Namespace owningNamespace = ((Element)eObject).getOwningNamespace();
+ if (owningNamespace == null || owningNamespace.getOwningNamespace() != null) {
+ return null;
+ }
+ QualifiedName shortQualifiedName =
+ qualifiedNameConverter.toQualifiedName("'" + ElementUtil.escapeString(shortName) + "'");
+ return QualifiedName.create(SHORT_NAME_QUALIFIER).append(shortQualifiedName);
+ }
+
+}
diff --git a/org.omg.kerml.xtext/src/org/omg/kerml/xtext/scoping/KerMLGlobalScope.xtend b/org.omg.kerml.xtext/src/org/omg/kerml/xtext/scoping/KerMLGlobalScope.xtend
index a732b5ccd..47326a6bf 100644
--- a/org.omg.kerml.xtext/src/org/omg/kerml/xtext/scoping/KerMLGlobalScope.xtend
+++ b/org.omg.kerml.xtext/src/org/omg/kerml/xtext/scoping/KerMLGlobalScope.xtend
@@ -39,23 +39,26 @@ import org.omg.sysml.lang.sysml.Namespace
import org.omg.sysml.lang.sysml.SysMLPackage
import org.omg.sysml.lang.sysml.Element
import org.omg.kerml.xtext.naming.QualifiedNameUtil
+import org.omg.kerml.xtext.resource.KerMLResourceDescriptionStrategy
class KerMLGlobalScope extends AbstractScope {
protected val IScope outer;
+ protected val IScope shortNameOuter;
protected val Resource resource
protected val Predicate filter;
protected val Predicate rootFilter;
protected val EClass referenceType
protected val KerMLScopeProvider scopeProvider
- static def createScope (IScope outer, Resource resource, Predicate filter, Predicate rootFilter, EClass type, KerMLScopeProvider scopeProvider) {
- return new KerMLGlobalScope(outer, resource, filter, rootFilter, type, scopeProvider);
+ static def createScope (IScope outer, IScope shortNameOuter, Resource resource, Predicate filter, Predicate rootFilter, EClass type, KerMLScopeProvider scopeProvider) {
+ return new KerMLGlobalScope(outer, shortNameOuter, resource, filter, rootFilter, type, scopeProvider);
}
- new(IScope outer, Resource resource, Predicate filter, Predicate rootFilter, EClass type, KerMLScopeProvider scopeProvider) {
+ new(IScope outer, IScope shortNameOuter, Resource resource, Predicate filter, Predicate rootFilter, EClass type, KerMLScopeProvider scopeProvider) {
super(IScope.NULLSCOPE, false)
this.outer = outer
+ this.shortNameOuter = shortNameOuter
this.resource = resource
this.filter = filter
this.rootFilter = rootFilter
@@ -78,6 +81,24 @@ class KerMLGlobalScope extends AbstractScope {
else description
}
+ /**
+ * Get the description of the root Element with the given (single segment) name. A root Element
+ * is exported under its declaredName by KerMLQualifiedNameProvider and, if it has one, under
+ * its declaredShortName by KerMLResourceDescriptionStrategy. Short names are exported under a
+ * separate qualifier and are only considered here if no root Element has the given name as its
+ * declaredName, so a declaredName always takes precedence over a declaredShortName.
+ */
+ def protected IEObjectDescription getRootElement(QualifiedName rootName) {
+ val root = outer.getSingleElement(rootName)
+ if (root !== null || shortNameOuter === null) {
+ return root
+ }
+ val shortName = QualifiedName.create(KerMLResourceDescriptionStrategy.SHORT_NAME_QUALIFIER).append(rootName)
+ val shortNameRoot = shortNameOuter.getSingleElement(shortName)
+ return if (shortNameRoot === null) null
+ else EObjectDescription.create(rootName, shortNameRoot.EObjectOrProxy)
+ }
+
override getSingleElement(QualifiedName name) {
val qualifiedName = QualifiedNameUtil.getNonGlobalQualifiedName(name)
var IEObjectDescription result = null
@@ -91,7 +112,7 @@ class KerMLGlobalScope extends AbstractScope {
if (result === null) {
val rootName = QualifiedName.create(qualifiedName.firstSegment)
- val root = outer.getSingleElement(rootName)
+ val root = getRootElement(rootName)
if (root !== null) {
if (qualifiedName.segmentCount == 1) {
if (referenceType == SysMLPackage.eINSTANCE.membership) {
diff --git a/org.omg.kerml.xtext/src/org/omg/kerml/xtext/scoping/KerMLGlobalScopeProvider.xtend b/org.omg.kerml.xtext/src/org/omg/kerml/xtext/scoping/KerMLGlobalScopeProvider.xtend
index 0ca6c2896..3ce370027 100644
--- a/org.omg.kerml.xtext/src/org/omg/kerml/xtext/scoping/KerMLGlobalScopeProvider.xtend
+++ b/org.omg.kerml.xtext/src/org/omg/kerml/xtext/scoping/KerMLGlobalScopeProvider.xtend
@@ -34,6 +34,7 @@ import com.google.inject.Inject
import org.eclipse.emf.ecore.resource.Resource
import org.omg.sysml.lang.sysml.SysMLPackage
import com.google.common.base.Predicates
+import org.omg.kerml.xtext.resource.KerMLResourceDescriptionStrategy
class KerMLGlobalScopeProvider extends DefaultGlobalScopeProvider {
@@ -42,7 +43,13 @@ class KerMLGlobalScopeProvider extends DefaultGlobalScopeProvider {
override IScope getScope(IScope parent, Resource context, boolean ignoreCase, EClass type, Predicate filter) {
val scope = super.getScope(parent, context, false, SysMLPackage.eINSTANCE.element, [eod | eod.name.segmentCount == 1])
- return KerMLGlobalScope.createScope(scope, context, filter, rootFilter, type, kerMLScopeProvider)
+ // The short names of root Elements are exported under a separate qualifier, so that they
+ // cannot shadow declaredNames. They are resolved by KerMLGlobalScope.getRootElement.
+ val shortNameScope = super.getScope(parent, context, false, SysMLPackage.eINSTANCE.element, [eod |
+ eod.name.segmentCount == 2 &&
+ KerMLResourceDescriptionStrategy.SHORT_NAME_QUALIFIER == eod.name.firstSegment
+ ])
+ return KerMLGlobalScope.createScope(scope, shortNameScope, context, filter, rootFilter, type, kerMLScopeProvider)
}
protected def Predicate getRootFilter() {
diff --git a/org.omg.kerml.xtext/src/org/omg/kerml/xtext/scoping/KerMLScope.xtend b/org.omg.kerml.xtext/src/org/omg/kerml/xtext/scoping/KerMLScope.xtend
index 459734972..3f998af46 100644
--- a/org.omg.kerml.xtext/src/org/omg/kerml/xtext/scoping/KerMLScope.xtend
+++ b/org.omg.kerml.xtext/src/org/omg/kerml/xtext/scoping/KerMLScope.xtend
@@ -3,6 +3,7 @@
* Copyright (c) 2018 IncQuery Labs Ltd.
* Copyright (c) 2018-2022, 2024, 2025 Model Driven Solutions, Inc.
* Copyright (c) 2018-2020 California Institute of Technology/Jet Propulsion Laboratory
+ * Copyright (c) 2026 tkanov
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Eclipse Public License as published by
@@ -51,18 +52,13 @@ import org.omg.sysml.lang.sysml.OwningMembership
import org.omg.sysml.lang.sysml.NamespaceImport
import org.omg.sysml.lang.sysml.MembershipImport
import org.omg.sysml.lang.sysml.SysMLPackage
-import com.google.inject.Inject
-import org.eclipse.xtext.naming.IQualifiedNameConverter
import org.eclipse.emf.ecore.util.EcoreUtil
import org.omg.sysml.util.NamespaceUtil
import org.omg.kerml.xtext.naming.QualifiedNameUtil
import org.omg.sysml.lang.sysml.Redefinition
-class KerMLScope extends AbstractScope {
-
- @Inject
- IQualifiedNameConverter qualifiedNameConverter
-
+class KerMLScope extends AbstractScope {
+
/*
* The following fields are fixed on construction.
*/
@@ -162,8 +158,14 @@ class KerMLScope extends AbstractScope {
!resolveInScope(QualifiedName.create(input.name.firstSegment), true).isEmpty()
}
- def getElement(String name) {
- var obj = EcoreUtil.resolve(getSingleElement(qualifiedNameConverter.toQualifiedName(name)).EObjectOrProxy, element)
+ def Element getElement(String name) {
+ // Note: The qualified name converter is obtained from the scopeProvider, because a
+ // KerMLScope is constructed directly, rather than being injected.
+ val description = getSingleElement(scopeProvider.qualifiedNameConverter.toQualifiedName(name))
+ if (description === null) {
+ return null
+ }
+ var obj = EcoreUtil.resolve(description.EObjectOrProxy, element)
if (obj instanceof Element) obj else null
}
diff --git a/org.omg.kerml.xtext/src/org/omg/kerml/xtext/scoping/KerMLScopeProvider.xtend b/org.omg.kerml.xtext/src/org/omg/kerml/xtext/scoping/KerMLScopeProvider.xtend
index 98e071833..0264c9cf6 100644
--- a/org.omg.kerml.xtext/src/org/omg/kerml/xtext/scoping/KerMLScopeProvider.xtend
+++ b/org.omg.kerml.xtext/src/org/omg/kerml/xtext/scoping/KerMLScopeProvider.xtend
@@ -3,6 +3,7 @@
* Copyright (c) 2018 IncQuery Labs Ltd.
* Copyright (c) 2018-2022, 2024 Model Driven Solutions, Inc.
* Copyright (c) 2018, 2019 California Institute of Technology/Jet Propulsion Laboratory
+ * Copyright (c) 2026 tkanov
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Eclipse Public License as published by
@@ -31,6 +32,7 @@ package org.omg.kerml.xtext.scoping
import com.google.common.base.Predicates
import com.google.inject.Inject
+import org.eclipse.xtext.naming.IQualifiedNameConverter
import java.util.Set
import org.eclipse.emf.ecore.EObject
import org.eclipse.emf.ecore.EReference
@@ -57,6 +59,13 @@ class KerMLScopeProvider extends AbstractKerMLScopeProvider {
@Inject
IGlobalScopeProvider globalScope
+ @Inject
+ IQualifiedNameConverter qualifiedNameConverter
+
+ def getQualifiedNameConverter() {
+ qualifiedNameConverter
+ }
+
@Inject
LibraryNamespaces libraryNamespaces
diff --git a/org.omg.kerml.xtext/src/org/omg/kerml/xtext/xmi/KerMLxRuntimeModule.xtend b/org.omg.kerml.xtext/src/org/omg/kerml/xtext/xmi/KerMLxRuntimeModule.xtend
index c1de3fed9..4e8d8f548 100644
--- a/org.omg.kerml.xtext/src/org/omg/kerml/xtext/xmi/KerMLxRuntimeModule.xtend
+++ b/org.omg.kerml.xtext/src/org/omg/kerml/xtext/xmi/KerMLxRuntimeModule.xtend
@@ -22,6 +22,8 @@ import com.google.inject.Binder
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl
import org.eclipse.xtext.naming.IQualifiedNameConverter
+import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy
+import org.omg.kerml.xtext.resource.KerMLResourceDescriptionStrategy
import org.eclipse.xtext.resource.IResourceServiceProvider
import org.eclipse.xtext.resource.IResourceDescriptions
import org.eclipse.xtext.resource.generic.AbstractGenericResourceRuntimeModule
@@ -76,6 +78,10 @@ class KerMLxRuntimeModule extends AbstractGenericResourceRuntimeModule{
KerMLQualifiedNameProvider
}
+ def Class extends IDefaultResourceDescriptionStrategy> bindIDefaultResourceDescriptionStrategy() {
+ KerMLResourceDescriptionStrategy
+ }
+
def Class extends IQualifiedNameConverter> bindIQualifiedNameConverter() {
KerMLQualifiedNameConverter
}
diff --git a/org.omg.sysml.interactive.tests/src/org/omg/sysml/interactive/tests/ShortNameScopeTest.java b/org.omg.sysml.interactive.tests/src/org/omg/sysml/interactive/tests/ShortNameScopeTest.java
new file mode 100644
index 000000000..45600a29c
--- /dev/null
+++ b/org.omg.sysml.interactive.tests/src/org/omg/sysml/interactive/tests/ShortNameScopeTest.java
@@ -0,0 +1,88 @@
+/**
+ * SysML 2 Pilot Implementation
+ * Copyright (C) 2026 tkanov
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Eclipse Public License, version 2, as published by
+ * the Eclipse Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Eclipse Public License for more details.
+ *
+ * You should have received a copy of the Eclipse Public License
+ * along with this program. If not, see .
+ *
+ * @license EPL-2.0
+ */
+package org.omg.sysml.interactive.tests;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.omg.sysml.interactive.SysMLInteractive;
+import org.omg.sysml.interactive.SysMLInteractiveResult;
+
+/**
+ * Tests for resolving qualified names whose first segment is the short name of a
+ * Package, both within a single resource and across resources (see issue #778).
+ */
+public class ShortNameScopeTest extends SysMLInteractiveTest {
+
+ private void assertNoIssues(SysMLInteractive instance, String input) {
+ SysMLInteractiveResult result = instance.process(input);
+ assertTrue("'" + input + "':\n" + result.formatIssues(), result.getIssues().isEmpty());
+ }
+
+ @Test
+ public void testShortNameQualificationSameResource() throws Exception {
+ SysMLInteractive instance = createSysMLInteractiveInstance();
+ assertNoIssues(instance, "package Alpha { part def Foo; } package Beta { part x : P::Foo; }");
+ }
+
+ @Test
+ public void testDeclaredNameQualificationAcrossResources() throws Exception {
+ SysMLInteractive instance = createSysMLInteractiveInstance();
+ assertNoIssues(instance, "package
Alpha { part def Foo; }");
+ assertNoIssues(instance, "package Beta { part x : Alpha::Foo; }");
+ }
+
+ @Test
+ public void testShortNameQualificationAcrossResources() throws Exception {
+ SysMLInteractive instance = createSysMLInteractiveInstance();
+ assertNoIssues(instance, "package
Alpha { part def Foo; }");
+ assertNoIssues(instance, "package Beta { part x : P::Foo; }");
+ }
+
+ @Test
+ public void testShortNameImportAcrossResources() throws Exception {
+ SysMLInteractive instance = createSysMLInteractiveInstance();
+ assertNoIssues(instance, "package
Alpha { part def Foo; }");
+ assertNoIssues(instance, "package Beta { private import P::*; part x : Foo; }");
+ }
+ @Test
+ public void testLibraryPackageIsNotShadowedByShortName() throws Exception {
+ SysMLInteractive instance = createSysMLInteractiveInstance();
+ // 'ScalarValues' is the declaredName of a standard library package, and here also the
+ // short name of a user package. References to the library package must still resolve.
+ assertNoIssues(instance, "package MyPackage { part def Foo; }");
+ assertNoIssues(instance, "package Beta { attribute a : ScalarValues::Real; }");
+ }
+
+ @Test
+ public void testDeclaredNameTakesPrecedenceOverShortName() throws Exception {
+ SysMLInteractive instance = createSysMLInteractiveInstance();
+ assertNoIssues(instance, "package Alpha { part def Foo; }");
+ assertNoIssues(instance, "package P { part def Bar; }");
+ assertNoIssues(instance, "package Beta { part x : P::Bar; }");
+ }
+
+ @Test
+ public void testDeclaredNameTakesPrecedenceOverShortNameWhenDeclaredFirst() throws Exception {
+ SysMLInteractive instance = createSysMLInteractiveInstance();
+ assertNoIssues(instance, "package P { part def Bar; }");
+ assertNoIssues(instance, "package
Alpha { part def Foo; }");
+ assertNoIssues(instance, "package Beta { part x : P::Bar; }");
+ }
+}
diff --git a/org.omg.sysml.xtext/src/org/omg/sysml/xtext/SysMLRuntimeModule.xtend b/org.omg.sysml.xtext/src/org/omg/sysml/xtext/SysMLRuntimeModule.xtend
index eb937ca74..2fa785af5 100644
--- a/org.omg.sysml.xtext/src/org/omg/sysml/xtext/SysMLRuntimeModule.xtend
+++ b/org.omg.sysml.xtext/src/org/omg/sysml/xtext/SysMLRuntimeModule.xtend
@@ -16,6 +16,8 @@ import org.eclipse.xtext.validation.IResourceValidator
import org.omg.kerml.xtext.linking.KerMLLazyLinkingResource
import org.omg.kerml.xtext.conversion.KerMLValueConverterService
import org.omg.kerml.xtext.naming.KerMLQualifiedNameProvider
+import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy
+import org.omg.kerml.xtext.resource.KerMLResourceDescriptionStrategy
import org.omg.kerml.xtext.scoping.KerMLLinker
import org.omg.kerml.xtext.validation.KerMLResourceValidator
import org.omg.sysml.logic.api.IModelLibraryProvider
@@ -65,7 +67,11 @@ class SysMLRuntimeModule extends AbstractSysMLRuntimeModule {
override Class extends IQualifiedNameProvider> bindIQualifiedNameProvider() {
KerMLQualifiedNameProvider
}
-
+
+ def Class extends IDefaultResourceDescriptionStrategy> bindIDefaultResourceDescriptionStrategy() {
+ KerMLResourceDescriptionStrategy
+ }
+
override Class extends ILinker> bindILinker() {
KerMLLinker
}
diff --git a/org.omg.sysml.xtext/src/org/omg/sysml/xtext/xmi/SysMLxRuntimeModule.xtend b/org.omg.sysml.xtext/src/org/omg/sysml/xtext/xmi/SysMLxRuntimeModule.xtend
index 0374d172e..591559ef3 100644
--- a/org.omg.sysml.xtext/src/org/omg/sysml/xtext/xmi/SysMLxRuntimeModule.xtend
+++ b/org.omg.sysml.xtext/src/org/omg/sysml/xtext/xmi/SysMLxRuntimeModule.xtend
@@ -22,6 +22,8 @@ import com.google.inject.Binder
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl
import org.eclipse.xtext.naming.IQualifiedNameConverter
+import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy
+import org.omg.kerml.xtext.resource.KerMLResourceDescriptionStrategy
import org.eclipse.xtext.naming.IQualifiedNameProvider
import org.eclipse.xtext.resource.IResourceServiceProvider
import org.eclipse.xtext.resource.IResourceDescriptions
@@ -78,6 +80,10 @@ class SysMLxRuntimeModule extends AbstractGenericResourceRuntimeModule {
KerMLQualifiedNameProvider
}
+ def Class extends IDefaultResourceDescriptionStrategy> bindIDefaultResourceDescriptionStrategy() {
+ KerMLResourceDescriptionStrategy
+ }
+
def Class extends IQualifiedNameConverter> bindIQualifiedNameConverter() {
KerMLQualifiedNameConverter
}