From b208c26d50d28b5f9619327de9a0b980119993ef Mon Sep 17 00:00:00 2001 From: Lutz Wrage Date: Thu, 17 Sep 2026 12:46:43 -0400 Subject: [PATCH 1/2] Add regression test for issue #3234 Replace the property-set filter characterization with a minimal SWTBot regression using two assigned workspace property sets and a contributed set. Validate the fixture before checking that Select All preserves workspace properties. Cover the requested group labels and order, an unimported workspace set, individual and group selection, reopening, and cancellation. Both tests fail on the existing dialog for the reported behavior. --- .../models/issue3234/.gitignore | 2 + .../models/issue3234/.project | 18 ++ .../models/issue3234/First_Properties.aadl | 3 + .../models/issue3234/Issue3234.aadl | 14 ++ .../models/issue3234/Second_Properties.aadl | 3 + .../models/issue3234/Unused_Properties.aadl | 4 + ...AadlPropertyViewPropertySetFilterTest.java | 75 -------- .../ui/tests/propertyview/Issue3234Test.java | 182 ++++++++++++++++++ 8 files changed, 226 insertions(+), 75 deletions(-) create mode 100644 core/org.osate.ui.tests/models/issue3234/.gitignore create mode 100644 core/org.osate.ui.tests/models/issue3234/.project create mode 100644 core/org.osate.ui.tests/models/issue3234/First_Properties.aadl create mode 100644 core/org.osate.ui.tests/models/issue3234/Issue3234.aadl create mode 100644 core/org.osate.ui.tests/models/issue3234/Second_Properties.aadl create mode 100644 core/org.osate.ui.tests/models/issue3234/Unused_Properties.aadl delete mode 100644 core/org.osate.ui.tests/src/org/osate/ui/tests/propertyview/AadlPropertyViewPropertySetFilterTest.java create mode 100644 core/org.osate.ui.tests/src/org/osate/ui/tests/propertyview/Issue3234Test.java 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; + } +} From f975ee116c10bc613d12e8666526e70bc78e80c8 Mon Sep 17 00:00:00 2001 From: Lutz Wrage Date: Thu, 17 Sep 2026 12:50:59 -0400 Subject: [PATCH 2/2] Include workspace property sets in the filter dialog Read workspace property sets from the Xtext index whenever the property view opens its filter dialog. Offer them in a Workspace Property Sets group immediately after the renamed Predeclared Property Sets group. Use the same URI-based selection and checkbox propagation for workspace and contributed sets so Select All restores workspace properties and individual selections survive reopening. Fixes #3234. --- .../ui/propertyview/AadlPropertyView.java | 17 +++- .../propertyview/PropertySetFilterDialog.java | 90 +++++++++++-------- 2 files changed, 67 insertions(+), 40 deletions(-) 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); }; }