blob: ac00b69ee1f24cba2d73a6ed3b138c2b5a5f918e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019 GK Software SE, and others.
*
* 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:
* Stephan Herrmann - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.ui.wizards.buildpaths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
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.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.TrayDialog;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.window.Window;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IModuleDescription;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.ui.wizards.NewWizardMessages;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleDependenciesList.ModuleKind;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleDependenciesList.ModulesLabelProvider;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleDialog.ListContentProvider;
public class ModuleSelectionDialog extends TrayDialog {
// widgets:
private TableViewer fViewer;
private Button fOkButton;
private Runnable fFlipMessage; // may show a wait message first, use this to flip to the normal message
boolean fInSetSelection= false; // to avoid re-entrance -> StackOverflow
// input data:
private IJavaProject fJavaProject;
private IClasspathEntry fJREEntry;
// internal storage and one client-provided function:
private Set<String> fAllIncluded; // transitive closure over modules already shown
private List<String> fAvailableModules; // additional modules outside fAllIncluded
private Function<List<String>, Set<String>> fClosureComputation;
private Map<String,IModuleDescription> fModulesByName= new HashMap<>();
// result:
private List<String> fSelectedModules;
/**
* Let the user select among available modules that are not yet included (explicitly or implicitly).
* @param shell for showing the dialog
* @param javaProject the java project whose build path is being configured
* @param jreEntry a classpath entry representing the JRE system library
* @param shownModules set of modules already shown in the LHS list ({@link ModuleDependenciesList})
* @param closureComputation a function from module names to their full transitive closure over 'requires'.
*/
protected ModuleSelectionDialog(Shell shell, IJavaProject javaProject, IClasspathEntry jreEntry, List<String> shownModules, Function<List<String>, Set<String>> closureComputation) {
super(shell);
fJavaProject= javaProject;
fJREEntry= jreEntry;
fAllIncluded= closureComputation.apply(shownModules);
fClosureComputation= closureComputation;
if (jreEntry != null) { // searching only modules from this JRE entry (quick)
List<String> result= new ArrayList<>();
for (IPackageFragmentRoot root : fJavaProject.findUnfilteredPackageFragmentRoots(fJREEntry)) {
checkAddModule(result, root.getModuleDescription());
}
result.sort(String::compareTo);
fAvailableModules= result;
} else { // searching all modules in the workspace (slow)
new Job("Searching modules in workspace") {
@Override
public IStatus run(IProgressMonitor monitor) {
try {
fAvailableModules= searchAvailableModules(monitor);
if (getReturnCode() == Window.CANCEL) {
return Status.CANCEL_STATUS;
}
shell.getDisplay().asyncExec(() -> {
if (fFlipMessage != null) {
fFlipMessage.run();
}
fViewer.setInput(fAvailableModules);
fViewer.refresh();
});
} catch (CoreException e) {
return e.getStatus();
}
return Status.OK_STATUS;
}
}.schedule();
}
}
private List<String> searchAvailableModules(IProgressMonitor monitor) throws CoreException {
List<String> result= new ArrayList<>();
SearchPattern pattern= SearchPattern.createPattern("*", IJavaSearchConstants.MODULE, IJavaSearchConstants.DECLARATIONS, SearchPattern.R_PATTERN_MATCH|SearchPattern.R_CASE_SENSITIVE); //$NON-NLS-1$
SearchRequestor requestor= new SearchRequestor() {
@Override
public void acceptSearchMatch(SearchMatch match) throws CoreException {
Object element= match.getElement();
if (element instanceof IModuleDescription) {
checkAddModule(result, (IModuleDescription) element);
}
}
};
SearchParticipant[] participants= new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() };
new SearchEngine().search(pattern, participants, SearchEngine.createWorkspaceScope(), requestor, monitor);
if (getReturnCode() == Window.CANCEL) { // TODO: should cancelPressed() actively abort the search?
return Collections.emptyList();
}
result.sort(String::compareTo);
return result;
}
void checkAddModule(List<String> result, IModuleDescription moduleDescription) {
if (moduleDescription == null)
return;
if (!fAllIncluded.contains(moduleDescription.getElementName())) {
result.add(moduleDescription.getElementName());
}
fModulesByName.put(moduleDescription.getElementName(), moduleDescription); // hold on to module description to be used for getResult()
}
@Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
newShell.setText(NewWizardMessages.ModuleSelectionDialog_addSystemModules_title);
// TODO:
// PlatformUI.getWorkbench().getHelpSystem().setHelp(newShell, IJavaHelpContextIds.MODULE_DIALOG);
}
@Override
protected int getShellStyle() {
return super.getShellStyle() | SWT.RESIZE;
}
@Override
protected Control createDialogArea(Composite parent) {
Composite composite= (Composite) super.createDialogArea(parent);
Label message= new Label(composite, SWT.NONE);
TableViewer tableViewer= new TableViewer(composite, SWT.MULTI | SWT.BORDER);
tableViewer.setContentProvider(new ListContentProvider());
tableViewer.setLabelProvider(new ModulesLabelProvider(s -> ModuleKind.System));
tableViewer.addSelectionChangedListener(this::selectionChanged);
PixelConverter converter= new PixelConverter(parent);
GridData gd= new GridData(SWT.FILL, SWT.FILL, true, true);
gd.widthHint= converter.convertWidthInCharsToPixels(50);
gd.heightHint= converter.convertHeightInCharsToPixels(20);
tableViewer.getControl().setLayoutData(gd);
if (fAvailableModules == null) {
message.setText("Searching modules in workspace ...");
fFlipMessage= () -> {
message.setText(NewWizardMessages.ModuleSelectionDialog_addSystemModules_message);
};
} else {
tableViewer.setInput(fAvailableModules);
message.setText(NewWizardMessages.ModuleSelectionDialog_addSystemModules_message);
}
fViewer= tableViewer;
return composite;
}
@Override
protected void createButtonsForButtonBar(Composite parent) {
fOkButton = createButton(parent, IDialogConstants.OK_ID,
NewWizardMessages.ModuleSelectionDialog_add_button, true);
createButton(parent, IDialogConstants.CANCEL_ID,
IDialogConstants.CANCEL_LABEL, false);
}
private void selectionChanged(SelectionChangedEvent e) {
IStructuredSelection selection= e.getStructuredSelection();
if (selection == null || selection.isEmpty()) {
fOkButton.setEnabled(false);
return;
}
List<String> selectedNames= selection.toList();
Set<String> closure= fClosureComputation.apply(selectedNames);
if (closure.size() > selectedNames.size()) {
// select all members of the closure:
if (!fInSetSelection) {
fInSetSelection= true;
fViewer.setSelection(new StructuredSelection(new ArrayList<>(closure)));
fInSetSelection= false;
}
}
fOkButton.setEnabled(true);
fSelectedModules= new ArrayList<>(closure); // remember result
}
public List<IModuleDescription> getResult() {
return fSelectedModules.stream()
.filter(m -> !fAllIncluded.contains(m)) // skip modules that are already included
.map(fModulesByName::get)
.collect(Collectors.toList());
}
}