| /******************************************************************************* |
| * 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()); |
| } |
| } |