Commit 38e797b7 authored by Henrik Rentz-Reichert's avatar Henrik Rentz-Reichert
Browse files

Bug 546282 - [ui] Implement "organize imports"

Implemented a fairly general mechanism that can be adapted easily
for other models, e.g. one using the FSM part only.

Conflicts:
	plugins/org.eclipse.etrice.core.common.ui/src/org/eclipse/etrice/core/common/ui/imports/ImportOrganizer.java

Change-Id: If6f6d6039833c2e54a337b60d41be6bb20b0ed0a
parent 34912487
......@@ -32,13 +32,18 @@ import org.eclipse.emf.ecore.EReference;
import org.eclipse.etrice.core.common.base.BaseFactory;
import org.eclipse.etrice.core.common.base.Import;
import org.eclipse.etrice.core.common.ui.imports.IOrganizeImportHelper.ImportRegionResult;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Display;
import org.eclipse.xtext.formatting.IWhitespaceInformationProvider;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.util.ReplaceRegion;
import org.eclipse.xtext.xbase.lib.Pair;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.inject.Inject;
......@@ -57,7 +62,11 @@ public class ImportOrganizer {
private IWhitespaceInformationProvider whitespaceInformationProvider;
public List<ReplaceRegion> getOrganizedImportChanges(XtextResource resource) {
Set<QualifiedName> typeUsages = getTypeUsages(resource);
return getOrganizedImportChanges(resource, true);
}
public List<ReplaceRegion> getOrganizedImportChanges(XtextResource resource, boolean interactive) {
Set<QualifiedName> typeUsages = getTypeUsages(resource, interactive);
typeUsages = simplifyImports(typeUsages);
......@@ -68,9 +77,11 @@ public class ImportOrganizer {
return changes;
}
protected Set<QualifiedName> getTypeUsages(XtextResource resource) {
protected Set<QualifiedName> getTypeUsages(XtextResource resource, boolean interactive) {
Set<QualifiedName> result = new HashSet<>();
Multimap<Pair<EClass, String>, QualifiedName> ambiguous = HashMultimap.create();
Multimap<EClass, EReference> typeReferences = organizeImportHelper.getTypeReferences();
EObject root = resource.getContents().get(0);
TreeIterator<EObject> it = root.eAllContents();
......@@ -84,7 +95,14 @@ public class ImportOrganizer {
if (((EObject) refObject).eIsProxy()) {
List<INode> nodes = NodeModelUtils.findNodesForFeature((EObject) object, ref);
if (!nodes.isEmpty()) {
result.addAll(organizeImportHelper.resolveFullyQualifiedName(nodes.get(0).getText().trim(), ref.getEReferenceType(), resource));
String refText = nodes.get(0).getText().trim();
List<QualifiedName> resolved = organizeImportHelper.resolveFullyQualifiedName(refText, ref.getEReferenceType(), resource);
if (resolved.size()==1) {
result.addAll(resolved);
}
else if (resolved.size()>1) {
ambiguous.putAll(new Pair<EClass, String>(object.eClass(), refText), resolved);
}
}
}
else {
......@@ -108,14 +126,43 @@ public class ImportOrganizer {
}
}
if (!ambiguous.isEmpty()) {
QualifiedName[][] namespaces = new QualifiedName[ambiguous.asMap().size()][];
int i = 0;
for (Collection<QualifiedName> lists : ambiguous.asMap().values()) {
QualifiedName[] list = new QualifiedName[lists.size()];
namespaces[i++] = lists.toArray(list);
}
result.addAll(doChooseImports(namespaces));
}
// remove our own namespace
QualifiedName ownNamespace = organizeImportHelper.getFullyQualifiedName(root);
result.removeIf(fqn -> fqn.startsWith(ownNamespace));
return result;
}
protected Set<QualifiedName> doChooseImports(QualifiedName[][] openChoices) {
NamespaceSelectionLabelProvider labelProvider = new NamespaceSelectionLabelProvider();
NamespaceSelectionDialog dialog = new NamespaceSelectionDialog(Display.getDefault().getActiveShell(), labelProvider);
dialog.setTitle("Organize Imports");
dialog.setMessage("&Choose type to import:");
dialog.setElements(openChoices);
Set<QualifiedName> result = new HashSet<>();
if (dialog.open() == Window.OK) {
Object[] res = dialog.getResult();
for (int i = 0; i < res.length; i++) {
Object[] array= (Object[]) res[i];
if (array.length>0 && array[0] instanceof QualifiedName) {
result.add((QualifiedName) array[0]);
}
}
}
return result;
}
private Set<QualifiedName> simplifyImports(Set<QualifiedName> typeUsages) {
protected Set<QualifiedName> simplifyImports(Set<QualifiedName> typeUsages) {
Map<QualifiedName, List<QualifiedName>> grouped = typeUsages.stream().collect(Collectors.groupingBy(fqn->fqn.skipLast(1)));
Set<QualifiedName> result = new HashSet<>();
grouped.forEach((namespace, names) -> {
......@@ -166,7 +213,7 @@ public class ImportOrganizer {
return result;
}
private String serializeImports(List<Import> allImportDeclarations, String newLine) {
protected String serializeImports(List<Import> allImportDeclarations, String newLine) {
if (allImportDeclarations.isEmpty()) {
return "";
}
......
/*******************************************************************************
* Copyright (c) 2019 protos software gmbh (http://www.protos.de).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* CONTRIBUTORS:
* Henrik Rentz-Reichert (initial contribution)
*
*******************************************************************************/
package org.eclipse.etrice.core.common.ui.imports;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog;
import org.eclipse.ui.dialogs.FilteredList;
/**
* @author Henrik Rentz-Reichert
*
* (initial code from org.eclipse.jdt.internal.ui.dialogs.MultiElementListSelectionDialog)
*/
public class NamespaceSelectionDialog extends AbstractElementListSelectionDialog {
private static class Page {
private Object[] elements;
public String filter;
public boolean okState= false;
public Page(Object[] elements) {
this.elements= elements;
}
}
private Page[] fPages;
private int fCurrentPage;
private int fNumberOfPages;
private Button fFinishButton;
private Button fBackButton;
private Button fNextButton;
private Button fSkipButton;
private Label fPageInfoLabel;
private String fPageInfoMessage= "Page {0} of {1}";
private Comparator<?> fComparator;
/**
* Constructs a multi-page list selection dialog.
* @param parent The parent shell
* @param renderer the label renderer.
*/
public NamespaceSelectionDialog(Shell parent, ILabelProvider renderer) {
super(parent, renderer);
}
/**
* Sets message shown in the right top corner. Use {0} and {1} as placeholders
* for the current and the total number of pages.
* @param message the message.
*/
public void setPageInfoMessage(String message) {
fPageInfoMessage= message;
}
/**
* Sets the elements to be displayed in the dialog.
* @param elements an array of pages holding arrays of elements
*/
public void setElements(Object[][] elements) {
fNumberOfPages= elements.length;
fPages= new Page[fNumberOfPages];
for (int i= 0; i != fNumberOfPages; i++)
fPages[i]= new Page(elements[i]);
initializeResult(fNumberOfPages);
}
/*
* @see Window#open()
*/
@SuppressWarnings("unchecked")
@Override
public int open() {
List<Object[]> selection= getInitialElementSelections();
if (selection == null || selection.size() != fNumberOfPages) {
setInitialSelections(new Object[fNumberOfPages]);
selection= getInitialElementSelections();
}
Assert.isTrue(selection.size() == fNumberOfPages);
return super.open();
}
/*
* @see org.eclipse.jface.dialogs.Dialog#createDialogArea(Composite)
*/
@Override
protected Control createDialogArea(Composite parent) {
Composite contents= (Composite) super.createDialogArea(parent);
createMessageArea(contents);
createFilterText(contents);
createFilteredList(contents);
fCurrentPage= 0;
setPageData();
applyDialogFont(contents);
return contents;
}
/*
* @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(Composite)
*/
@Override
protected void createButtonsForButtonBar(Composite parent) {
fSkipButton= createButton(parent, IDialogConstants.SKIP_ID, IDialogConstants.SKIP_LABEL, false);
fBackButton= createButton(parent, IDialogConstants.BACK_ID, IDialogConstants.BACK_LABEL, false);
// XXX: Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=279425
boolean HAS_BUG_279425= true;
fNextButton= createButton(parent, IDialogConstants.NEXT_ID, IDialogConstants.NEXT_LABEL, !HAS_BUG_279425);
fFinishButton= createButton(parent, IDialogConstants.OK_ID, IDialogConstants.FINISH_LABEL, HAS_BUG_279425);
createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
}
/*
* XXX: Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=279425
* The whole method can be removed once that bug is fixed.
* @see org.eclipse.jface.dialogs.Dialog#initializeBounds()
* @since 3.5.1
*/
@Override
protected void initializeBounds() {
super.initializeBounds();
fNextButton.getShell().setDefaultButton(fNextButton);
}
/*
* @see org.eclipse.ui.dialogs.SelectionDialog#createMessageArea(Composite)
*/
@Override
protected Label createMessageArea(Composite parent) {
Composite composite= new Composite(parent, SWT.NONE);
GridLayout layout= new GridLayout();
layout.marginHeight= 0;
layout.marginWidth= 0;
layout.horizontalSpacing= 5;
layout.numColumns= 2;
composite.setLayout(layout);
GridData data= new GridData(GridData.HORIZONTAL_ALIGN_FILL);
composite.setLayoutData(data);
Label messageLabel= super.createMessageArea(composite);
fPageInfoLabel= new Label(composite, SWT.NULL);
fPageInfoLabel.setText(getPageInfoMessage());
data= new GridData(GridData.HORIZONTAL_ALIGN_FILL);
data.horizontalAlignment= GridData.END;
fPageInfoLabel.setLayoutData(data);
applyDialogFont(messageLabel);
return messageLabel;
}
/*
* @see org.eclipse.ui.dialogs.SelectionStatusDialog#computeResult()
*/
@Override
protected void computeResult() {
setResult(fCurrentPage, getSelectedElements());
}
/*
* @see org.eclipse.jface.dialogs.Dialog#buttonPressed(int)
*/
@Override
protected void buttonPressed(int buttonId) {
if (buttonId == IDialogConstants.SKIP_ID) {
boolean isLastPage= fCurrentPage == fNumberOfPages - 1 ? true : false;
turnPage(true, true);
if (isLastPage) {
buttonPressed(IDialogConstants.OK_ID);
}
} else if (buttonId == IDialogConstants.BACK_ID) {
turnPage(false, false);
} else if (buttonId == IDialogConstants.NEXT_ID) {
turnPage(true, false);
} else {
super.buttonPressed(buttonId);
}
}
/**
* @see AbstractElementListSelectionDialog#handleDefaultSelected()
*/
@Override
protected void handleDefaultSelected() {
if (validateCurrentSelection()) {
if (fCurrentPage == fNumberOfPages - 1) {
buttonPressed(IDialogConstants.OK_ID);
} else {
buttonPressed(IDialogConstants.NEXT_ID);
}
}
}
/**
* @see AbstractElementListSelectionDialog#updateButtonsEnableState(IStatus)
*/
@Override
protected void updateButtonsEnableState(IStatus status) {
boolean isOK= !status.matches(IStatus.ERROR);
fPages[fCurrentPage].okState= isOK;
boolean isAllOK= isOK;
for (int i= 0; i != fNumberOfPages; i++)
isAllOK = isAllOK && fPages[i].okState;
fFinishButton.setEnabled(isAllOK);
boolean nextButtonEnabled= isOK && (fCurrentPage < fNumberOfPages - 1);
fNextButton.setEnabled(nextButtonEnabled);
fBackButton.setEnabled(fCurrentPage != 0);
// since there will always be a default selection - see FilteredList.TableUpdateJob.defaultSelect()
fSkipButton.setEnabled(true);
if (nextButtonEnabled) {
getShell().setDefaultButton(fNextButton);
} else if (isAllOK) {
getShell().setDefaultButton(fFinishButton);
}
}
private void turnPage(boolean toNextPage, boolean skipSelection) {
Page page= fPages[fCurrentPage];
// store filter
String filter= getFilter();
if (filter == null)
filter= ""; //$NON-NLS-1$
page.filter= filter;
if (skipSelection) {
setSelection(null);
}
// store selection
Object[] selectedElements= getSelectedElements();
@SuppressWarnings("unchecked")
List<Object[]> list= getInitialElementSelections();
list.set(fCurrentPage, selectedElements);
// store result
setResult(fCurrentPage, selectedElements);
if (toNextPage) {
if (fCurrentPage + 1 >= fNumberOfPages)
return;
fCurrentPage++;
} else {
if (fCurrentPage - 1 < 0)
return;
fCurrentPage--;
}
if (fPageInfoLabel != null && !fPageInfoLabel.isDisposed())
fPageInfoLabel.setText(getPageInfoMessage());
setPageData();
validateCurrentSelection();
}
private void setPageData() {
Page page= fPages[fCurrentPage];
// 1. set elements
setListElements(page.elements);
// 2. apply filter
String filter= page.filter;
if (filter == null)
filter= ""; //$NON-NLS-1$
setFilter(filter);
// 3. select elements
Object[] selectedElements= (Object[]) getInitialElementSelections().get(fCurrentPage);
setSelection(selectedElements);
fFilteredList.setFocus();
}
private String getPageInfoMessage() {
if (fPageInfoMessage == null)
return ""; //$NON-NLS-1$
Object[] args= new Object[] { Integer.toString(fCurrentPage + 1), Integer.toString(fNumberOfPages) };
return MessageFormat.format(fPageInfoMessage, args);
}
private void initializeResult(int length) {
List<Object> result= new ArrayList<>(length);
for (int i= 0; i != length; i++)
result.add(null);
setResult(result);
}
/**
* Gets the current Page.
* @return Returns a int
*/
public int getCurrentPage() {
return fCurrentPage;
}
/**
* Set the <code>Comparator</code> used to sort
* the elements in the List.
*
* @param comparator the comparator to use, not null.
*/
public void setComparator(Comparator<?> comparator) {
fComparator= comparator;
if (fFilteredList != null)
fFilteredList.setComparator(fComparator);
}
@Override
protected FilteredList createFilteredList(Composite parent) {
FilteredList filteredList= super.createFilteredList(parent);
if (fComparator != null) {
filteredList.setComparator(fComparator);
}
return filteredList;
}
}
/*******************************************************************************
* Copyright (c) 2019 protos software gmbh (http://www.protos.de).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* CONTRIBUTORS:
* Henrik Rentz-Reichert (initial contribution)
*
*******************************************************************************/
package org.eclipse.etrice.core.common.ui.imports;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.xtext.naming.QualifiedName;
/**
* @author Henrik Rentz-Reichert
*
*/
public class NamespaceSelectionLabelProvider extends LabelProvider {
@Override
public String getText(Object element) {
if (element instanceof QualifiedName) {
return element.toString();
}
else return super.getText(element);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment