diff --git a/org.omg.sysml.logic/src/main/java/org/omg/sysml/adapter/PortUsageAdapter.java b/org.omg.sysml.logic/src/main/java/org/omg/sysml/adapter/PortUsageAdapter.java
index b85fd25d9..a52f5e2eb 100644
--- a/org.omg.sysml.logic/src/main/java/org/omg/sysml/adapter/PortUsageAdapter.java
+++ b/org.omg.sysml.logic/src/main/java/org/omg/sysml/adapter/PortUsageAdapter.java
@@ -21,12 +21,12 @@
package org.omg.sysml.adapter;
import org.eclipse.emf.ecore.EObject;
-import org.omg.sysml.lang.sysml.FeatureMembership;
import org.omg.sysml.lang.sysml.PartDefinition;
import org.omg.sysml.lang.sysml.PartUsage;
import org.omg.sysml.lang.sysml.PortDefinition;
import org.omg.sysml.lang.sysml.PortUsage;
import org.omg.sysml.lang.sysml.Type;
+import org.omg.sysml.util.UsageUtil;
public class PortUsageAdapter extends UsageAdapter {
@@ -44,12 +44,9 @@ public PortUsage getTarget() {
public void postProcess() {
super.postProcess();
PortUsage target = getTarget();
- EObject container = target.eContainer();
- if (container instanceof FeatureMembership) {
- Type owningType = ((FeatureMembership)container).getOwningType();
- if (!(owningType instanceof PortDefinition || owningType instanceof PortUsage)) {
- target.setIsComposite(false);
- }
+ EObject featuringType = UsageUtil.getExpectedFeaturingTypeOf(target);
+ if (!(featuringType instanceof PortDefinition || featuringType instanceof PortUsage)) {
+ target.setIsComposite(false);
}
}
diff --git a/org.omg.sysml.logic/src/main/java/org/omg/sysml/adapter/UsageAdapter.java b/org.omg.sysml.logic/src/main/java/org/omg/sysml/adapter/UsageAdapter.java
index 004115ca9..97f23f2d4 100644
--- a/org.omg.sysml.logic/src/main/java/org/omg/sysml/adapter/UsageAdapter.java
+++ b/org.omg.sysml.logic/src/main/java/org/omg/sysml/adapter/UsageAdapter.java
@@ -62,9 +62,7 @@ public void postProcess () {
if (target.isVariation()) {
target.setIsAbstract(true);
}
- if (target.getDirection() != null || target.isEnd() ||
- // Note: A parsed Usage can only get a featuring type if it is owned via a FeatureMembership.
- !(target.eContainer() instanceof FeatureMembership)) {
+ if (target.getDirection() != null || target.isEnd() || !UsageUtil.hasFeaturingType(target)) {
target.setIsComposite(false);
}
}
diff --git a/org.omg.sysml.logic/src/main/java/org/omg/sysml/util/UsageUtil.java b/org.omg.sysml.logic/src/main/java/org/omg/sysml/util/UsageUtil.java
index fee046c46..7085edf51 100644
--- a/org.omg.sysml.logic/src/main/java/org/omg/sysml/util/UsageUtil.java
+++ b/org.omg.sysml.logic/src/main/java/org/omg/sysml/util/UsageUtil.java
@@ -1,6 +1,6 @@
/*******************************************************************************
* SysML 2 Pilot Implementation
- * Copyright (c) 2021-2025 Model Driven Solutions, Inc.
+ * Copyright (c) 2021-2026 Model Driven Solutions, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Eclipse Public License as published by
@@ -26,6 +26,7 @@
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
+import org.eclipse.emf.ecore.EObject;
import org.omg.sysml.adapter.UsageAdapter;
import org.omg.sysml.lang.sysml.AcceptActionUsage;
import org.omg.sysml.lang.sysml.ActionUsage;
@@ -90,6 +91,33 @@ public static boolean mayTimeVary(Usage usage) {
return getUsageAdapter(usage).mayTimeVary();
}
+ // Featuring Types
+
+ /**
+ * Determine the featuringType a Usage is expected to have after transformation,
+ * without actually computing the full derivation for featuringType. Assumes that
+ * a Usage in SysML can only get a featuringType in one of two ways:
+ * 1. If it is an ownedFeature.
+ * 2. If it is a variant of a variation Usage that has a featuringType.
+ * Note that the special featuringTypes of variable features are ignored.
+ */
+ public static Type getExpectedFeaturingTypeOf(Usage usage) {
+ EObject container = usage.eContainer();
+ if (container instanceof FeatureMembership featureMembership) {
+ return featureMembership.getOwningType();
+ } else if (container != null &&
+ container.eContainer() instanceof Usage containingUsage &&
+ containingUsage.isVariation()) {
+ return getExpectedFeaturingTypeOf(containingUsage);
+ } else {
+ return null;
+ }
+ }
+
+ public static boolean hasFeaturingType(Usage usage) {
+ return getExpectedFeaturingTypeOf(usage) != null;
+ }
+
// Variants
public static boolean isVariant(Usage usage) {
diff --git a/org.omg.sysml.logic/src/test/java/org/omg/sysml/logic/UsagePostProcessTest.java b/org.omg.sysml.logic/src/test/java/org/omg/sysml/logic/UsagePostProcessTest.java
new file mode 100644
index 000000000..db261dac0
--- /dev/null
+++ b/org.omg.sysml.logic/src/test/java/org/omg/sysml/logic/UsagePostProcessTest.java
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * SysML 2 Pilot Implementation
+ * Copyright (c) 2026 Model Driven Solutions, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Eclipse Public License as published by
+ * the Eclipse Foundation, version 2 of the License.
+ * (at your option) any later version.
+ *
+ * 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 theEclipse Public License
+ * along with this program. If not, see .
+ *
+ * @license EPL-2.0
+ *
+ *******************************************************************************/
+package org.omg.sysml.logic;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.eclipse.emf.ecore.EClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.omg.sysml.lang.sysml.Element;
+import org.omg.sysml.lang.sysml.FeatureDirectionKind;
+import org.omg.sysml.lang.sysml.Namespace;
+import org.omg.sysml.lang.sysml.Package;
+import org.omg.sysml.lang.sysml.SysMLFactory;
+import org.omg.sysml.lang.sysml.SysMLPackage;
+import org.omg.sysml.lang.sysml.Usage;
+import org.omg.sysml.lang.sysml.VariantMembership;
+import org.omg.sysml.util.ElementUtil;
+import org.omg.sysml.util.NamespaceUtil;
+import org.omg.sysml.util.TypeUtil;
+
+public class UsagePostProcessTest {
+
+ @BeforeClass
+ public static void setUp() {
+ SysMLLogicStandaloneSetup.doSetup();
+ SysMLPackage.eINSTANCE.eClass();
+ }
+
+ /**
+ * Test that Usage.postProcess sets the property "composite" to false in appropriate cases.
+ * @throws InvocationTargetException
+ */
+ @Test
+ public void settingCompositeToFalse() throws InvocationTargetException {
+ /*
+ * package Test {\n"
+ * part p {
+ * attribute a;
+ * part x;
+ * in part y
+ * variation part z {
+ * variant variation part u {
+ * variant part v;
+ * }
+ * variant port w;
+ * }
+ * }
+ * variation part q {
+ * variant part r;
+ * }
+ * }
+ */
+ Package test = (Package) createElement(SysMLPackage.Literals.PACKAGE, "test", null);
+ Usage p = (Usage) createElement(SysMLPackage.Literals.PART_USAGE, "p", test);
+ Usage a = (Usage) createElement(SysMLPackage.Literals.ATTRIBUTE_USAGE, "a", p);
+ Usage x = (Usage) createElement(SysMLPackage.Literals.PART_USAGE, "x", p);
+ Usage y = (Usage) createElement(SysMLPackage.Literals.PART_USAGE, "y", p);
+ y.setDirection(FeatureDirectionKind.IN);
+ Usage z = (Usage) createElement(SysMLPackage.Literals.PART_USAGE, "z", p);
+ z.setIsVariation(true);
+ Usage u = (Usage) createElement(SysMLPackage.Literals.PART_USAGE, "u", z);
+ u.setIsVariation(true);
+ Usage v = (Usage) createElement(SysMLPackage.Literals.PART_USAGE, "v", u);
+ Usage w = (Usage) createElement(SysMLPackage.Literals.PORT_USAGE, "w", u);
+ Usage q = (Usage) createElement(SysMLPackage.Literals.PART_USAGE, "q", test);
+ q.setIsVariation(true);
+ Usage r = (Usage) createElement(SysMLPackage.Literals.PART_USAGE, "r", q);
+
+ // Post-process after creating the entire model.
+ postProcess(p, a, x, y, z, u, v, w, q, r);
+
+ assertTrue(p.isReference());
+ assertTrue(a.isReference());
+ assertFalse(x.isReference());
+ assertTrue(y.isReference());
+ assertFalse(z.isReference());
+ assertFalse(u.isReference());
+ assertFalse(v.isReference());
+ assertTrue(w.isReference());
+ assertTrue(q.isReference());
+ assertTrue(r.isReference());
+ }
+
+ /**
+ * Create an Element with the given eClass, name and parent. If the created Element and the parent
+ * are both Usages, then the Element is added to the parent as a variant, if the parent is a variation,
+ * or as an ownedFeature, if the parent is not a variation. Otherwise, the Element is added as an
+ * ownedMember of the parent. (Adding ownedFeatures of Definitions is not currently needed.)
+ */
+ protected static Element createElement(EClass eClass, String name, Namespace parent) {
+ Element element = (Element)SysMLFactory.eINSTANCE.create(eClass);
+ element.setDeclaredName(name);
+ if (element instanceof Usage usage && parent instanceof Usage parentUsage) {
+ if (parentUsage.isVariation()) {
+ VariantMembership membership = SysMLFactory.eINSTANCE.createVariantMembership();
+ membership.setOwnedVariantUsage(usage);
+ parentUsage.getOwnedRelationship().add(membership);
+ } else {
+ TypeUtil.addOwnedFeatureTo(parentUsage, usage);
+ }
+ } else if (parent != null) {
+ NamespaceUtil.addOwnedMemberTo(parent, element);
+ }
+ return element;
+ }
+
+ /**
+ * Call the postProcess method of the ElementAdapter of the given array of elements.
+ */
+ protected static void postProcess(Element... elements) {
+ for (Element element: elements) {
+ ElementUtil.getElementAdapter(element).postProcess();
+ }
+ }
+
+}