diff --git a/core/org.osate.ui.tests/models/issue3234/.gitignore b/core/org.osate.ui.tests/models/issue3234/.gitignore new file mode 100644 index 00000000000..afce51184c6 --- /dev/null +++ b/core/org.osate.ui.tests/models/issue3234/.gitignore @@ -0,0 +1,2 @@ +/.aadlbin-gen/ +/instances/ diff --git a/core/org.osate.ui.tests/models/issue3234/.project b/core/org.osate.ui.tests/models/issue3234/.project new file mode 100644 index 00000000000..61a0a3d9ee7 --- /dev/null +++ b/core/org.osate.ui.tests/models/issue3234/.project @@ -0,0 +1,18 @@ + + + issue3234 + + + + + + org.eclipse.xtext.ui.shared.xtextBuilder + + + + + + org.osate.core.aadlnature + org.eclipse.xtext.ui.shared.xtextNature + + diff --git a/core/org.osate.ui.tests/models/issue3234/First_Properties.aadl b/core/org.osate.ui.tests/models/issue3234/First_Properties.aadl new file mode 100644 index 00000000000..a9eb7db4c59 --- /dev/null +++ b/core/org.osate.ui.tests/models/issue3234/First_Properties.aadl @@ -0,0 +1,3 @@ +property set First_Properties is + Value: aadlinteger applies to (thread); +end First_Properties; diff --git a/core/org.osate.ui.tests/models/issue3234/Issue3234.aadl b/core/org.osate.ui.tests/models/issue3234/Issue3234.aadl new file mode 100644 index 00000000000..26cd186a670 --- /dev/null +++ b/core/org.osate.ui.tests/models/issue3234/Issue3234.aadl @@ -0,0 +1,14 @@ +package Issue3234 +public + with First_Properties, Second_Properties; + + thread T + end T; + + thread implementation T.impl + properties + First_Properties::Value => 1; + Second_Properties::Value => 2; + Timing_Properties::Period => 10 ms; + end T.impl; +end Issue3234; diff --git a/core/org.osate.ui.tests/models/issue3234/Second_Properties.aadl b/core/org.osate.ui.tests/models/issue3234/Second_Properties.aadl new file mode 100644 index 00000000000..87f5b62daa2 --- /dev/null +++ b/core/org.osate.ui.tests/models/issue3234/Second_Properties.aadl @@ -0,0 +1,3 @@ +property set Second_Properties is + Value: aadlinteger applies to (thread); +end Second_Properties; diff --git a/core/org.osate.ui.tests/models/issue3234/Unused_Properties.aadl b/core/org.osate.ui.tests/models/issue3234/Unused_Properties.aadl new file mode 100644 index 00000000000..5668f5fa324 --- /dev/null +++ b/core/org.osate.ui.tests/models/issue3234/Unused_Properties.aadl @@ -0,0 +1,4 @@ +-- Not imported or applicable to the selected thread, but still selectable in the workspace group. +property set Unused_Properties is + Value: aadlinteger applies to (system); +end Unused_Properties; diff --git a/core/org.osate.ui.tests/src/org/osate/ui/tests/propertyview/AadlPropertyViewPropertySetFilterTest.java b/core/org.osate.ui.tests/src/org/osate/ui/tests/propertyview/AadlPropertyViewPropertySetFilterTest.java deleted file mode 100644 index 975b0bc79b6..00000000000 --- a/core/org.osate.ui.tests/src/org/osate/ui/tests/propertyview/AadlPropertyViewPropertySetFilterTest.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (c) 2004-2026 Carnegie Mellon University and others. (see Contributors file). - * All Rights Reserved. - * - * NO WARRANTY. ALL MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY - * KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE - * OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT - * MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT. - * - * This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * SPDX-License-Identifier: EPL-2.0 - * - * Created, in part, with funding and support from the United States Government. (see Acknowledgments file). - * - * This program includes and/or can make use of certain third party source code, object code, documentation and other - * files ("Third Party Software"). The Third Party Software that is used by this program is dependent upon your system - * configuration. By using this program, You agree to comply with any and all relevant Third Party Software terms and - * conditions contained in any such Third Party Software or separate license file distributed with such Third Party - * Software. The parties who own the Third Party Software ("Third Party Licensors") are intended third party benefici- - * aries to this license with respect to the terms applicable to their Third Party Software. Third Party Software li- - * censes only apply to the Third Party Software and not any other portion of this program or this program as a whole. - */ -package org.osate.ui.tests.propertyview; - -import static org.osate.ui.tests.propertyview.util.AadlPropertyViewBot.assertAbsent; -import static org.osate.ui.tests.propertyview.util.AadlPropertyViewBot.assertPresent; -import static org.osate.ui.tests.propertyview.util.AadlPropertyViewBot.assertPropertySets; -import static org.osate.ui.tests.propertyview.util.AadlPropertyViewBot.filterPropertySets; -import static org.osate.ui.tests.propertyview.util.AadlPropertyViewBot.showDefaultValues; -import static org.osate.ui.tests.propertyview.util.AadlPropertyViewBot.showUndefinedProperties; -import static org.osate.ui.tests.propertyview.util.WorkbenchTestUtil.selectInEditor; - -import java.util.List; - -import org.eclipse.swtbot.swt.finder.junit.SWTBotJunit4ClassRunner; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests the "Property Set Filters..." dialog in the view menu of the AADL Property Values view. - *

