ClassifierInfoView.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.ui.views;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.layout.TreeColumnLayout;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.PageBook;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
import org.osate.aadl2.BehavioredImplementation;
import org.osate.aadl2.Classifier;
import org.osate.aadl2.ComponentImplementation;
import org.osate.aadl2.ComponentType;
import org.osate.aadl2.Connection;
import org.osate.aadl2.EndToEndFlow;
import org.osate.aadl2.Feature;
import org.osate.aadl2.FeatureGroupType;
import org.osate.aadl2.FlowImplementation;
import org.osate.aadl2.FlowSpecification;
import org.osate.aadl2.GroupExtension;
import org.osate.aadl2.ImplementationExtension;
import org.osate.aadl2.ModeFeature;
import org.osate.aadl2.NamedElement;
import org.osate.aadl2.Prototype;
import org.osate.aadl2.Realization;
import org.osate.aadl2.Subcomponent;
import org.osate.aadl2.TypeExtension;
import org.osate.aadl2.modelsupport.resources.OsateResourceUtil;
import org.osate.search.AadlFinder;
import org.osate.ui.OsateUiPlugin;
import org.osate.ui.UiUtil;
public final class ClassifierInfoView extends ViewPart {
private static final String UNNAMED = "<unnamed>";
private static final String CONNECTIONS_SECTION = "Connections";
private static final String CALLS_SECTION = "Calls";
private static final String PROCESSOR_FEATURES_SECTION = "Processor features";
private static final String INTERNAL_FEATURES_SECTION = "Internal features";
private static final String SUBCOMPONENTS_SECTION = "Subcomponents";
private static final String MODES_SECTION = "Modes";
private static final String FLOWS_SECTION = "Flows";
private static final String FEATURES_SECTION = "Features";
private static final String PROTOTYPES_SECTION = "Prototypes";
private static final String NO_PREFIX = "";
private static final String INVERSE_OF = "inverse of ";
private static final String IMPLEMENTS = "implements ";
private static final String EXTENDS = "extends ";
private static final String INVERTED_BY = "inverted by ";
private static final String IMPLEMENTED_BY = "implemented by ";
private static final String EXTENDED_BY = "extended by ";
private static final String AADL_ICON = "/icons/aadl.gif";
private static final String LINK_ICON = "icons/link_to_editor.png";
private static final String CLEAR_ICON = "icons/delete.png";
private static final String REFRESH_ICON = "icons/refresh.png";
private static final String ANCESTORS_ICON = "icons/super_co.png";
private static final String DESCENDANTS_ICON = "icons/sub_co.png";
private static final String LINK_WITH_EDITOR_ACTION_NAME = "Link with Editor";
private static final String CLEAR_ACTION_NAME = "Clear View";
private static final String REFRESH_ACTION_NAME = "Refresh View";
private static final String REFRESH_TOOL_TOP = "Refreshes the view. Only enabled when a refresh is required.";
private static final String ANCESTORS_ACTION_NAME = "Ancestors Tree";
private static final String DESCENDANTS_ACTION_NAME = "Descendants Tree";
public static final String VIEW_ID = "org.osate.ui.classifier_info_view";
/**
* The most recently selected element in the view, or <code>null</code> is there is no
* selection. Remembered so that if we toggle from DONT_SYNC to SYNC, we know where to
* jump to.
*/
private URI lastSelectedURI = null;
private PageBook treePages;
private Composite ancestorTreeComposite;
private TreeViewer ancestorTreeViewer;
private Composite descendantTreeComposite;
private TreeViewer descendantTreeViewer;
private TreeViewer memberTreeViewer;
private Action ancestorAction;
private Action descendantAction;
private Action refreshAction;
private final ILabelProvider modelElementLabelProvider;
private Image aadlImage;
/* Mark these all as volatile because they are touched by UI and work threads. */
// Does clicking on a tree element automatically jump the editor location
private volatile boolean syncWithEditor = true;
private volatile Trees<?> whichTree;
/*
* The Job (if any currently running to refresh the view. If another refresh occurs, we need to cancel
* this job first. Protected by synchronizing on the view object.
*/
private Job currentRefreshJob = null;
/*
* Do not set directly. Use the setNeedsRefresh() method so that the refresh button is properly
* enabled.
*/
private volatile boolean needsRefresh = false;
private volatile boolean autoRefresh = true;
/*
* Keep track of the URI of the classifier being viewed so that we can refresh if
* the workspace changes. Need to keep the URI and not the classifier object
* itself so that we can reparse it and pick up the changes.
*/
private URI hierarchyClassifierURI = null;
private URI memberClassifierURI = null;
/*
* Synchronized because it is used by the display thread and whatever thread executes
* the resource change listener.
*/
private final Set<IResource> projectDependencies = Collections.synchronizedSet(new HashSet<>());
private IResourceChangeListener rsrcListener = null;
// ======================================================================
// == Constructor
// ======================================================================
public ClassifierInfoView() {
modelElementLabelProvider = UiUtil.getModelElementLabelProvider();
/*
* Init this here in the constructor and not in the field declaration because the instance field
* ANCESTOR_TREE needs to be initialized first. It is declared later in the class because it makes more sense logically.
*/
whichTree = ANCESTOR_TREE;
}
// ======================================================================
// == Static helper methods
// ======================================================================
/**
* Make sure the view is open and in front.
*/
public static ClassifierInfoView open(final IWorkbenchWindow window) {
/* I basically stole this from org.eclipse.jdt.internal.ui.util.OpenTypeHiearchyUtil.openInViewPart() */
final IWorkbenchPage page = window.getActivePage();
try {
final ClassifierInfoView result = (ClassifierInfoView) page.showView(VIEW_ID);
return result;
} catch (CoreException e) {
OsateUiPlugin.log(e);
}
return null;
}
// ======================================================================
// == Creation and clean up of all the UI stuff
// ======================================================================
@Override
public void createPartControl(final Composite parent) {
final SashForm sash = new SashForm(parent, SWT.HORIZONTAL);
// Create the trees
final SelectionAndDoubleClickHandler handler = new SelectionAndDoubleClickHandler();
treePages = new PageBook(sash, SWT.NULL);
createAncestorTree(treePages, handler);
createDescendantTree(treePages, handler);
createMemberTree(sash, handler);
whichTree.switchTo();
final IActionBars actionBars = getViewSite().getActionBars();
final IToolBarManager toolBarManager = actionBars.getToolBarManager();
final IMenuManager menuManager = actionBars.getMenuManager();
// Tree selection radio buttons
ancestorAction = new Action(ANCESTORS_ACTION_NAME, IAction.AS_RADIO_BUTTON) {
{
setToolTipText(ANCESTORS_ACTION_NAME);
setImageDescriptor(OsateUiPlugin.getImageDescriptor(ANCESTORS_ICON));
}
@Override
public void run() {
whichTree = ANCESTOR_TREE;
whichTree.switchTo();
clearMembers(true);
}
};
toolBarManager.add(ancestorAction);
descendantAction = new Action(DESCENDANTS_ACTION_NAME, IAction.AS_RADIO_BUTTON) {
{
setToolTipText(DESCENDANTS_ACTION_NAME);
setImageDescriptor(OsateUiPlugin.getImageDescriptor(DESCENDANTS_ICON));
setChecked(false);
clearMembers(true);
}
@Override
public void run() {
whichTree = DESCENDANT_TREE;
whichTree.switchTo();
}
};
toolBarManager.add(descendantAction);
whichTree.setChecked();
// Refresh button
refreshAction = new Action(REFRESH_ACTION_NAME, IAction.AS_PUSH_BUTTON) {
{
setToolTipText(REFRESH_TOOL_TOP);
setImageDescriptor(OsateUiPlugin.getImageDescriptor(REFRESH_ICON));
setEnabled(false);
}
@Override
public void run() {
refresh();
}
};
toolBarManager.add(refreshAction);
// Clear view button
toolBarManager.add(new Action(CLEAR_ACTION_NAME, IAction.AS_PUSH_BUTTON) {
{
setToolTipText(CLEAR_ACTION_NAME);
setImageDescriptor(OsateUiPlugin.getImageDescriptor(CLEAR_ICON));
}
@Override
public void run() {
clearDisplay(true);
}
});
// Link button
toolBarManager.add(new Action(LINK_WITH_EDITOR_ACTION_NAME, IAction.AS_CHECK_BOX) {
{
setToolTipText(LINK_WITH_EDITOR_ACTION_NAME);
setImageDescriptor(OsateUiPlugin.getImageDescriptor(LINK_ICON));
setChecked(syncWithEditor);
}
@Override
public void run() {
syncWithEditor = !syncWithEditor;
if (syncWithEditor) {
gotoURI(lastSelectedURI);
}
}
});
// Create the view menu
menuManager.add(new Action("Auto refresh", IAction.AS_CHECK_BOX) {
{
setToolTipText("When checked, the view will automatically refresh");
setChecked(autoRefresh);
}
@Override
public void run() {
final boolean isChecked = isChecked();
autoRefresh = isChecked;
// force the refresh action to update its state
setNeedsRefresh(needsRefresh);
// If we just now checked to "auto refresh" and a refresh is necessary, do the refresh
if (isChecked && needsRefresh) {
refresh();
}
}
});
}
@Override
public void init(final IViewSite site) throws PartInitException {
aadlImage = new Image(site.getShell().getDisplay(),
ClassifierInfoView.class.getResourceAsStream(AADL_ICON));
super.init(site);
rsrcListener = new Listener();
ResourcesPlugin.getWorkspace().addResourceChangeListener(rsrcListener);
}
@Override
public void dispose() {
if (rsrcListener != null) {
ResourcesPlugin.getWorkspace().removeResourceChangeListener(rsrcListener);
}
aadlImage.dispose();
aadlImage = null;
}
@Override
public void setFocus() {
memberTreeViewer.getControl().setFocus();
}
// ======================================================================
// == Manage the refresh button
// ======================================================================
private void setNeedsRefresh(final boolean value) {
needsRefresh = value;
refreshAction.setEnabled(!autoRefresh && value);
}
// ======================================================================
// == Trees
// ======================================================================
/*
* Cannot use a proper Java enum here because I need the enum instances to read local state of the view and enums
* are always static.
*/
private abstract class Trees<T extends HierarchyTree<?>> {
public final void switchTo() {
treePages.showPage(getPage());
}
public final void setChecked() {
getAction().setChecked(true);
}
protected abstract Action getAction();
protected abstract Composite getPage();
}
private final Trees<AncestorTree> ANCESTOR_TREE = new Trees<AncestorTree>() {
@Override
protected Action getAction() {
return ancestorAction;
}
@Override
protected Composite getPage() {
return ancestorTreeComposite;
}
};
private final Trees<DescendantTree> DESCENDANT_TREE = new Trees<DescendantTree>() {
@Override
protected Action getAction() {
return descendantAction;
}
@Override
protected Composite getPage() {
return descendantTreeComposite;
}
};
private void createAncestorTree(final Composite parent, final SelectionAndDoubleClickHandler handler) {
ancestorTreeComposite = new Composite(parent, SWT.NONE);
final TreeColumnLayout treeColumnLayout = new TreeColumnLayout();
ancestorTreeComposite.setLayout(treeColumnLayout);
ancestorTreeViewer = new TreeViewer(ancestorTreeComposite, SWT.H_SCROLL | SWT.V_SCROLL);
ancestorTreeViewer.getTree().setLinesVisible(false);
ancestorTreeViewer.getTree().setHeaderVisible(false);
ancestorTreeViewer.getTree().setFont(parent.getFont());
final TreeViewerColumn treeViewerCol = new TreeViewerColumn(ancestorTreeViewer, SWT.LEFT);
treeColumnLayout.setColumnData(treeViewerCol.getColumn(), new ColumnWeightData(1, true));
treeViewerCol.setLabelProvider(new ColumnLabelProvider() {
@Override
public Image getImage(Object element) {
return ((AncestorTreeNode) element).getImage();
}
@Override
public String getText(final Object element) {
return ((AncestorTreeNode) element).getText();
}
});
ancestorTreeViewer.setContentProvider(new ITreeContentProvider() {
@Override
public boolean hasChildren(final Object element) {
return ((AncestorTreeNode) element).hasChildren();
}
@Override
public Object getParent(final Object element) {
return ((AncestorTreeNode) element).getParent();
}
@Override
public Object[] getElements(final Object inputElement) {
if (inputElement != null) {
return ((AncestorTree) inputElement).getChildren();
} else {
return new Object[0];
}
}
@Override
public Object[] getChildren(final Object parentElement) {
return ((AncestorTreeNode) parentElement).getChildren();
}
});
ancestorTreeViewer.addSelectionChangedListener(handler);
ancestorTreeViewer.addDoubleClickListener(handler);
}
private void createDescendantTree(final Composite parent, final SelectionAndDoubleClickHandler handler) {
descendantTreeComposite = new Composite(parent, SWT.NONE);
final TreeColumnLayout treeColumnLayout = new TreeColumnLayout();
descendantTreeComposite.setLayout(treeColumnLayout);
descendantTreeViewer = new TreeViewer(descendantTreeComposite, SWT.H_SCROLL | SWT.V_SCROLL);
descendantTreeViewer.getTree().setLinesVisible(false);
descendantTreeViewer.getTree().setHeaderVisible(false);
descendantTreeViewer.getTree().setFont(parent.getFont());
final TreeViewerColumn treeViewerCol = new TreeViewerColumn(descendantTreeViewer, SWT.LEFT);
treeColumnLayout.setColumnData(treeViewerCol.getColumn(), new ColumnWeightData(1, true));
treeViewerCol.setLabelProvider(new ColumnLabelProvider() {
@Override
public Image getImage(Object element) {
return ((DescendantTreeNode) element).getImage();
}
@Override
public String getText(final Object element) {
return ((DescendantTreeNode) element).getText();
}
});
descendantTreeViewer.setContentProvider(new ITreeContentProvider() {
@Override
public boolean hasChildren(final Object element) {
return ((DescendantTreeNode) element).hasChildren();
}
@Override
public Object getParent(final Object element) {
return ((DescendantTreeNode) element).getParent();
}
@Override
public Object[] getElements(final Object inputElement) {
if (inputElement != null) {
return ((DescendantTree) inputElement).getChildren();
} else {
return new Object[0];
}
}
@Override
public Object[] getChildren(final Object parentElement) {
return ((DescendantTreeNode) parentElement).getChildren();
}
});
descendantTreeViewer.addSelectionChangedListener(handler);
descendantTreeViewer.addDoubleClickListener(handler);
}
private void createMemberTree(final Composite parent, final SelectionAndDoubleClickHandler handler) {
final Composite treeComposite = new Composite(parent, SWT.NONE);
final TreeColumnLayout treeColumnLayout = new TreeColumnLayout();
treeComposite.setLayout(treeColumnLayout);
memberTreeViewer = new TreeViewer(treeComposite, SWT.H_SCROLL | SWT.V_SCROLL);
memberTreeViewer.getTree().setLinesVisible(false);
memberTreeViewer.getTree().setHeaderVisible(false);
memberTreeViewer.getTree().setFont(parent.getFont());
final TreeViewerColumn treeViewerCol = new TreeViewerColumn(memberTreeViewer, SWT.LEFT);
treeColumnLayout.setColumnData(treeViewerCol.getColumn(), new ColumnWeightData(1, true));
treeViewerCol.setLabelProvider(new ColumnLabelProvider() {
@Override
public Image getImage(Object element) {
return ((MemberTreeNode) element).getImage();
}
@Override
public String getText(final Object element) {
return ((MemberTreeNode) element).getText();
}
});
memberTreeViewer.setContentProvider(new ITreeContentProvider() {
@Override
public boolean hasChildren(final Object element) {
return ((MemberTreeNode) element).hasChildren();
}
@Override
public Object getParent(final Object element) {
return null;
}
@Override
public Object[] getElements(final Object inputElement) {
if (inputElement != null) {
return ((MemberTree) inputElement).getSections();
} else {
return new Object[0];
}
}
@Override
public Object[] getChildren(final Object parentElement) {
return ((MemberTreeNode) parentElement).getChildren();
}
});
memberTreeViewer.addSelectionChangedListener(handler);
memberTreeViewer.addDoubleClickListener(handler);
}
// ======================================================================
// == Set the view input
// ======================================================================
public void setInput(final URI classifierURI) {
if (classifierURI != null) {
hierarchyClassifierURI = classifierURI;
memberClassifierURI = classifierURI;
final Classifier classifier = (Classifier) new ResourceSetImpl().getEObject(classifierURI, true);
updateDisplay(classifier, classifier);
}
}
public void setMember(final URI memberURI) {
if (memberURI != null) {
memberClassifierURI = memberURI;
final Classifier classifier = (Classifier) new ResourceSetImpl().getEObject(memberURI, true);
updateDisplay(null, classifier);
}
}
private void refresh() {
/*
* NB. If hierarhcyClassiferURI is null, then the memberClassifierURI is null. It may be the case
* that hierarhcyClassiferURI is non-null and memberClassifierURI is null.
*/
if (hierarchyClassifierURI != null) {
final Classifier hierarchyClassifier = getClassifierFromURI(hierarchyClassifierURI);
if (hierarchyClassifier != null) {
// Reset the member classifier to the hierarchy root
memberClassifierURI = hierarchyClassifierURI;
updateDisplay(hierarchyClassifier, hierarchyClassifier);
} else {
// root node no longer exists, reset the display
clearDisplay(true);
}
}
}
/**
* Get the Classifier from the URI. Return {@code null} if the resource
* no longer exits, or the resource no longer contains the classifier.
*/
private static Classifier getClassifierFromURI(final URI uri) {
final IFile rsrc = OsateResourceUtil.toIFile(uri);
if (rsrc.exists()) {
return (Classifier) new ResourceSetImpl().getEObject(uri, true);
} else {
return null;
}
}
private void clearDisplay(final boolean resetURI) {
clearHierarchy(resetURI);
clearMembers(resetURI);
}
private void clearHierarchy(final boolean resetURI) {
if (resetURI) {
hierarchyClassifierURI = null;
projectDependencies.clear();
}
getViewSite().getShell().getDisplay().asyncExec(() -> {
ancestorTreeViewer.setInput(AncestorTree.EMTPY_TREE);
descendantTreeViewer.setInput(DescendantTree.EMPTY_TREE);
});
}
private void clearMembers(final boolean resetURI) {
if (resetURI) {
memberClassifierURI = null;
}
getViewSite().getShell().getDisplay().asyncExec(() -> {
memberTreeViewer.setInput(MemberTree.EMPTY_TREE);
});
}
// Synchronized to make manipulation of the currentRefreshJob atomic
private synchronized void updateDisplay(final Classifier inputHierarchy, final Classifier inputMember) {
// Stop any current refresh job
if (currentRefreshJob != null) {
currentRefreshJob.cancel();
}
final Job waitForJob = currentRefreshJob;
// Compute the tree in a separate job and then update the view on the display thread.
final IWorkbenchSiteProgressService service = getSite().getService(IWorkbenchSiteProgressService.class);
final Job refreshJob = new Job("Classifier Info View Refresh") {
@Override
protected IStatus run(final IProgressMonitor monitor) {
// Wait for the current refresh to finish (it should have been cancelled)
if (waitForJob != null) {
try {
waitForJob.join();
} catch (final OperationCanceledException | InterruptedException e) {
/*
* We were interrupted while waiting. Not sure this should be possible, but
* if it happens, we clear the display.
*/
clearDisplay(false);
return Status.CANCEL_STATUS;
}
}
boolean needsRefresh = false;
boolean skipMember = false;
final SubMonitor subMonitor = SubMonitor.convert(monitor, 2);
if (inputHierarchy != null) {
subMonitor.subTask("Buidling classifier hiearchy");
final Set<IProject> projects = getDependantProjects(inputHierarchy);
projectDependencies.addAll(projects);
try {
final AncestorTree ancestorTreeModel = createAncestorTree(inputHierarchy, subMonitor);
subMonitor.worked(1);
final DescendantTree descendantTreeModel = createDescendantTree(inputHierarchy, projects,
subMonitor.split(1));
// Update the view contents in the UI thread
getViewSite().getShell().getDisplay().asyncExec(() -> {
ancestorTreeViewer.setInput(ancestorTreeModel);
ancestorTreeViewer.expandToLevel(2);
ancestorTreeViewer
.setSelection(new TreeSelection(new TreePath(ancestorTreeModel.getChildren())));
descendantTreeViewer.setInput(descendantTreeModel);
descendantTreeViewer.expandToLevel(2);
descendantTreeViewer
.setSelection(new TreeSelection(new TreePath(descendantTreeModel.getChildren())));
});
} catch (final OperationCanceledException | InterruptedException e) {
/*
* Hierarchy build cancelled-- clear both panes. Originally we just cleared the
* hierarchy pane, but then it is confusing what is being shown in the member pane.
* Set the 'skip' flag so we don't read the member pane below.
*/
clearDisplay(false);
// Update incomplete, so we still need to refresh later
needsRefresh = true;
skipMember = true;
}
subMonitor.done();
} else {
subMonitor.worked(2);
}
if (inputMember != null && !skipMember) {
final MemberTree memberTree;
if (inputMember instanceof ComponentType) {
memberTree = createMemberTree((ComponentType) inputMember);
} else if (inputMember instanceof ComponentImplementation) {
memberTree = createMemberTree((ComponentImplementation) inputMember);
} else if (inputMember instanceof FeatureGroupType) {
memberTree = createMemberTree((FeatureGroupType) inputMember);
} else {
// Shouldn't get here
memberTree = null;
}
if (memberTree != null) {
// Update the view contents in the UI thread
getViewSite().getShell().getDisplay().asyncExec(() -> {
memberTreeViewer.setInput(memberTree);
memberTreeViewer.expandToLevel(2);
});
} else {
clearMembers(false);
}
}
setNeedsRefresh(needsRefresh);
// Mark the view as changed (should show up with a bold title)
service.warnOfContentChange();
return Status.OK_STATUS;
}
};
refreshJob.setRule(null); // doesn't write resources
refreshJob.setUser(true);
refreshJob.setSystem(false);
// Run the job using the progress service so that the view is marked as busy (italics)
currentRefreshJob = refreshJob;
service.schedule(refreshJob, 0, true);
}
// ======================================================================
// == Helper classes
// ======================================================================
// ----------------------------------------------------------------------
// -- Selection and Double-click Listener
// ----------------------------------------------------------------------
private class SelectionAndDoubleClickHandler implements ISelectionChangedListener, IDoubleClickListener {
@Override
public void selectionChanged(final SelectionChangedEvent event) {
URI selectedURI = null;
final ISelection selection = event.getSelection();
if (selection instanceof IStructuredSelection) {
final IStructuredSelection structuredSelection = (IStructuredSelection) selection;
if (structuredSelection.size() == 1) {
final URIProvider selectedNode = (URIProvider) structuredSelection.getFirstElement();
if (selectedNode != null) {
selectedURI = selectedNode.getURI();
if (selectedURI != null) {
// Update the member view if the selection is NOT from the member view
if (event.getSource() != memberTreeViewer) {
setMember(selectedURI);
}
// Jump in the text editor if we are synchronizing
if (syncWithEditor) {
gotoURI(selectedURI);
}
}
}
}
}
lastSelectedURI = selectedURI;
}
@Override
public void doubleClick(final DoubleClickEvent event) {
final IStructuredSelection selected = (IStructuredSelection) event.getSelection();
final URIProvider selectedNode = (URIProvider) selected.getFirstElement();
if (selectedNode != null) {
final URI selectedURI = selectedNode.getURI();
if (selectedURI != null) {
gotoURI(selectedURI);
}
lastSelectedURI = selectedURI;
}
}
}
// ----------------------------------------------------------------------
// -- Resource Listener
// ----------------------------------------------------------------------
/**
* When a resource in the workspace is moved or deleted we check to see if it is overrides a
* plug-in contributed resource. If so, we need to update the global preferences.
*/
private class Listener implements IResourceChangeListener {
@Override
public void resourceChanged(final IResourceChangeEvent event) {
/*
* See Issue #2436. Need to check for changes to .aadlbin files in the projects that
* we care about.
*/
final AtomicBoolean changed = new AtomicBoolean(false);
if (event.getType() == IResourceChangeEvent.POST_CHANGE) {
final IResourceDelta docDelta = event.getDelta();
if (docDelta != null) {
try {
docDelta.accept(delta -> {
/*
* Must be an ".aadlbin" file in one the projects we care about to be interesting.
*/
final IResource resource = delta.getResource();
if (resource instanceof IFile && "aadlbin".equalsIgnoreCase(resource.getFileExtension())
&& projectDependencies.contains(resource.getProject())) {
changed.set(true);
}
return true;
});
} catch (final CoreException e) {
OsateUiPlugin.log(e);
}
}
}
if (changed.get()) {
if (autoRefresh) {
refresh();
} else {
setNeedsRefresh(true);
}
}
}
}
// ----------------------------------------------------------------------
// -- Generic hierarchy tree
// ----------------------------------------------------------------------
private interface URIProvider {
public URI getURI();
}
private abstract static class HierarchyTree<X extends HierarchyTreeNode<?>> {
private final X[] children;
private HierarchyTree() {
children = emptyChildren();
}
private HierarchyTree(final X root) {
children = createChildren(root);
}
protected abstract X[] emptyChildren();
protected abstract X[] createChildren(final X root);
public final X[] getChildren() {
return children;
}
}
private abstract class HierarchyTreeNode<SELF extends HierarchyTreeNode<?>> implements URIProvider {
protected SELF parent;
private final URI classifierURI;
private final String label;
private final Image image;
private HierarchyTreeNode(final EObject classifier, final String prefix) {
this.classifierURI = EcoreUtil.getURI(classifier);
this.label = prefix + modelElementLabelProvider.getText(classifier);
this.image = modelElementLabelProvider.getImage(classifier);
}
@Override
public final URI getURI() {
return classifierURI;
}
public final Image getImage() {
return image;
}
public final String getText() {
return label;
}
public abstract boolean hasChildren();
public abstract HierarchyTreeNode<SELF>[] getChildren();
public final SELF getParent() {
return parent;
}
}
// ----------------------------------------------------------------------
// -- Ancestor tree
// ----------------------------------------------------------------------
private final static class AncestorTree extends HierarchyTree<AncestorTreeNode> {
private final static AncestorTreeNode[] EMPTY_CHILDREN = new AncestorTreeNode[0];
public final static AncestorTree EMTPY_TREE = new AncestorTree();
private AncestorTree() {
super();
}
private AncestorTree(final AncestorTreeNode root) {
super(root);
}
@Override
protected final AncestorTreeNode[] emptyChildren() {
return EMPTY_CHILDREN;
}
@Override
protected final AncestorTreeNode[] createChildren(final AncestorTreeNode root) {
return new AncestorTreeNode[] { root };
}
}
private AncestorTree createAncestorTree(final Classifier classifier, final IProgressMonitor progressMonitor)
throws InterruptedException {
return new AncestorTree(createAncestorTreeNode(classifier, NO_PREFIX, progressMonitor));
}
private final class AncestorTreeNode extends HierarchyTreeNode<AncestorTreeNode> {
private final AncestorTreeNode[] children;
private AncestorTreeNode(final Classifier classifier, final String prefix, final AncestorTreeNode[] children) {
super(classifier, prefix);
this.children = children;
for (final AncestorTreeNode child : children) {
child.parent = this;
}
}
@Override
public final boolean hasChildren() {
return children.length > 0;
}
@Override
public final AncestorTreeNode[] getChildren() {
return children;
}
}
private AncestorTreeNode createAncestorTreeNode(final Classifier classifier, final String prefix,
final IProgressMonitor progressMonitor) throws InterruptedException {
if (progressMonitor.isCanceled()) {
throw new InterruptedException();
}
AncestorTreeNode[] children = new AncestorTreeNode[0];
if (classifier instanceof ComponentType) {
final ComponentType extended = ((ComponentType) classifier.getExtended());
if (extended != null) {
children = new AncestorTreeNode[] { createAncestorTreeNode(extended, EXTENDS, progressMonitor) };
}
} else if (classifier instanceof ComponentImplementation) {
final ComponentImplementation asCompImpl = (ComponentImplementation) classifier;
final ComponentType implemented = asCompImpl.getType();
final ComponentImplementation extended = asCompImpl.getExtended();
if (extended == null) {
children = new AncestorTreeNode[] { createAncestorTreeNode(implemented, IMPLEMENTS, progressMonitor) };
} else {
children = new AncestorTreeNode[] { createAncestorTreeNode(implemented, IMPLEMENTS, progressMonitor),
createAncestorTreeNode(extended, EXTENDS, progressMonitor) };
}
} else if (classifier instanceof FeatureGroupType) {
final FeatureGroupType asFeatureGroup = (FeatureGroupType) classifier;
final FeatureGroupType inverseOf = asFeatureGroup.getInverse();
final FeatureGroupType extended = asFeatureGroup.getExtended();
final List<AncestorTreeNode> childrenList = new ArrayList<>(2);
if (inverseOf != null) {
childrenList.add(createAncestorTreeNode(inverseOf, INVERSE_OF, progressMonitor));
}
if (extended != null) {
childrenList.add(createAncestorTreeNode(extended, EXTENDS, progressMonitor));
}
children = childrenList.toArray(children);
}
return new AncestorTreeNode(classifier, prefix, children);
}
// ----------------------------------------------------------------------
// -- Descendant tree
// ----------------------------------------------------------------------
private final static class DescendantTree extends HierarchyTree<DescendantTreeNode> {
private static final DescendantTreeNode[] EMPTY_CHILDREN = new DescendantTreeNode[0];
public static final DescendantTree EMPTY_TREE = new DescendantTree();
private DescendantTree() {
super();
}
private DescendantTree(final DescendantTreeNode root) {
super(root);
}
@Override
protected final DescendantTreeNode[] emptyChildren() {
return EMPTY_CHILDREN;
}
@Override
protected final DescendantTreeNode[] createChildren(final DescendantTreeNode root) {
return new DescendantTreeNode[] { root };
}
}
private final class DescendantTreeNode extends HierarchyTreeNode<DescendantTreeNode> {
private final List<DescendantTreeNode> children = new LinkedList<>();
private DescendantTreeNode(final EObject classifier, final String prefix) {
super(classifier, prefix);
}
private void addChild(final DescendantTreeNode child) {
child.parent = this;
children.add(child);
}
@Override
public boolean hasChildren() {
return children.size() > 0;
}
@Override
public DescendantTreeNode[] getChildren() {
return children.toArray(new DescendantTreeNode[children.size()]);
}
}
private DescendantTree createDescendantTree(final Classifier rootClassifier, final Set<IProject> projects,
final IProgressMonitor progressMonitor) throws InterruptedException {
final SubMonitor subMonitor = SubMonitor.convert(progressMonitor);
final Deque<DescendantTreeNode> deque = new LinkedList<>();
final DescendantTreeNode root = new DescendantTreeNode(rootClassifier, NO_PREFIX);
deque.addLast(root);
/* We maintain a queue of classifiers to search for, starting with our root classifier. For each
* one, we look for all references to it, and then process all references that appear in a context
* where it is being implemented/extended/inverted.
*/
final AadlFinder.Scope scope = new AadlFinder.ResourceSetScope(projects);
while (!deque.isEmpty()) {
if (subMonitor.isCanceled()) {
throw new InterruptedException();
}
final DescendantTreeNode current = deque.removeFirst();
/* Find all the references to the current classifier */
// Use the "logarithmic progress pattern described in SubMonitor JavaDoc
subMonitor.subTask(current.getText());
subMonitor.setWorkRemaining(10);
final AtomicBoolean cancelled = new AtomicBoolean(false);
AadlFinder.getInstance().getAllReferencesToTypeInScope(scope, (resourceSet, refDesc) -> {
if (subMonitor.isCanceled()) {
cancelled.set(true);
}
if (refDesc.getTargetEObjectUri().equals(current.getURI())) {
subMonitor.setWorkRemaining(10);
subMonitor.split(1);
final URI sourceEObjectUri = refDesc.getSourceEObjectUri();
final EObject eObj = resourceSet.getEObject(sourceEObjectUri, true);
EObject childObject = null;
String childPrefix = null;
if (eObj instanceof TypeExtension) {
childObject = ((TypeExtension) eObj).eContainer();
childPrefix = EXTENDED_BY;
} else if (eObj instanceof Realization) {
childObject = ((Realization) eObj).eContainer();
childPrefix = IMPLEMENTED_BY;
} else if (eObj instanceof ImplementationExtension) {
childObject = ((ImplementationExtension) eObj).eContainer();
childPrefix = EXTENDED_BY;
} else if (eObj instanceof GroupExtension) {
childObject = ((GroupExtension) eObj).eContainer();
childPrefix = EXTENDED_BY;
} else if (eObj instanceof FeatureGroupType) {
childObject = eObj;
childPrefix = INVERTED_BY;
}
/* Add the referencing node to the tree */
if (childObject != null) {
final DescendantTreeNode child = new DescendantTreeNode(childObject, childPrefix);
current.addChild(child);
deque.addLast(child);
}
}
}, subMonitor.split(1));
if (cancelled.get()) {
throw new InterruptedException();
}
}
return new DescendantTree(root);
}
private static final Set<IProject> getDependantProjects(final Classifier rootClassifier) {
final Set<IProject> projects = new HashSet<>();
final IProject rootProject = OsateResourceUtil.toIFile(rootClassifier.eResource().getURI()).getProject();
getDependantProjects(rootProject, projects);
return Collections.unmodifiableSet(projects);
}
private static final void getDependantProjects(final IProject project, final Set<IProject> projects) {
projects.add(project);
for (final IProject child : project.getReferencingProjects()) {
getDependantProjects(child, projects);
}
}
// ----------------------------------------------------------------------
// -- Member tree
// ----------------------------------------------------------------------
private final static class MemberTree {
private static final SectionNode[] EMPTY_SECTIONS = new SectionNode[0];
private static final MemberTree EMPTY_TREE = new MemberTree();
private final SectionNode[] sections;
private MemberTree() {
sections = EMPTY_SECTIONS;
}
private MemberTree(List<SectionNode> sectionsList) {
sections = sectionsList.toArray(new SectionNode[sectionsList.size()]);
}
public SectionNode[] getSections() {
return sections;
}
}
private MemberTree createMemberTree(final FeatureGroupType fgt) {
final List<SectionNode> sections = new ArrayList<>();
addSection(sections, PROTOTYPES_SECTION, fgt, fgt.getAllPrototypes(), ClassifierInfoView::getRefinedPrototype);
final EList<Feature> allFeatures = fgt.getAllFeatures();
addFeatureGroupFeaturesSection(sections, fgt, allFeatures);
return new MemberTree(sections);
}
private MemberTree createMemberTree(final ComponentType ct) {
final List<SectionNode> sections = new ArrayList<>();
addSection(sections, PROTOTYPES_SECTION, ct, ct.getAllPrototypes(), ClassifierInfoView::getRefinedPrototype);
addSection(sections, FEATURES_SECTION, ct, ct.getAllFeatures(), ClassifierInfoView::getRefinedFeature);
addSection(sections, FLOWS_SECTION, ct, ct.getAllFlowSpecifications(), ClassifierInfoView::getRefinedFlowSpec);
addSection(sections, MODES_SECTION, ct, getAllModesAndModeTransitions(ct), ClassifierInfoView::cannotBeRefined);
return new MemberTree(sections);
}
private MemberTree createMemberTree(final ComponentImplementation ci) {
final List<SectionNode> sections = new ArrayList<>();
addSection(sections, PROTOTYPES_SECTION, ci, ci.getAllPrototypes(), ClassifierInfoView::getRefinedPrototype);
addSection(sections, FEATURES_SECTION, ci, ci.getType().getAllFeatures(), ClassifierInfoView::getRefinedFeature);
addSection(sections, SUBCOMPONENTS_SECTION, ci, ci.getAllSubcomponents(), ClassifierInfoView::getRefinedSubcomponent);
addSection(sections, INTERNAL_FEATURES_SECTION, ci, ci.getAllInternalFeatures(), ClassifierInfoView::cannotBeRefined);
addSection(sections, PROCESSOR_FEATURES_SECTION, ci, ci.getAllProcessorFeatures(),
ClassifierInfoView::cannotBeRefined);
if (ci instanceof BehavioredImplementation) {
addSection(sections, CALLS_SECTION, ci, ((BehavioredImplementation) ci).getAllSubprogramCallSequences(),
ClassifierInfoView::cannotBeRefined);
}
addSection(sections, CONNECTIONS_SECTION, ci, ci.getAllConnections(), ClassifierInfoView::getRefinedConnection);
addFlowImplementationSection(sections, ci);
addSection(sections, MODES_SECTION, ci, getAllModesAndModeTransitions(ci), ClassifierInfoView::cannotBeRefined);
return new MemberTree(sections);
}
private <M extends NamedElement> void addSection(final List<SectionNode> sections, final String heading,
final Classifier classifier, final List<M> members, final GetRefined<M> gr) {
if (members != null && members.size() > 0) {
sections.add(createSectionNode(classifier, members, heading, gr));
}
}
private void addFeatureGroupFeaturesSection(final List<SectionNode> sections, final FeatureGroupType fgt,
final List<Feature> members) {
if (members != null && members.size() > 0) {
sections.add(createFeatureGroupFeaturesSectionNode(fgt, members));
}
}
private void addFlowImplementationSection(final List<SectionNode> sections, final ComponentImplementation ci) {
List<FlowSpecification> flowSpecs = ci.getType().getAllFlowSpecifications();
if (flowSpecs == null) {
flowSpecs = Collections.emptyList();
}
List<FlowImplementation> flowImpls = ci.getAllFlowImplementations();
if (flowImpls == null) {
flowImpls = Collections.emptyList();
}
List<EndToEndFlow> end2endFlows = ci.getAllEndToEndFlows();
if (end2endFlows == null) {
end2endFlows = Collections.emptyList();
}
if (!flowSpecs.isEmpty() || !flowImpls.isEmpty() || !end2endFlows.isEmpty()) {
sections.add(createSectionFromFlowImplementations(ci, flowSpecs, flowImpls, end2endFlows));
}
}
private static interface MemberTreeNode extends URIProvider {
public String getText();
public Image getImage();
public boolean hasChildren();
public Object[] getChildren();
}
private final class SectionNode implements MemberTreeNode {
private final String heading;
private final MemberNode[] members;
private SectionNode(final String heading, List<MemberNode> membersList) {
this.heading = heading;
this.members = membersList.toArray(new MemberNode[membersList.size()]);
}
@Override
public URI getURI() {
return null;
}
@Override
public String getText() {
return heading;
}
@Override
public Image getImage() {
return aadlImage;
}
@Override
public boolean hasChildren() {
return members.length > 0;
}
@Override
public MemberNode[] getChildren() {
return members;
}
}
public <M extends NamedElement> SectionNode createSectionNode(final Classifier classifier, final List<M> members,
final String heading, final GetRefined<M> gr) {
Collections.sort(members, MEMBER_COMPARATOR);
final List<MemberNode> memberNodes = new ArrayList<>();
for (final M member : members) {
memberNodes.add(createMemberNode(classifier, member, gr));
}
return new SectionNode(heading, memberNodes);
}
public SectionNode createFeatureGroupFeaturesSectionNode(final FeatureGroupType fgt, final List<Feature> members) {
final Collection<Classifier> ancestors = fgt.getSelfPlusAllExtended();
// Don't sort the features of a feature group type because the order matters
final List<MemberNode> memberNodes = new ArrayList<>();
for (final Feature member : members) {
memberNodes.add(createMemberNode(fgt, ancestors, member, ClassifierInfoView::getRefinedFeature));
}
return new SectionNode(FEATURES_SECTION, memberNodes);
}
public SectionNode createSectionFromFlowImplementations(final ComponentImplementation ci,
List<FlowSpecification> flowSpecs, List<FlowImplementation> flowImpls, List<EndToEndFlow> end2endFlows) {
/*
* Get all the flow specifications from the type. Overlay on top of them the flow implementations
* from the implementation. That is, anything that is implemented in the implementation will be
* replaced by the Flow implementation in the map.
*/
final Map<String, NamedElement> flowSpecsAndImplsMap = new HashMap<>();
flowSpecs.forEach(fs -> flowSpecsAndImplsMap.put(fs.getName(), fs));
flowImpls.forEach(fi -> flowSpecsAndImplsMap.put(fi.getSpecification().getFullName(), fi));
final List<NamedElement> flowSpecsAndImpls = new ArrayList<>(flowSpecsAndImplsMap.values());
final List<MemberNode> memberNodes = new ArrayList<>();
Collections.sort(flowSpecsAndImpls, MEMBER_COMPARATOR);
flowSpecsAndImpls.forEach(f -> {
if (f instanceof FlowSpecification) {
memberNodes.add(createMemberNode(ci, (FlowSpecification) f, ClassifierInfoView::getRefinedFlowSpec));
} else {
memberNodes.add(createMemberNodeFromFlowImplementation(ci, (FlowImplementation) f));
}
});
Collections.sort(end2endFlows, MEMBER_COMPARATOR);
end2endFlows
.forEach(e2e -> memberNodes.add(createMemberNode(ci, e2e, ClassifierInfoView::getRefinedEndToEndFlow)));
return new SectionNode(FLOWS_SECTION, memberNodes);
}
private final class MemberNode implements MemberTreeNode {
private static final String FROM_CLOSE = "]";
private static final String FROM_OPEN = " [from ";
private static final String REFINED = "refined ";
private final URI memberURI;
private final String label;
private final Image image;
private final MemberNode[] ancestorMember;
private MemberNode(final NamedElement member, final Classifier inheritedFrom, final boolean isInverted,
final boolean isRefined, final MemberNode ancestor) {
memberURI = EcoreUtil.getURI(member);
final String name = getName(member);
label = (isInverted ? INVERSE_OF : NO_PREFIX) + (isRefined ? REFINED : NO_PREFIX)
+ unparseMember(member, name)
+ (inheritedFrom != null ? (FROM_OPEN + inheritedFrom.getName() + FROM_CLOSE) : NO_PREFIX);
image = modelElementLabelProvider.getImage(member);
ancestorMember = ancestor == null ? new MemberNode[0] : new MemberNode[] { ancestor };
}
private MemberNode(final NamedElement m, final Classifier from, final boolean refined,
final MemberNode ancestor) {
this(m, from, false, refined, ancestor);
}
@Override
public URI getURI() {
return memberURI;
}
@Override
public String getText() {
return label;
}
@Override
public Image getImage() {
return image;
}
@Override
public boolean hasChildren() {
return ancestorMember.length > 0;
}
@Override
public MemberNode[] getChildren() {
return ancestorMember;
}
private String unparseMember(final EObject member, final String name) {
return name;
}
}
public <M extends NamedElement> MemberNode createMemberNode(final Classifier classifier, final M member,
final GetRefined<M> gr) {
final Classifier q = (Classifier) member.eContainer();
final boolean isLocal = q.equals(classifier);
final M refined = gr.getRefined(member);
final boolean isRefined = refined != null;
return new MemberNode(member, isLocal ? null : q, isRefined,
!isRefined ? null : createMemberNode(classifier, refined, gr));
}
public <M extends NamedElement> MemberNode createMemberNode(final FeatureGroupType fgt,
final Collection<Classifier> ancestors, final M member, final GetRefined<M> gr) {
final Classifier q = (Classifier) member.eContainer();
final boolean isLocal = q.equals(fgt);
final M refined = gr.getRefined(member);
final boolean isRefined = refined != null;
return new MemberNode(member, isLocal ? null : q, !ancestors.contains(q), isRefined,
!isRefined ? null : createMemberNode(fgt, refined, gr));
}
public MemberNode createMemberNodeFromFlowImplementation(final ComponentImplementation ci,
final FlowImplementation flowImpl) {
final Classifier q = (Classifier) flowImpl.eContainer();
final boolean isLocal = q.equals(ci);
final FlowSpecification flowSpec = flowImpl.getSpecification();
return new MemberNode(flowImpl, isLocal ? null : q, false,
flowSpec.eIsProxy() ? null : createMemberNode(ci, flowSpec, ClassifierInfoView::getRefinedFlowSpec));
}
private static final List<ModeFeature> getAllModesAndModeTransitions(final ComponentType ct) {
final List<ModeFeature> modeFeatures = new ArrayList<>();
modeFeatures.addAll(ct.getAllModes());
modeFeatures.addAll(ct.getAllModeTransitions());
return modeFeatures;
}
private static final List<ModeFeature> getAllModesAndModeTransitions(final ComponentImplementation ci) {
final List<ModeFeature> modeFeatures = new ArrayList<>();
modeFeatures.addAll(ci.getAllModes());
modeFeatures.addAll(ci.getAllModeTransitions());
return modeFeatures;
}
private static final Comparator<NamedElement> MEMBER_COMPARATOR = new MemberComparator();
private static final class MemberComparator implements Comparator<NamedElement> {
@Override
public int compare(final NamedElement o1, final NamedElement o2) {
return String.CASE_INSENSITIVE_ORDER.compare(getName(o1), getName(o2));
}
}
@FunctionalInterface
private static interface GetRefined<M extends NamedElement> {
public M getRefined(M member);
}
private static <X extends NamedElement> X cannotBeRefined(final X x) {
// cannot be refined
return null;
}
private static Prototype getRefinedPrototype(final Prototype p) {
return p.getRefined();
}
private static Feature getRefinedFeature(final Feature f) {
return f.getRefined();
}
private static FlowSpecification getRefinedFlowSpec(final FlowSpecification fs) {
return fs.getRefined();
}
private static Subcomponent getRefinedSubcomponent(final Subcomponent sub) {
return sub.getRefined();
}
private static Connection getRefinedConnection(final Connection c) {
return c.getRefined();
}
private static EndToEndFlow getRefinedEndToEndFlow(final EndToEndFlow e) {
return e.getRefined();
}
private static void gotoURI(final URI gotoURI) {
UiUtil.getInstance().openDeclarativeModelElement(gotoURI);
}
private static String getName(NamedElement ne) {
return (ne != null && !ne.eIsProxy() && ne.getName() != null) ? ne.getName() : UNNAMED;
}
}