Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions org.omg.kerml.xtext/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Export-Package: org.omg.kerml.xtext,
org.omg.kerml.xtext.naming,
org.omg.kerml.xtext.parser.antlr,
org.omg.kerml.xtext.parser.antlr.internal,
org.omg.kerml.xtext.resource,
org.omg.kerml.xtext.scoping,
org.omg.kerml.xtext.serializer,
org.omg.kerml.xtext.services,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import org.omg.kerml.xtext.library.LibraryNamespaces
import org.omg.kerml.xtext.linking.KerMLLazyLinkingResource
import org.omg.kerml.xtext.naming.KerMLQualifiedNameConverter
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.KerMLGlobalScopeProvider
import org.omg.kerml.xtext.scoping.KerMLLinker
import org.omg.kerml.xtext.validation.KerMLResourceValidator
Expand Down Expand Up @@ -76,6 +78,10 @@ class KerMLRuntimeModule extends AbstractKerMLRuntimeModule {
KerMLQualifiedNameProvider
}

def Class<? extends IDefaultResourceDescriptionStrategy> bindIDefaultResourceDescriptionStrategy() {
KerMLResourceDescriptionStrategy
}

override Class<? extends ILinker> bindILinker() {
KerMLLinker
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* 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 <https://www.eclipse.org/legal/epl-2.0/>.
*
* @license EPL-2.0 <http://spdx.org/licenses/EPL-2.0>
*/

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 <code>declaredShortName</code> of a root
* Element, in addition to the <code>declaredName</code>-based qualified name exported by
* {@link org.omg.kerml.xtext.naming.KerMLQualifiedNameProvider}.
*
* <p>Local (same-resource) name resolution matches both the <code>memberName</code> and the
* <code>memberShortName</code> of a Membership. Without the additional exported names, the first
* segment of a cross-resource qualified name could only be resolved by <code>declaredName</code>,
* so a reference such as <code>P::Foo</code>, where <code>P</code> is the short name of a root
* Package in another resource, would not resolve.
*
* <p>Short names are exported under the qualifier {@link #SHORT_NAME_QUALIFIER}, which keeps them
* in a separate key space from <code>declaredName</code>-based qualified names. This matters for
* two reasons. First, a short name can then never displace the <code>declaredName</code> 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 <code>declaredName</code>
* lookup has failed.
*
* <p>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 <code>declaredShortName</code> but no
* <code>declaredName</code>.
*/
public class KerMLResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy {

/**
* The first segment of the qualified name under which the <code>declaredShortName</code> 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 = "<short-name>";

private static final Logger LOGGER = Logger.getLogger(KerMLResourceDescriptionStrategy.class);

@Inject
private IQualifiedNameConverter qualifiedNameConverter;

@Override
public boolean createEObjectDescriptions(EObject eObject, IAcceptor<IEObjectDescription> 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 <code>declaredShortName</code> 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<IEObjectDescription> filter;
protected val Predicate<IEObjectDescription> rootFilter;
protected val EClass referenceType
protected val KerMLScopeProvider scopeProvider

static def createScope (IScope outer, Resource resource, Predicate<IEObjectDescription> filter, Predicate<IEObjectDescription> rootFilter, EClass type, KerMLScopeProvider scopeProvider) {
return new KerMLGlobalScope(outer, resource, filter, rootFilter, type, scopeProvider);
static def createScope (IScope outer, IScope shortNameOuter, Resource resource, Predicate<IEObjectDescription> filter, Predicate<IEObjectDescription> rootFilter, EClass type, KerMLScopeProvider scopeProvider) {
return new KerMLGlobalScope(outer, shortNameOuter, resource, filter, rootFilter, type, scopeProvider);
}

new(IScope outer, Resource resource, Predicate<IEObjectDescription> filter, Predicate<IEObjectDescription> rootFilter, EClass type, KerMLScopeProvider scopeProvider) {
new(IScope outer, IScope shortNameOuter, Resource resource, Predicate<IEObjectDescription> filter, Predicate<IEObjectDescription> rootFilter, EClass type, KerMLScopeProvider scopeProvider) {
super(IScope.NULLSCOPE, false)
this.outer = outer
this.shortNameOuter = shortNameOuter
this.resource = resource
this.filter = filter
this.rootFilter = rootFilter
Expand All @@ -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
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -42,7 +43,13 @@ class KerMLGlobalScopeProvider extends DefaultGlobalScopeProvider {

override IScope getScope(IScope parent, Resource context, boolean ignoreCase, EClass type, Predicate<IEObjectDescription> 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<IEObjectDescription> getRootFilter() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -57,6 +59,13 @@ class KerMLScopeProvider extends AbstractKerMLScopeProvider {
@Inject
IGlobalScopeProvider globalScope

@Inject
IQualifiedNameConverter qualifiedNameConverter

def getQualifiedNameConverter() {
qualifiedNameConverter
}

@Inject
LibraryNamespaces libraryNamespaces

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -76,6 +78,10 @@ class KerMLxRuntimeModule extends AbstractGenericResourceRuntimeModule{
KerMLQualifiedNameProvider
}

def Class<? extends IDefaultResourceDescriptionStrategy> bindIDefaultResourceDescriptionStrategy() {
KerMLResourceDescriptionStrategy
}

def Class<? extends IQualifiedNameConverter> bindIQualifiedNameConverter() {
KerMLQualifiedNameConverter
}
Expand Down
Loading