- * This is a class of its own with a single test because the view has no way back to not filtering by property set at - * all: once the dialog has been confirmed the view keeps applying whatever filter it was given. Every test class opens - * the view from scratch, so a following class is unaffected. - */ -@RunWith(SWTBotJunit4ClassRunner.class) -public class AadlPropertyViewPropertySetFilterTest extends AadlPropertyViewTestBase { - @Test - public void testPropertySetFilterDialog() { - selectInEditor(MODEL_FILE, VALUED_IMPL); - - /* Bring in the property sets contributed by plug-ins, which is what the dialog lists. */ - showUndefinedProperties(true); - showDefaultValues(true); - assertPresent(NOT_IMPORTED_PROPERTY_SET); - assertPresent(PROPERTY_SET, "Int_Property"); - - /* Deselecting every property set empties the tree. */ - filterPropertySets(false); - assertPropertySets(List.of()); - - /* Selecting every property set brings the contributed property sets back. */ - filterPropertySets(true); - assertPresent(NOT_IMPORTED_PROPERTY_SET); - - /* - * The dialog only lists the property sets that plug-ins contribute, so a property set that lives in the - * workspace can never be selected in it and stays hidden even after "Select All". This is the current behavior - * of the view, not necessarily the intended one. - */ - assertAbsent(PROPERTY_SET); - assertAbsent(EXTRA_PROPERTY_SET); - } -} diff --git a/core/org.osate.ui.tests/src/org/osate/ui/tests/propertyview/Issue3234Test.java b/core/org.osate.ui.tests/src/org/osate/ui/tests/propertyview/Issue3234Test.java new file mode 100644 index 00000000000..54f6aa5ba23 --- /dev/null +++ b/core/org.osate.ui.tests/src/org/osate/ui/tests/propertyview/Issue3234Test.java @@ -0,0 +1,182 @@ +/** + * Copyright (c) 2004-2026 Carnegie Mellon University and others. (see Contributors file). + * All Rights Reserved. + * + * NO WARRANTY. ALL MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY + * KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE + * OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT + * MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT. + * + * This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * SPDX-License-Identifier: EPL-2.0 + * + * Created, in part, with funding and support from the United States Government. (see Acknowledgments file). + * + * This program includes and/or can make use of certain third party source code, object code, documentation and other + * files ("Third Party Software"). The Third Party Software that is used by this program is dependent upon your system + * configuration. By using this program, You agree to comply with any and all relevant Third Party Software terms and + * conditions contained in any such Third Party Software or separate license file distributed with such Third Party + * Software. The parties who own the Third Party Software ("Third Party Licensors") are intended third party benefici- + * aries to this license with respect to the terms applicable to their Third Party Software. Third Party Software li- + * censes only apply to the Third Party Software and not any other portion of this program or this program as a whole. + */ +package org.osate.ui.tests.propertyview; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.osate.ui.tests.propertyview.util.AadlPropertyViewBot.assertPropertySets; +import static org.osate.ui.tests.propertyview.util.WorkbenchTestUtil.assertNoErrorsInProject; +import static org.osate.ui.tests.propertyview.util.WorkbenchTestUtil.closeAllEditors; +import static org.osate.ui.tests.propertyview.util.WorkbenchTestUtil.deleteProject; +import static org.osate.ui.tests.propertyview.util.WorkbenchTestUtil.importModelProject; +import static org.osate.ui.tests.propertyview.util.WorkbenchTestUtil.openEditor; +import static org.osate.ui.tests.propertyview.util.WorkbenchTestUtil.prepareWorkbench; +import static org.osate.ui.tests.propertyview.util.WorkbenchTestUtil.selectInEditor; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot; +import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable; +import org.eclipse.swtbot.swt.finder.junit.SWTBotJunit4ClassRunner; +import org.eclipse.swtbot.swt.finder.waits.Conditions; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.osate.ui.tests.propertyview.util.AadlPropertyViewBot; + +/** + * Workspace property sets must be selectable in the property view's filter dialog, alongside contributed sets. + */ +@RunWith(SWTBotJunit4ClassRunner.class) +public class Issue3234Test { + private static final String PROJECT = "issue3234"; + private static final String MODEL_FILE = "Issue3234.aadl"; + private static final String WORKSPACE_GROUP = "Workspace Property Sets"; + private static final List ALL_SETS = List.of("First_Properties", "Second_Properties", "Timing_Properties"); + private static final SWTWorkbenchBot bot = new SWTWorkbenchBot(); + private static IProject project; + private SWTBotShell dialog; + + @BeforeClass + public static void importModel() { + prepareWorkbench(); + project = importModelProject(PROJECT); + assertNoErrorsInProject(project); + } + + @Before + public void openView() { + AadlPropertyViewBot.reopen(); + openEditor(project, MODEL_FILE); + selectInEditor(MODEL_FILE, "implementation T.impl"); + assertPropertySets(ALL_SETS); + } + + @After + public void closeDialogAndEditor() { + if (dialog != null && dialog.isOpen()) { + closeDialog("Cancel"); + } + closeAllEditors(); + AadlPropertyViewBot.close(); + } + + @AfterClass + public static void discardModel() { + deleteProject(PROJECT); + } + + @Test + public void selectAllPreservesWorkspacePropertySets() { + filterPropertySets(true); + assertPropertySets(ALL_SETS); + + filterPropertySets(false); + assertPropertySets(List.of()); + + filterPropertySets(true); + assertPropertySets(ALL_SETS); + } + + @Test + public void workspaceGroupOrderAndIndividualSelections() { + openDialog(); + var groups = dialog.bot().tree().getAllItems(); + assertEquals("Predeclared Property Sets", groups[0].getText()); + assertTrue(groups[0].expand().getNodes().contains("Timing_Properties")); + assertEquals(WORKSPACE_GROUP, groups[1].getText()); + var workspace = groups[1].expand(); + assertEquals(List.of("First_Properties", "Second_Properties", "Unused_Properties"), + Arrays.stream(workspace.getItems()).map(SWTBotTreeItem::getText).toList()); + workspace.getNode("First_Properties").uncheck(); + assertTrue(workspace.isChecked()); + assertTrue(workspace.isGrayed()); + closeDialog("OK"); + assertPropertySets(List.of("Second_Properties", "Timing_Properties")); + + openDialog(); + workspace = dialog.bot().tree().getTreeItem(WORKSPACE_GROUP).expand(); + assertFalse(workspace.getNode("First_Properties").isChecked()); + assertTrue(workspace.getNode("Second_Properties").isChecked()); + assertTrue(workspace.isGrayed()); + dialog.bot().button("Select All").click(); + closeDialog("Cancel"); + assertPropertySets(List.of("Second_Properties", "Timing_Properties")); + + openDialog(); + workspace = dialog.bot().tree().getTreeItem(WORKSPACE_GROUP).expand(); + assertFalse(workspace.getNode("First_Properties").isChecked()); + workspace.uncheck(); + closeDialog("OK"); + assertPropertySets(List.of("Timing_Properties")); + + openDialog(); + workspace = dialog.bot().tree().getTreeItem(WORKSPACE_GROUP).expand(); + assertFalse(workspace.isChecked()); + assertFalse(workspace.isGrayed()); + workspace.check(); + closeDialog("OK"); + assertPropertySets(ALL_SETS); + } + + private void openDialog() { + var view = AadlPropertyViewBot.view(); + // Invoke the existing action without entering Cocoa's native popup-menu loop. + UIThreadRunnable.asyncExec(() -> { + var menu = view.getViewReference().getView(false).getViewSite().getActionBars().getMenuManager(); + for (var item : menu.getItems()) { + if (item instanceof ActionContributionItem contribution + && contribution.getAction().getText().equals("Property Set Filters...")) { + contribution.getAction().run(); + return; + } + } + fail("Property Set Filters action is missing"); + }); + dialog = bot.shell("Property Set Filters"); + dialog.activate(); + } + + private void filterPropertySets(boolean selectAll) { + openDialog(); + dialog.bot().button(selectAll ? "Select All" : "Deselect All").click(); + closeDialog("OK"); + } + + private void closeDialog(String button) { + dialog.bot().button(button).click(); + bot.waitUntil(Conditions.shellCloses(dialog)); + dialog = null; + } +} diff --git a/core/org.osate.xtext.aadl2.ui/src/org/osate/xtext/aadl2/ui/propertyview/AadlPropertyView.java b/core/org.osate.xtext.aadl2.ui/src/org/osate/xtext/aadl2/ui/propertyview/AadlPropertyView.java index 905cfca8c90..a540a613e2b 100644 --- a/core/org.osate.xtext.aadl2.ui/src/org/osate/xtext/aadl2/ui/propertyview/AadlPropertyView.java +++ b/core/org.osate.xtext.aadl2.ui/src/org/osate/xtext/aadl2/ui/propertyview/AadlPropertyView.java @@ -88,6 +88,7 @@ import org.eclipse.ui.part.ViewPart; import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.resource.EObjectAtOffsetHelper; +import org.eclipse.xtext.resource.IResourceDescriptionsProvider; import org.eclipse.xtext.resource.SaveOptions; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.scoping.IScopeProvider; @@ -99,6 +100,7 @@ import org.eclipse.xtext.ui.editor.outline.impl.EObjectNode; import org.eclipse.xtext.util.concurrent.IUnitOfWork; import org.osate.aadl2.Aadl2Factory; +import org.osate.aadl2.Aadl2Package; import org.osate.aadl2.AadlPackage; import org.osate.aadl2.BasicProperty; import org.osate.aadl2.BasicPropertyAssociation; @@ -214,6 +216,9 @@ public class AadlPropertyView extends ViewPart { @Inject private IURIEditorOpener editorOpener; + @Inject + private IResourceDescriptionsProvider resourceDescriptionsProvider; + private URI previousSelectionURI; private CachePropertyLookupJob cachePropertyLookupJob; @@ -535,8 +540,16 @@ public void run() { var propertySetFiltersAction = new Action("Property Set Filters...") { @Override public void run() { - var dialog = filteredPropertySets == null ? new PropertySetFilterDialog(getViewSite().getShell()) - : new PropertySetFilterDialog(getViewSite().getShell(), filteredPropertySets); + var workspacePropertySets = new LinkedHashMap(); + var descriptions = resourceDescriptionsProvider.getResourceDescriptions(new ResourceSetImpl()); + for (var description : descriptions.getExportedObjectsByType(Aadl2Package.eINSTANCE.getPropertySet())) { + var uri = description.getEObjectURI().trimFragment(); + if (uri.isPlatformResource()) { + workspacePropertySets.put(uri, description.getName().toString()); + } + } + var dialog = new PropertySetFilterDialog(getViewSite().getShell(), workspacePropertySets, + filteredPropertySets); if (dialog.open() == Window.OK) { filteredPropertySets = dialog.getSelectedPropertySets(); treeViewer.refresh(); diff --git a/core/org.osate.xtext.aadl2.ui/src/org/osate/xtext/aadl2/ui/propertyview/PropertySetFilterDialog.java b/core/org.osate.xtext.aadl2.ui/src/org/osate/xtext/aadl2/ui/propertyview/PropertySetFilterDialog.java index 5e9c4546a29..b50e2afbaeb 100644 --- a/core/org.osate.xtext.aadl2.ui/src/org/osate/xtext/aadl2/ui/propertyview/PropertySetFilterDialog.java +++ b/core/org.osate.xtext.aadl2.ui/src/org/osate/xtext/aadl2/ui/propertyview/PropertySetFilterDialog.java @@ -53,18 +53,17 @@ import org.osate.pluginsupport.PluginSupportUtil; class PropertySetFilterDialog extends Dialog { - private final DialogContentProvider contentProvider = new DialogContentProvider(); + private static final List PREDECLARED_PATH = List.of("Predeclared_Property_Sets"); + + private final DialogContentProvider contentProvider; private CheckboxTreeViewer treeViewer; private Set selectedPropertySets; - PropertySetFilterDialog(Shell parentShell) { - super(parentShell); - } - - PropertySetFilterDialog(Shell parentShell, Set selectedPropertySets) { + PropertySetFilterDialog(Shell parentShell, Map workspacePropertySets, Set selectedPropertySets) { super(parentShell); + contentProvider = new DialogContentProvider(workspacePropertySets); this.selectedPropertySets = selectedPropertySets; } @@ -99,10 +98,11 @@ private CheckboxTreeViewer createTreeViewer(Composite composite) { viewer.setComparator(new ViewerComparator(String.CASE_INSENSITIVE_ORDER) { @Override public int category(Object element) { - /* Directories are shown above the property sets of the directory that contains them. */ + /* Predeclared and workspace groups come first, followed by other directories and property sets. */ return switch (element) { - case ContributedDirectory directory -> 0; - case ContributedPropertySet propertySet -> 1; + case ContributedDirectory directory -> directory.path().equals(PREDECLARED_PATH) ? 0 : 2; + case WorkspacePropertySets workspace -> 1; + case PropertySetEntry propertySet -> 3; case null, default -> throw new AssertionError("Unexpected element: " + element); }; } @@ -117,7 +117,7 @@ public void checkStateChanged(CheckStateChangedEvent event) { } private void setChildrenChecked(Object element, boolean checked) { - if (element instanceof ContributedDirectory) { + if (contentProvider.hasChildren(element)) { for (var child : contentProvider.getChildren(element)) { viewer.setChecked(child, checked); setChildrenChecked(child, checked); @@ -127,7 +127,7 @@ private void setChildrenChecked(Object element, boolean checked) { private void setParentCheckState(Object element, boolean checked) { var parent = contentProvider.getParent(element); - if (parent instanceof ContributedDirectory) { + if (contentProvider.hasChildren(parent)) { var children = contentProvider.getChildren(parent); if (Arrays.stream(children).anyMatch(child -> viewer.getChecked(child) != checked)) { viewer.setGrayChecked(parent, true); @@ -143,28 +143,20 @@ private void setParentCheckState(Object element, boolean checked) { viewer.setCheckStateProvider(new ICheckStateProvider() { @Override public boolean isChecked(Object element) { - return switch (element) { - case ContributedDirectory directory -> - Arrays.stream(contentProvider.getChildren(element)).anyMatch(this::isChecked); - case ContributedPropertySet propertySet -> selectedPropertySets.contains(propertySet.uri()); - case null, default -> throw new AssertionError("Unexpected element: " + element); - }; + if (element instanceof PropertySetEntry propertySet) { + return selectedPropertySets.contains(propertySet.uri()); + } + return Arrays.stream(contentProvider.getChildren(element)).anyMatch(this::isChecked); } @Override public boolean isGrayed(Object element) { - return switch (element) { - case ContributedDirectory directory -> { - var children = contentProvider.getChildren(element); - if (Arrays.stream(children).anyMatch(this::isGrayed)) { - yield true; - } - var checkedChildrenCount = Arrays.stream(children).filter(this::isChecked).count(); - yield checkedChildrenCount > 0 && checkedChildrenCount < children.length; + var children = contentProvider.getChildren(element); + if (Arrays.stream(children).anyMatch(this::isGrayed)) { + return true; } - case ContributedPropertySet propertySet -> false; - case null, default -> throw new AssertionError("Unexpected element: " + element); - }; + var checkedChildrenCount = Arrays.stream(children).filter(this::isChecked).count(); + return checkedChildrenCount > 0 && checkedChildrenCount < children.length; } }); } @@ -205,8 +197,8 @@ protected boolean isResizable() { @Override protected void okPressed() { selectedPropertySets = Arrays.stream(treeViewer.getCheckedElements()) - .filter(ContributedPropertySet.class::isInstance) - .map(element -> ((ContributedPropertySet) element).uri()) + .filter(PropertySetEntry.class::isInstance) + .map(element -> ((PropertySetEntry) element).uri()) .collect(Collectors.toCollection(LinkedHashSet::new)); super.okPressed(); } @@ -221,11 +213,18 @@ private static boolean isPrefix(List first, List second) { private record ContributedDirectory(Object parent, List path) { @Override public String toString() { - return path.getLast(); + return path.equals(PREDECLARED_PATH) ? "Predeclared Property Sets" : path.getLast(); } } - private record ContributedPropertySet(Object parent, URI uri, String name) { + private record WorkspacePropertySets(Map propertySets) { + @Override + public String toString() { + return "Workspace Property Sets"; + } + } + + private record PropertySetEntry(Object parent, URI uri, String name) { @Override public String toString() { return name; @@ -233,27 +232,41 @@ public String toString() { } private static class DialogContentProvider implements ITreeContentProvider { + private final WorkspacePropertySets workspacePropertySets; + + DialogContentProvider(Map workspacePropertySets) { + this.workspacePropertySets = new WorkspacePropertySets(Map.copyOf(workspacePropertySets)); + } + @Override public Object[] getElements(Object inputElement) { @SuppressWarnings("unchecked") var propertySets = (Map) inputElement; - return propertySets.entrySet().stream(). map(entry -> { + var contributedElements = propertySets.entrySet().stream(). map(entry -> { var uri = entry.getKey(); var firstSignificantIndex = PluginSupportUtil.getFirstSignificantIndex(uri); if (isPropertySetItself(uri, firstSignificantIndex)) { - return new ContributedPropertySet(inputElement, uri, entry.getValue()); + return new PropertySetEntry(inputElement, uri, entry.getValue()); } return new ContributedDirectory(inputElement, List.of(uri.segment(firstSignificantIndex.getAsInt()))); - }).distinct().toArray(); + }).distinct(); + return Stream.concat(contributedElements, Stream.of(workspacePropertySets)).toArray(); } @Override public boolean hasChildren(Object element) { - return element instanceof ContributedDirectory; + return element instanceof ContributedDirectory || element instanceof WorkspacePropertySets; } @Override public Object[] getChildren(Object parentElement) { + if (parentElement instanceof WorkspacePropertySets workspace) { + return workspace.propertySets() + .entrySet() + .stream() + .map(entry -> new PropertySetEntry(workspace, entry.getKey(), entry.getValue())) + .toArray(); + } if (!(parentElement instanceof ContributedDirectory directory)) { return new Object[0]; } @@ -267,7 +280,7 @@ public Object[] getChildren(Object parentElement) { var nextSignificantIndex = PluginSupportUtil.getFirstSignificantIndex(uri).getAsInt() + directoryPath.size(); if (nextSignificantIndex == uri.segmentCount() - 1) { - return new ContributedPropertySet(parentElement, uri, entry.getValue()); + return new PropertySetEntry(parentElement, uri, entry.getValue()); } var childPath = Stream .concat(directoryPath.stream(), Stream.of(uri.segment(nextSignificantIndex))) @@ -282,7 +295,8 @@ public Object[] getChildren(Object parentElement) { public Object getParent(Object element) { return switch (element) { case ContributedDirectory directory -> directory.parent(); - case ContributedPropertySet propertySet -> propertySet.parent(); + case WorkspacePropertySets workspace -> null; + case PropertySetEntry propertySet -> propertySet.parent(); case null, default -> throw new AssertionError("Unexpected element: " + element); }; }