ClassifierOperationDialog.java
/**
* Copyright (c) 2004-2025 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.ge.aadl2.ui.internal.dialogs;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Shell;
import org.osate.aadl2.ComponentCategory;
import org.osate.ge.aadl2.internal.util.classifiers.ClassifierOperation;
import org.osate.ge.aadl2.internal.util.classifiers.ClassifierOperationPart;
import org.osate.ge.aadl2.internal.util.classifiers.ClassifierOperationPartType;
import com.google.common.collect.ImmutableList;
/**
* Dialog used for creating or selecting a classifier.
* If a new implementation is being created, the dialog can also prompt to create a new type.
*
*/
public class ClassifierOperationDialog {
public static final String primaryPartIdentifier = "org.osate.ge.PrimaryPartIdentifier";
public static final String baseValueIdentifier = "org.osate.ge.BaseValueIdentifier";
static String NOT_SELECTED_LABEL = "<Not Selected>";
public static interface Model {
String getTitle();
String getMessage(final ClassifierOperation value);
Collection<?> getPackageOptions();
String getPrimarySelectTitle();
String getPrimarySelectMessage();
Collection<?> getPrimarySelectOptions();
Collection<?> getUnfilteredPrimarySelectOptions();
Collection<?> getBaseSelectOptions(final ClassifierOperationPartType primaryOperation);
Collection<?> getUnfilteredBaseSelectOptions(final ClassifierOperationPartType primaryOperation);
String validate(ClassifierOperation value);
}
private static class Arguments {
private Arguments(final Model model, final EnumSet<ClassifierOperationPartType> allowedOperations, final Object defaultPackage,
final Object defaultSelection, final boolean showPrimaryPackageSelector,
final ImmutableList<ComponentCategory> componentCategories) {
this.model = Objects.requireNonNull(model, "model must not be null");
this.allowedOperations = Objects.requireNonNull(allowedOperations, "allowedOperations must not be null");
this.defaultPackage = defaultPackage;
this.defaultSelection = defaultSelection;
this.showPrimaryPackageSelector = showPrimaryPackageSelector;
this.componentCategories = componentCategories;
if ((componentCategories == null || componentCategories.isEmpty())
&& allowedOperations.stream().anyMatch(op -> op == ClassifierOperationPartType.NEW_COMPONENT_TYPE
|| op == ClassifierOperationPartType.NEW_COMPONENT_IMPLEMENTATION)) {
throw new RuntimeException(
"Component category must not be null or empty if new component type or component implementation is allowed.");
}
}
public final Model model;
public final EnumSet<ClassifierOperationPartType> allowedOperations;
public final Object defaultPackage;
public final Object defaultSelection;
public final boolean showPrimaryPackageSelector;
public final ImmutableList<ComponentCategory> componentCategories; // For create component operations
}
public static class ArgumentBuilder {
private Model model;
private EnumSet<ClassifierOperationPartType> allowedOperations;
private Object defaultPackage;
private Object defaultSelection;
private boolean showPrimaryPackageSelector = true;
private ImmutableList<ComponentCategory> componentCategories = ImmutableList.of();
public ArgumentBuilder(final Model model, final EnumSet<ClassifierOperationPartType> allowedOperations) {
this.model = Objects.requireNonNull(model, "model must not be null");
this.allowedOperations = Objects.requireNonNull(allowedOperations, "allowedOperations must not be null");
}
public ArgumentBuilder defaultPackage(final Object value) {
this.defaultPackage = value;
return this;
}
public ArgumentBuilder defaultSelection(final Object value) {
this.defaultSelection = value;
return this;
}
public ArgumentBuilder showPrimaryPackageSelector(final boolean value) {
this.showPrimaryPackageSelector = value;
return this;
}
public ArgumentBuilder componentCategories(final ImmutableList<ComponentCategory> value) {
this.componentCategories = value;
return this;
}
public Arguments create() {
return new Arguments(model, allowedOperations, defaultPackage, defaultSelection,
showPrimaryPackageSelector, componentCategories);
}
}
private static class InnerDialog extends TitleAreaDialog {
private final Arguments args;
private ClassifierOperationPartEditor primaryPartEditor;
private ClassifierOperationPartEditor baseValueWidget;
private Group baseGroup;
protected InnerDialog(final Shell parentShell, final Arguments args) {
super(parentShell);
this.args = Objects.requireNonNull(args, "args must not be null");
setShellStyle(getShellStyle() | SWT.RESIZE);
this.setHelpAvailable(false);
}
@Override
protected void configureShell(final Shell newShell) {
super.configureShell(newShell);
newShell.setText(args.model.getTitle());
newShell.setMinimumSize(250, 50);
}
@Override
protected Point getInitialSize() {
final Point initialSize = super.getInitialSize();
return new Point(initialSize.x, Math.max(initialSize.y, 550));
}
@Override
public void create() {
super.create();
setTitle(args.model.getTitle());
updateMessage();
}
@Override
protected Control createDialogArea(final Composite parent) {
final Composite area = (Composite) super.createDialogArea(parent);
// Scrollable
final ScrolledComposite scrolled = new ScrolledComposite(area, SWT.H_SCROLL | SWT.V_SCROLL);
scrolled.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());
scrolled.setExpandVertical(true);
scrolled.setExpandHorizontal(true);
final Composite container = new Composite(scrolled, SWT.NONE);
container.setLayout(GridLayoutFactory.swtDefaults().numColumns(2).create());
// Determine initial component category selection. Set it if there is only one valid option.
final ComponentCategory initialComponentCategory = args.componentCategories.size() == 1
? args.componentCategories.get(0)
: null;
// Editor for the primary operation part
primaryPartEditor = new ClassifierOperationPartEditor(container, args.allowedOperations,
args.showPrimaryPackageSelector, args.componentCategories,
new ClassifierOperationPartEditor.Model() {
@Override
public Collection<?> getPackageOptions() {
return args.model.getPackageOptions();
}
@Override
public String getSelectTitle() {
return args.model.getPrimarySelectTitle();
}
@Override
public String getSelectMessage() {
return args.model.getPrimarySelectMessage();
}
@Override
public Collection<?> getSelectOptions() {
return args.model.getPrimarySelectOptions();
}
@Override
public Collection<?> getUnfilteredSelectOptions() {
return args.model.getUnfilteredPrimarySelectOptions();
}
});
primaryPartEditor.setIdentifierFieldTestingId(primaryPartIdentifier);
primaryPartEditor.setSelectedElement(args.defaultSelection);
primaryPartEditor.setSelectedComponentCategory(initialComponentCategory);
primaryPartEditor.setSelectedPackage(args.defaultPackage);
primaryPartEditor.setLayoutData(GridDataFactory.fillDefaults().span(2, 1).grab(true, false).create());
baseGroup = new Group(container, SWT.NONE);
baseGroup.setText("Base");
baseGroup.setLayout(GridLayoutFactory.swtDefaults().create());
baseGroup.setLayoutData(GridDataFactory.fillDefaults().span(2, 1).grab(true, false).create());
baseValueWidget = new ClassifierOperationPartEditor(baseGroup,
EnumSet.allOf(ClassifierOperationPartType.class), true, args.componentCategories,
new ClassifierOperationPartEditor.Model() {
@Override
public Collection<?> getPackageOptions() {
return args.model.getPackageOptions();
}
@Override
public String getSelectTitle() {
return "Select Base Classifier";
}
@Override
public String getSelectMessage() {
return "Select a base classifier.";
}
@Override
public Collection<?> getSelectOptions() {
return args.model
.getBaseSelectOptions(primaryPartEditor.getConfiguredOperation().getType());
}
@Override
public Collection<?> getUnfilteredSelectOptions() {
return args.model.getUnfilteredBaseSelectOptions(
primaryPartEditor.getConfiguredOperation().getType());
}
});
baseValueWidget.setIdentifierFieldTestingId(baseValueIdentifier);
baseValueWidget.setSelectedElement(args.defaultSelection);
baseValueWidget.setSelectedComponentCategory(initialComponentCategory);
baseValueWidget.setSelectedPackage(args.defaultPackage);
baseValueWidget.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).create());
// Update the base whenever the primary widget is updated
primaryPartEditor.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
updateBase();
validate();
}
});
baseValueWidget.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
validate();
}
});
updateBase();
// The set scrolled composite' content
scrolled.setContent(container);
scrolled.setMinSize(container.computeSize(SWT.DEFAULT, SWT.DEFAULT));
// Update the min size of the scrolled composite whenever the the size of the widgets change.
final ControlListener resizeListener = new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
scrolled.setMinSize(container.computeSize(SWT.DEFAULT, SWT.DEFAULT));
}
};
primaryPartEditor.addControlListener(resizeListener);
baseGroup.addControlListener(resizeListener);
return area;
}
@Override
protected void createButtonsForButtonBar(Composite parent) {
super.createButtonsForButtonBar(parent);
// Disable the OK button. Afterwards it will be updated based on validation results
getButton(IDialogConstants.OK_ID).setEnabled(false);
}
private void updateBase() {
final ClassifierOperationPart primaryPart = primaryPartEditor.getConfiguredOperation();
final ClassifierOperationPartType primaryOp = primaryPart.getType();
baseGroup.setVisible(ClassifierOperationPartType.isCreate(primaryOp));
// Set allowed component categories and selected component category for the base widget
final ComponentCategory cc = primaryPart.getComponentCategory();
baseValueWidget
.setAllowedComponentCategories(cc == null ? ImmutableList.of() : ImmutableList.of(cc));
baseValueWidget.setSelectedComponentCategory(cc);
if (baseGroup.getVisible()) {
switch (primaryOp) {
case NEW_COMPONENT_TYPE:
baseValueWidget
.setAllowedOperations(EnumSet.of(ClassifierOperationPartType.NONE, ClassifierOperationPartType.EXISTING));
// Set default value for base operation
if (baseValueWidget.getConfiguredOperation().getType() == null) {
baseValueWidget.setCurrentOperationPartType(ClassifierOperationPartType.NONE);
}
break;
case NEW_COMPONENT_IMPLEMENTATION:
baseValueWidget.setAllowedOperations(
EnumSet.of(ClassifierOperationPartType.NEW_COMPONENT_TYPE, ClassifierOperationPartType.EXISTING));
// Set default value for base operation
if (baseValueWidget.getConfiguredOperation().getType() == null) {
baseValueWidget.setCurrentOperationPartType(ClassifierOperationPartType.NEW_COMPONENT_TYPE);
}
break;
case NEW_FEATURE_GROUP_TYPE:
baseValueWidget
.setAllowedOperations(EnumSet.of(ClassifierOperationPartType.NONE, ClassifierOperationPartType.EXISTING));
// Set default value for base operation
if (baseValueWidget.getConfiguredOperation().getType() == null) {
baseValueWidget.setCurrentOperationPartType(ClassifierOperationPartType.NONE);
}
break;
default:
}
}
}
private void validate() {
final Button okBtn = getButton(IDialogConstants.OK_ID);
if(okBtn != null) {
final String errorMsg = args.model.validate(createResult());
setErrorMessage(errorMsg);
okBtn.setEnabled(errorMsg == null);
updateMessage();
}
}
private void updateMessage() {
setMessage(args.model.getMessage(createResult()), IMessageProvider.INFORMATION);
}
private ClassifierOperation createResult() {
return new ClassifierOperation(primaryPartEditor.getConfiguredOperation(),
baseValueWidget.getConfiguredOperation());
}
}
private final InnerDialog dlg;
private ClassifierOperationDialog(final Shell parentShell, final Arguments args) {
this.dlg = new InnerDialog(parentShell, args);
}
/**
* Returns if the user did not select OK.
* @return
*/
private ClassifierOperation open() {
if (dlg.open() == Window.OK) {
return dlg.createResult();
} else {
return null;
}
}
public static ClassifierOperation show(final Shell parentShell, final Arguments args) {
final ClassifierOperationDialog dlg = new ClassifierOperationDialog(parentShell, args);
return dlg.open();
}
public static void main(final String[] args) {
final Model testModel = new Model() {
@Override
public String getTitle() {
return "Select Element";
}
@Override
public String getMessage(final ClassifierOperation value) {
return "Select an element.";
}
@Override
public Collection<?> getPackageOptions() {
final List<Object> result = new ArrayList<>();
result.add("A");
result.add("B");
return result;
}
@Override
public String getPrimarySelectTitle() {
return "Select Element";
}
@Override
public String getPrimarySelectMessage() {
return "Select an element.";
}
@Override
public List<Object> getPrimarySelectOptions() {
final List<Object> result = new ArrayList<>();
result.add("C");
result.add("D");
return result;
}
@Override
public List<Object> getUnfilteredPrimarySelectOptions() {
final List<Object> result = getPrimarySelectOptions();
result.add("E");
result.add("F");
return result;
}
@Override
public List<Object> getBaseSelectOptions(final ClassifierOperationPartType primaryOperation) {
final List<Object> result = new ArrayList<>();
result.add("G");
result.add("H");
return result;
}
@Override
public Collection<?> getUnfilteredBaseSelectOptions(final ClassifierOperationPartType primaryOperation) {
final List<Object> result = getBaseSelectOptions(primaryOperation);
result.add("I");
result.add("J");
return result;
}
@Override
public String validate(final ClassifierOperation value) {
return (value.getPrimaryPart().getType() == ClassifierOperationPartType.NEW_COMPONENT_IMPLEMENTATION
&& value.getPrimaryPart().getIdentifier().isEmpty())
? "Primary identifier must not be empty."
: null;
}
};
Display.getDefault().syncExec(() -> {
final ClassifierOperation result = show(new Shell(),
new ArgumentBuilder(testModel,
EnumSet.complementOf(EnumSet.of(ClassifierOperationPartType.NONE)))
.componentCategories(ImmutableList.of(ComponentCategory.ABSTRACT)).create());
System.out.println("Result: " + result);
});
}
}