Skip to content
Merged
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

Large diffs are not rendered by default.

Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@
import org.omg.sysml.lang.sysml.Type;
import org.omg.sysml.lang.sysml.TypeFeaturing;
import org.omg.sysml.lang.sysml.VisibilityKind;
import org.omg.sysml.util.ConnectorUtil;
import org.omg.sysml.util.ElementUtil;
import org.omg.sysml.util.ExpressionUtil;
import org.omg.sysml.util.FeatureUtil;
import org.omg.sysml.util.NonNotifyingEObjectEList;
import org.omg.sysml.util.TypeUtil;
import org.omg.sysml.util.VirtualContainer;

public class FeatureAdapter extends TypeAdapter {

Expand Down Expand Up @@ -126,10 +126,13 @@ public void addImplicitFeaturingTypes() {

public void addFeaturingType(Type type) {
implicitFeaturingTypes.add(type);
VirtualContainer.attach(type, getTarget());
}

public void addFeaturingTypes(Collection<Type> featuringTypes) {
implicitFeaturingTypes.addAll(featuringTypes);
for (Type featuringType : featuringTypes) {
addFeaturingType(featuringType);
}
}

public void forEachImplicitFeaturingType(Consumer<Type> action) {
Expand Down Expand Up @@ -811,8 +814,7 @@ protected void addImplicitFeaturingTypesIfNecessary() {
}

protected BindingConnector addBindingConnector(Collection<Type> featuringTypes, Feature source, Feature target) {
BindingConnector connector = ConnectorUtil.createBindingConnector(source, target);
ConnectorUtil.transformBindingConnector(connector, getTarget());
BindingConnector connector = createImplicitBindingConnector(source, target);
addImplicitMemberBindingConnector(connector);
FeatureUtil.addFeaturingTypesTo(connector, featuringTypes);
return connector;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*******************************************************************************
* SysML 2 Pilot Implementation
* Copyright (c) 2021-2026 Model Driven Solutions, Inc.
* Copyright (c) 2026 Obeo
*
* 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 @@ -60,6 +61,7 @@
import org.omg.sysml.util.ImplicitGeneralizationMap;
import org.omg.sysml.util.NonNotifyingEObjectEList;
import org.omg.sysml.util.TypeUtil;
import org.omg.sysml.util.VirtualContainer;

public class TypeAdapter extends NamespaceAdapter {

Expand Down Expand Up @@ -364,6 +366,7 @@ protected static boolean hasNoConformingSpecializations(Type type, Class<?> kind
public void addImplicitGeneralType(EClass eClass, Type general) {
if (isAddImplicitGeneralTypes && general != null && general != getTarget() && !isImplicitSpecializationFor(eClass, general)) {
implicitGeneralTypes.computeIfAbsent(eClass, e -> new ArrayList<>()).add(general);
VirtualContainer.attach(general, getTarget());
}
}

Expand Down Expand Up @@ -514,10 +517,21 @@ protected static Feature getBaseTypeFeature(Element element) {

// Transformation

/**
* Creates and transforms a binding connector implied by a constraint on the target Type. The
* implied relationship is the owning membership of the connector in the target, so the target
* is its virtual container. It is recorded before the transformation, which looks up the
* library general of the connector, and before any query on the connector.
*/
protected BindingConnector createImplicitBindingConnector(Feature source, Feature target) {
BindingConnector connector = VirtualContainer.attach(ConnectorUtil.createBindingConnector(source, target), getTarget());
ConnectorUtil.transformBindingConnector(connector);
return connector;
}

public BindingConnector addBindingConnector(Feature source, Feature target) {
Type type = getTarget();
BindingConnector connector = ConnectorUtil.createBindingConnector(source, target);
ConnectorUtil.transformBindingConnector(connector, getTarget());
BindingConnector connector = createImplicitBindingConnector(source, target);
Type contextType = ConnectorUtil.getContextTypeFor(connector);
if (contextType == type) {
addImplicitFeatureBindingConnector(connector);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Copyright (c) 2021-2022 Model Driven Solutions, Inc.
* Copyright (c) 2023 Mgnite Inc.
* Copyright (c) 2026 Budapest University of Technology and Economics
* Copyright (c) 2026 Obeo
Comment thread
seidewitz marked this conversation as resolved.
Resolved
*
* 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 @@ -53,9 +54,9 @@ public static BindingConnector createBindingConnector(Feature source, Feature ta
return connector;
}

public static void transformBindingConnector(BindingConnector connector, Type owner) {
public static void transformBindingConnector(BindingConnector connector) {
TypeUtil.addImplicitGeneralTypeTo(connector, SysMLPackage.eINSTANCE.getSubsetting(),
SysMLLibraryUtil.getLibraryType(owner, ImplicitGeneralizationMap.getDefaultSupertypeFor(connector.getClass(), "binary")));
SysMLLibraryUtil.getLibraryType(connector, ImplicitGeneralizationMap.getDefaultSupertypeFor(connector.getClass(), "binary")));
for (Feature end: TypeUtil.getEndFeatureOf(connector)) {
ElementUtil.transform(end);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,47 @@ public static String escapeName(String name) {
public static boolean isIdentifier(String name) {
return name.matches("[a-zA-Z_]\\w*");
}


// Containment

/**
* Returns the container of an element: its real container when it has one, otherwise the
* virtual container recorded for an element created outside the model (see
* {@link VirtualContainer}).
*
* @param element the element whose container is requested
* @return the real or virtual container, or {@code null} for a root or an unlinked element
*/
public static EObject getEffectiveContainer(EObject element) {
if (element == null) {
return null;
}
EObject container = element.eContainer();
if (container != null) {
return container;
}
return VirtualContainer.getVirtualContainer(element);
}

/**
* Returns the element itself when it belongs to a resource, otherwise its closest effective
* container (see {@link #getEffectiveContainer(EObject)}) that does. The walk terminates as
* long as effective containers form no cycle: {@link VirtualContainer#attach} never records a
* link that would close one, and no element must later be inserted into an element that
* reaches it through effective containers.
*
* @param element the element from which the model is reached
* @return the closest element that belongs to a resource, or {@code null} when neither the
* element nor any of its effective containers does
*/
public static EObject getClosestElementInResource(EObject element) {
EObject current = element;
while (current != null && current.eResource() == null) {
current = getEffectiveContainer(current);
}
return current;
}

// Qualified Names

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* SysML 2 Pilot Implementation
* Copyright (c) 2021-2025 Model Driven Solutions, Inc.
* Copyright (c) 2026 Budapest University of Technology and Economics
* Copyright (c) 2026 Obeo
Comment thread
seidewitz marked this conversation as resolved.
Resolved
*
* 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 @@ -350,7 +351,18 @@ public static void insertImplicitTypeFeaturings(Feature feature) {
featuring.setIsImplied(true);
featuring.setFeaturingType(type);
featuring.setFeatureOfType(feature);
if (type.getOwningRelationship() == null && !type.eIsProxy()) {
// A featuring type created for this implied TypeFeaturing, such as an <Owner>_snapshots
// feature or a feature chain, is detached: it becomes an owned related element of the
// inserted relationship. Having no owning relationship does not prove that a type is
// detached: the root namespace of a model has no owner either. A parsed model cannot cause
// a confusion, since its root namespace is an unnamed Namespace, which cannot be a featuring
// type. A model built by other means, such as a test building elements programmatically,
// can however have a Type as the root of its resource and as the featuring type of one of its
// own members. Adopting that root would move it out of its resource into its own member, a
// containment cycle that ElementUtil.getClosestElementInResource could not leave. Only a type
// that is neither owned nor in a resource is therefore adopted; the root of a model built
// without any resource still cannot be told apart from a detached type.
if (!type.eIsProxy() && type.getOwningRelationship() == null && type.eResource() == null) {
featuring.getOwnedRelatedElement().add(type);
}
feature.getOwnedRelationship().add(featuring);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,19 +158,30 @@ public static void setProviderLookup(ProviderLookup lookup) {
* Resolves a library element by qualified name relative to the given context
* element.
*
* <p>The context element is used to locate the originating resource and obtain
* the appropriate {@link IModelLibraryProvider}. If no provider is available
* or the provider cannot resolve the name, this method returns {@code null}.
* <p>The lookup starts from the closest element in a resource: the context itself, or, for an
* element created outside the model, the closest of its effective containers that belongs to
* a resource (see {@link ElementUtil#getClosestElementInResource}). That element locates the
* originating resource, obtains the appropriate {@link IModelLibraryProvider} and is passed to
* it. If no provider is available or the provider cannot resolve the name, this method returns
* {@code null}.
*
* @param context the lookup context
* @param name the qualified library name to resolve
* @return the resolved library element, or {@code null} if it cannot be
* resolved
*/
public static Element getLibraryElement(Element context, String name) {
Resource resource = context == null ? null : context.eResource();
Element lookupContext = context;
Resource resource = null;
if (ElementUtil.getClosestElementInResource(context) instanceof Element attached) {
lookupContext = attached;
resource = attached.eResource();
}
IModelLibraryProvider provider = getInstance(resource);
return provider == null ? null : provider.getElement(context, name);
if (provider == null) {
return null;
}
return provider.getElement(lookupContext, name);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,9 @@ public static void insertImplicitSpecializations(Type type) {
newSpecialization.setIsImplied(true);
newSpecialization.setGeneral(general);
newSpecialization.setSpecific(type);
if (general.getOwningRelationship() == null && !general.eIsProxy()) {
// Only a detached general, such as a feature chain, is adopted; a root Type of its
// resource stays where it is (see FeatureUtil.insertImplicitTypeFeaturings).
if (!general.eIsProxy() && general.getOwningRelationship() == null && general.eResource() == null) {
newSpecialization.getOwnedRelatedElement().add(general);
}
type.getOwnedRelationship().add(newSpecialization);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* SysML 2 Pilot Implementation
* Copyright (c) 2026 Obeo
*
* 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.sysml.util;

import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.ecore.EObject;
import org.omg.sysml.lang.sysml.Element;

/**
* Links an element created outside the model, such as a feature chain or a binding connector
* inferred for an implied relationship, to the model element for which it was created.
* <p>
* The specification has no detached elements: the implied Relationship is owned by the element
* being constrained, and a feature chain it targets is owned by that Relationship. KerML §8.4.3.1:
* "In all cases, the source and owningRelatedElement of the Relationship is the Element being
* constrained, with the target being as given in the last column of the table." KerML §7.3.4.6:
* "the related element specified using the feature chain notation becomes an owned related feature
* of the relationship with the feature chain as notated." The implementation creates such elements
* before the Relationship that will own them; this adapter records their future owner so that
* scope lookups can reach the model from them. It is recorded when such an element is stored as
* the target of an implied relationship: an implicit general type, an implicit featuring type or
* an implicit binding connector, whose implied relationship is its owning membership.
* <p>
* The link does not change the model. Once the element is really contained, its real container
* takes precedence: use {@link ElementUtil#getEffectiveContainer(EObject)} rather than this class.
* <p>
* Specification quotations are from KerML 1.1 Beta 2.
*/
public final class VirtualContainer extends AdapterImpl {

private final Element container;

private VirtualContainer(Element container) {
this.container = container;
}

/**
* Records {@code container} as the virtual container of an element that is the root of a
* detached tree: it is not a proxy, has no container and is not in a resource. Any other element
* is left unchanged, as is an element that already has a virtual container.
* <p>
* A model built without a resource has roots that are not in a resource either, and one of them
* can be stored as the target of an implied relationship owned by one of its own members, such
* as the featuring type of a nested feature. The link is not recorded when {@code element} is
* already an effective container of {@code container}, so that effective containers never form
* a cycle and {@link ElementUtil#getClosestElementInResource(EObject)} terminates.
*
* @param <T> the type of the element
* @param element the element stored as the target of an implied relationship
* @param container the model element that owns the implied relationship
* @return {@code element}
*/
public static <T extends EObject> T attach(T element, Element container) {
if (isDetachedRoot(element) && container != null && getVirtualContainer(element) == null
&& !isEffectiveContainerOf(element, container)) {
element.eAdapters().add(new VirtualContainer(container));
}
return element;
}

private static boolean isDetachedRoot(EObject element) {
return element != null && !element.eIsProxy() && element.eContainer() == null && element.eResource() == null;
}

private static boolean isEffectiveContainerOf(EObject ancestor, EObject element) {
for (EObject current = element; current != null; current = ElementUtil.getEffectiveContainer(current)) {
if (current == ancestor) {
return true;
}
}
return false;
}

/**
* Returns the virtual container recorded for an element.
*
* @param element the element to inspect
* @return the recorded container, or {@code null} when none was recorded
*/
public static Element getVirtualContainer(EObject element) {
if (element == null) {
return null;
}
for (var adapter : element.eAdapters()) {
if (adapter instanceof VirtualContainer virtual) {
return virtual.container;
}
}
return null;
}
}
Binary file not shown.
Loading