SetFeatureClassifierPropertySection.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.properties;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.Adapters;
import org.eclipse.e4.core.contexts.EclipseContextFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jface.viewers.IFilter;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.views.properties.tabbed.AbstractPropertySection;
import org.eclipse.ui.views.properties.tabbed.ITabbedPropertyConstants;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.eclipse.xtext.util.Strings;
import org.osate.aadl2.Aadl2Package;
import org.osate.aadl2.AadlPackage;
import org.osate.aadl2.AbstractFeature;
import org.osate.aadl2.AbstractFeatureClassifier;
import org.osate.aadl2.BusFeatureClassifier;
import org.osate.aadl2.Classifier;
import org.osate.aadl2.ComponentCategory;
import org.osate.aadl2.DataClassifier;
import org.osate.aadl2.DataSubcomponentType;
import org.osate.aadl2.Feature;
import org.osate.aadl2.FeatureClassifier;
import org.osate.aadl2.FeatureGroup;
import org.osate.aadl2.FeatureType;
import org.osate.aadl2.NamedElement;
import org.osate.aadl2.PackageSection;
import org.osate.aadl2.Prototype;
import org.osate.aadl2.SubprogramClassifier;
import org.osate.aadl2.SubprogramGroupSubcomponentType;
import org.osate.aadl2.SubprogramSubcomponentType;
import org.osate.ge.BusinessObjectSelection;
import org.osate.ge.aadl2.AadlImportsUtil;
import org.osate.ge.aadl2.internal.util.AadlClassifierUtil;
import org.osate.ge.aadl2.internal.util.AadlPrototypeUtil;
import org.osate.ge.aadl2.internal.util.AgeAadlUtil;
import org.osate.ge.aadl2.internal.util.classifiers.ClassifierOperation;
import org.osate.ge.aadl2.internal.util.classifiers.ClassifierOperationExecutor;
import org.osate.ge.aadl2.internal.util.classifiers.ClassifierOperationPartType;
import org.osate.ge.aadl2.ui.AadlModelAccessUtil;
import org.osate.ge.aadl2.ui.internal.AadlUiUtil;
import org.osate.ge.aadl2.ui.internal.dialogs.ClassifierOperationDialog;
import org.osate.ge.aadl2.ui.internal.dialogs.DefaultCreateSelectClassifierDialogModel;
import org.osate.ge.aadl2.ui.internal.dialogs.ElementSelectionDialog;
import org.osate.ge.internal.operations.OperationExecutor;
import org.osate.ge.internal.services.AadlModificationService;
import org.osate.ge.internal.ui.util.InternalPropertySectionUtil;
import org.osate.ge.operations.Operation;
import org.osate.ge.services.ReferenceBuilderService;
import org.osate.ge.ui.PropertySectionUtil;
import org.osgi.framework.FrameworkUtil;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
public class SetFeatureClassifierPropertySection extends AbstractPropertySection {
public static class Filter implements IFilter {
@Override
public boolean select(final Object toTest) {
return PropertySectionUtil.isBoCompatible(toTest, bo -> {
if (bo instanceof Feature) {
final Feature feature = (Feature) bo;
return featureTypeToMetadataMap.containsKey(feature.eClass());
}
return false;
});
}
}
private static Map<EClass, FeatureClassifierMetadata> featureTypeToMetadataMap = new HashMap<EClass, FeatureClassifierMetadata>();
static {
final Aadl2Package p = Aadl2Package.eINSTANCE;
featureTypeToMetadataMap.put(p.getBusAccess(), new FeatureClassifierMetadata(
p.getBusFeatureClassifier(), BusFeatureClassifier.class, "setBusFeatureClassifier",
ImmutableList.of(ComponentCategory.BUS, ComponentCategory.VIRTUAL_BUS)));
featureTypeToMetadataMap.put(p.getDataAccess(), new FeatureClassifierMetadata(
p.getDataSubcomponentType(), DataSubcomponentType.class, "setDataFeatureClassifier",
ImmutableList.of(ComponentCategory.DATA)));
featureTypeToMetadataMap.put(p.getSubprogramAccess(), new FeatureClassifierMetadata(
p.getSubprogramSubcomponentType(), SubprogramSubcomponentType.class, "setSubprogramFeatureClassifier",
ImmutableList.of(ComponentCategory.SUBPROGRAM)));
featureTypeToMetadataMap.put(p.getSubprogramGroupAccess(),
new FeatureClassifierMetadata(p.getSubprogramGroupSubcomponentType(),
SubprogramGroupSubcomponentType.class, "setSubprogramGroupFeatureClassifier",
ImmutableList.of(ComponentCategory.SUBPROGRAM_GROUP)));
featureTypeToMetadataMap.put(p.getAbstractFeature(), new FeatureClassifierMetadata(
p.getAbstractFeatureClassifier(), AbstractFeatureClassifier.class, "setAbstractFeatureClassifier",
ImmutableList.of(ComponentCategory.ABSTRACT, ComponentCategory.DATA, ComponentCategory.BUS,
ComponentCategory.VIRTUAL_BUS, ComponentCategory.SUBPROGRAM,
ComponentCategory.SUBPROGRAM_GROUP)));
featureTypeToMetadataMap.put(p.getFeatureGroup(),
new FeatureClassifierMetadata(p.getFeatureType(), FeatureType.class, "setFeatureType",
ImmutableList.of()));
featureTypeToMetadataMap.put(p.getParameter(), new FeatureClassifierMetadata(
p.getDataSubcomponentType(), DataSubcomponentType.class, "setDataFeatureClassifier",
ImmutableList.of(ComponentCategory.DATA)));
featureTypeToMetadataMap.put(p.getDataPort(), new FeatureClassifierMetadata(
p.getDataSubcomponentType(), DataSubcomponentType.class, "setDataFeatureClassifier",
ImmutableList.of(ComponentCategory.DATA)));
featureTypeToMetadataMap.put(p.getEventDataPort(), new FeatureClassifierMetadata(
p.getDataSubcomponentType(), DataSubcomponentType.class, "setDataFeatureClassifier",
ImmutableList.of(ComponentCategory.DATA)));
featureTypeToMetadataMap.put(p.getEventDataSource(),
new FeatureClassifierMetadata(p.getDataClassifier(), DataClassifier.class, "setDataClassifier",
ImmutableList.of(ComponentCategory.DATA)));
featureTypeToMetadataMap.put(p.getPortProxy(),
new FeatureClassifierMetadata(p.getDataClassifier(), DataClassifier.class, "setDataClassifier", ImmutableList.of(ComponentCategory.BUS)));
featureTypeToMetadataMap.put(p.getSubprogramProxy(), new FeatureClassifierMetadata(
p.getSubprogramClassifier(), SubprogramClassifier.class, "setSubprogramClassifier",
ImmutableList.of(ComponentCategory.SUBPROGRAM)));
}
/**
* Holds information used to implement property section functionality that is specific to the type of feature.
*
*/
private static class FeatureClassifierMetadata {
private final EClass classifierEClass;
private final Class<?> classifierClass;
private final String setterName;
private final ImmutableList<ComponentCategory> createComponentCategories; // Component categories which are allowed if creating a new one.
public FeatureClassifierMetadata(final EClass classifierEClass, final Class<?> classifierClass,
final String setterName, final ImmutableList<ComponentCategory> componentCategories) {
this.classifierEClass = classifierEClass;
this.classifierClass = classifierClass;
this.setterName = setterName;
this.createComponentCategories = componentCategories;
}
}
private BusinessObjectSelection selectedBos;
private Label curFeatureClassifier;
private Button chooseBtn;
private Button createBtn;
@Override
public void createControls(final Composite parent, final TabbedPropertySheetPage aTabbedPropertySheetPage) {
super.createControls(parent, aTabbedPropertySheetPage);
FormData fd;
final Composite composite = getWidgetFactory().createFlatFormComposite(parent);
final Composite container = getWidgetFactory().createComposite(composite);
final Label sectionLabel = PropertySectionUtil.createSectionLabel(composite, getWidgetFactory(),
"Classifier:");
container.setLayout(new FormLayout());
fd = new FormData();
fd.left = new FormAttachment(0, STANDARD_LABEL_WIDTH);
fd.top = new FormAttachment(sectionLabel, 0, SWT.CENTER);
container.setLayoutData(fd);
curFeatureClassifier = getWidgetFactory().createLabel(container, new String());
fd = new FormData();
fd.left = new FormAttachment(0, 0);
fd.top = new FormAttachment(0, ITabbedPropertyConstants.VSPACE);
curFeatureClassifier.setLayoutData(fd);
chooseBtn = InternalPropertySectionUtil.createButton(getWidgetFactory(), container, null, setClassifierListener,
"Choose...", SWT.PUSH);
fd = new FormData();
fd.left = new FormAttachment(curFeatureClassifier, ITabbedPropertyConstants.HSPACE);
fd.top = new FormAttachment(curFeatureClassifier, 0, SWT.CENTER);
chooseBtn.setLayoutData(fd);
createBtn = InternalPropertySectionUtil.createButton(getWidgetFactory(), container, null,
createClassifierListener, "Create...", SWT.PUSH);
fd = new FormData();
fd.left = new FormAttachment(chooseBtn, ITabbedPropertyConstants.HSPACE);
fd.top = new FormAttachment(chooseBtn, 0, SWT.CENTER);
createBtn.setLayoutData(fd);
InternalPropertySectionUtil.setPropertiesHelp(aTabbedPropertySheetPage.getControl());
}
final SelectionListener setClassifierListener = new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
final List<Feature> features = selectedBos.boStream(Feature.class).collect(Collectors.toList());
final Iterator<Feature> it = features.iterator();
final Feature feature = it.next();
final List<Object> potentialFeatureClassifiers = new ArrayList<>(getPotentialFeatureClassifiers(feature));
// List will contain classifiers that are available to all selected elements
while (it.hasNext()) {
potentialFeatureClassifiers.retainAll(getPotentialFeatureClassifiers(it.next()));
}
// Prompt the user for the element
final ElementSelectionDialog dlg = new ElementSelectionDialog(Display.getCurrent().getActiveShell(),
"Select a Classifier", "Select a classifier.", potentialFeatureClassifiers);
if (dlg.open() != Window.CANCEL) {
// Import the package if necessary
final EObject selectedType;
if (dlg.getFirstSelectedElement() != null) {
// Resolve the reference
selectedType = EcoreUtil.resolve((EObject) dlg.getFirstSelectedElement(), feature.eResource());
} else {
selectedType = null;
}
// Set the classifier
selectedBos.modify(Feature.class, f -> {
setFeatureClassifier(f, selectedType);
});
}
}
};
final SelectionListener createClassifierListener = new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
final List<Feature> features = selectedBos.boStream(Feature.class).collect(Collectors.toList());
// It should be safe to assume that the EClass of all selected feature match because the button would be disabled otherwise.
final FeatureClassifierMetadata metadata = featureTypeToMetadataMap.get(features.get(0).eClass());
// Get required services
final IEclipseContext context = EclipseContextFactory
.getServiceContext(FrameworkUtil.getBundle(getClass()).getBundleContext());
final AadlModificationService aadlModService = Objects.requireNonNull(
context.getActive(AadlModificationService.class), "Unable to retrieve AADL modification service");
final ReferenceBuilderService referenceBuilder = Objects.requireNonNull(
context.getActive(ReferenceBuilderService.class), "Unable to retrieve reference builder service");
// Determine project to use
final IProject project = AgeAadlUtil.getCommonProject(features)
.orElseThrow(() -> new RuntimeException("Unable to determine project"));
// Get the resource set to use. Use the resource set from the selected model elements to ensure the proper model elements are modified.
// If the resource set did not match for all model elements, the create button would be disabled
final ResourceSet rs = features.get(0).eResource().getResourceSet();
final ClassifierOperationDialog.Model model = new DefaultCreateSelectClassifierDialogModel(
rs, "Configure classifier.") {
@Override
public String getTitle() {
return "Create Classifier";
}
@Override
public Collection<?> getPackageOptions() {
return AgeAadlUtil.getEditablePackages(project);
}
@Override
public Collection<?> getBaseSelectOptions(final ClassifierOperationPartType primaryOperation) {
return metadata.createComponentCategories.stream()
.map(cc -> AadlClassifierUtil.getValidBaseClassifierDescriptions(project, cc,
primaryOperation == ClassifierOperationPartType.NEW_COMPONENT_IMPLEMENTATION))
.reduce(Sets::union).orElse(Collections.emptySet());
}
};
// Show the dialog to determine the operation
final boolean isFeatureGroup = metadata.classifierEClass == Aadl2Package.eINSTANCE.getFeatureType();
final ClassifierOperationDialog.ArgumentBuilder argBuilder = new ClassifierOperationDialog.ArgumentBuilder(
model,
isFeatureGroup ? EnumSet.of(ClassifierOperationPartType.NEW_FEATURE_GROUP_TYPE)
: EnumSet.of(ClassifierOperationPartType.NEW_COMPONENT_TYPE,
ClassifierOperationPartType.NEW_COMPONENT_IMPLEMENTATION))
.defaultPackage(AgeAadlUtil.getCommonPackage(features).orElse(null))
.componentCategories(metadata.createComponentCategories);
final ClassifierOperation classifierOp = ClassifierOperationDialog
.show(Display.getCurrent().getActiveShell(), argBuilder.create());
if (classifierOp != null) {
final Operation op = Operation.createWithBuilder(opBuilder -> {
// Add actual operation steps to the operation builder based on the classifier operation
final ClassifierOperationExecutor classifierOperationHandler = new ClassifierOperationExecutor(
rs, project);
opBuilder = classifierOperationHandler.execute(opBuilder, classifierOp, null);
// Modify the subcomponents based on the result of the classifier operation
selectedBos.modifyWithOperation(opBuilder, Feature.class, (featureToModify, classifier) -> {
final EObject resolvedClassifier = AadlUiUtil
.resolveWithLiveResourceSetIfProject((EObject) classifier, project);
setFeatureClassifier(featureToModify, resolvedClassifier);
});
});
// Execute the operation
new OperationExecutor(aadlModService, referenceBuilder).execute(op);
}
}
};
@Override
public void setInput(final IWorkbenchPart part, final ISelection selection) {
super.setInput(part, selection);
selectedBos = Adapters.adapt(selection, BusinessObjectSelection.class);
}
@Override
public void refresh() {
final List<Feature> features = selectedBos.boStream(Feature.class).collect(Collectors.toList());
curFeatureClassifier.setText(getFeatureClassifierLabel(features));
createBtn.setEnabled(
!features.isEmpty() && features.stream().allMatch(f -> f.eClass() == features.get(0).eClass())
&& AadlUiUtil.allHaveSameValidResourceSet(features)
&& AgeAadlUtil.getCommonProject(features).isPresent());
}
private static String getFeatureClassifierLabel(final List<Feature> features) {
final Iterator<Feature> it = features.iterator();
final EObject fc = getAllFeatureClassifier(it.next());
while (it.hasNext()) {
// If feature classifiers are not the same, set to multiple
if (fc != getAllFeatureClassifier(it.next())) {
return "<Multiple>";
}
}
return getFeatureClassifierName(fc);
}
private static EObject getAllFeatureClassifier(Feature feature) {
if (feature instanceof FeatureGroup) {
final FeatureGroup fg = (FeatureGroup) feature;
return fg.getAllClassifier();
}
FeatureClassifier result;
do {
result = feature.getFeatureClassifier();
feature = feature.getRefined();
} while (feature != null && result == null);
return result;
}
private static String getFeatureClassifierName(final EObject fc) {
if (fc == null) {
return "<None>";
} else if (fc instanceof NamedElement) {
return Strings.emptyIfNull(((NamedElement) fc).getQualifiedName());
} else if (fc instanceof Prototype) {
return "<Prototype>";
} else {
return "";
}
}
/**
* Return a list of EObjectDescriptions and NamedElements for potential classifiers for the specified feature
* @return
*/
private List<Object> getPotentialFeatureClassifiers(final NamedElement feature) {
final List<Object> featureClassifiers = new ArrayList<Object>();
featureClassifiers.add(null);
final FeatureClassifierMetadata setterInfo = featureTypeToMetadataMap.get(feature.eClass());
// Populate the list with valid classifier descriptions
for (final IEObjectDescription desc : AadlModelAccessUtil.getAllEObjectsByType(feature.eResource(),
setterInfo.classifierEClass)) {
featureClassifiers.add(desc);
}
// Add any prototypes that are of the appropriate type
AadlPrototypeUtil.getAllPrototypes(feature.getContainingClassifier()).forEachOrdered(p -> {
if (setterInfo.classifierEClass.isInstance(p)) {
featureClassifiers.add(p);
}
});
return featureClassifiers;
}
private static void setFeatureClassifier(final NamedElement feature, final Object classifier) {
if (classifier != null) {
// Import its package if necessary
final AadlPackage pkg = (AadlPackage) feature.getElementRoot();
if (classifier instanceof Classifier && ((Classifier) classifier).getNamespace() != null && pkg != null) {
final PackageSection section = pkg.getPublicSection();
final AadlPackage selectedClassifierPkg = (AadlPackage) ((Classifier) classifier).getNamespace()
.getOwner();
if (selectedClassifierPkg != null && pkg != selectedClassifierPkg) {
AadlImportsUtil.addImportIfNeeded(section, selectedClassifierPkg);
}
}
// If the feature is an abstract feature, need to reset the feature prototype. Only a prototype or a classifier may be set.
if (feature instanceof AbstractFeature) {
((AbstractFeature) feature).setFeaturePrototype(null);
}
}
final FeatureClassifierMetadata setterInfo = featureTypeToMetadataMap.get(feature.eClass());
try {
final Method method = feature.getClass().getMethod(setterInfo.setterName, setterInfo.classifierClass);
method.invoke(feature, classifier);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}