Bug 546352 - [9] New comprehensive UI for Modularity Details
Change-Id: I43d7af9018db152b250e44b7f4428921ad93f563
Signed-off-by: Stephan Herrmann <stephan.herrmann@berlin.de>
diff --git a/org.eclipse.jdt.ui/icons/full/ovr16/auto_mod_ovr.png b/org.eclipse.jdt.ui/icons/full/ovr16/auto_mod_ovr.png
new file mode 100644
index 0000000..2c74726
--- /dev/null
+++ b/org.eclipse.jdt.ui/icons/full/ovr16/auto_mod_ovr.png
Binary files differ
diff --git a/org.eclipse.jdt.ui/icons/full/ovr16/exports_pkg_ovr.png b/org.eclipse.jdt.ui/icons/full/ovr16/exports_pkg_ovr.png
new file mode 100644
index 0000000..f850dc7
--- /dev/null
+++ b/org.eclipse.jdt.ui/icons/full/ovr16/exports_pkg_ovr.png
Binary files differ
diff --git a/org.eclipse.jdt.ui/icons/full/ovr16/opens_pkg_ovr.png b/org.eclipse.jdt.ui/icons/full/ovr16/opens_pkg_ovr.png
new file mode 100644
index 0000000..9b0bbf1
--- /dev/null
+++ b/org.eclipse.jdt.ui/icons/full/ovr16/opens_pkg_ovr.png
Binary files differ
diff --git a/org.eclipse.jdt.ui/icons/full/ovr16/reads_mod_ovr.png b/org.eclipse.jdt.ui/icons/full/ovr16/reads_mod_ovr.png
new file mode 100644
index 0000000..a28b4d7
--- /dev/null
+++ b/org.eclipse.jdt.ui/icons/full/ovr16/reads_mod_ovr.png
Binary files differ
diff --git a/org.eclipse.jdt.ui/icons/full/ovr16/system_mod_ovr.png b/org.eclipse.jdt.ui/icons/full/ovr16/system_mod_ovr.png
new file mode 100644
index 0000000..3ea2f5c
--- /dev/null
+++ b/org.eclipse.jdt.ui/icons/full/ovr16/system_mod_ovr.png
Binary files differ
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/JavaPluginImages.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/JavaPluginImages.java
index 7863cbd..da1233f 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/JavaPluginImages.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/JavaPluginImages.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2018 IBM Corporation and others.
+ * Copyright (c) 2000, 2019 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -444,6 +444,11 @@
public static final ImageDescriptor DESC_OVR_CLASS= createUnManagedCached(T_OVR, "class_tsk.png"); //$NON-NLS-1$
public static final ImageDescriptor DESC_OVR_ABSTRACT_CLASS= createUnManagedCached(T_OVR, "class_abs_tsk.png"); //$NON-NLS-1$
public static final ImageDescriptor DESC_OVR_LIBRARY= createUnManagedCached(T_OVR, "library_ovr.png"); //$NON-NLS-1$
+ public static final ImageDescriptor DESC_OVR_SYSTEM_MOD= createUnManagedCached(T_OVR, "system_mod_ovr.png"); //$NON-NLS-1$
+ public static final ImageDescriptor DESC_OVR_AUTO_MOD= createUnManagedCached(T_OVR, "auto_mod_ovr.png"); //$NON-NLS-1$
+ public static final ImageDescriptor DESC_OVR_EXPORTS= createUnManagedCached(T_OVR, "exports_pkg_ovr.png"); //$NON-NLS-1$
+ public static final ImageDescriptor DESC_OVR_OPENS= createUnManagedCached(T_OVR, "opens_pkg_ovr.png"); //$NON-NLS-1$
+ public static final ImageDescriptor DESC_OVR_READS= createUnManagedCached(T_OVR, "reads_mod_ovr.png"); //$NON-NLS-1$
// Call Hierarchy
public static final ImageDescriptor DESC_OVR_RECURSIVE= createUnManaged(T_OVR, "recursive_co.png"); //$NON-NLS-1$
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/NewWizardMessages.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/NewWizardMessages.java
index 0ca534d..f99c870 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/NewWizardMessages.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/NewWizardMessages.java
@@ -297,6 +297,7 @@
public static String BuildPathsBlock_tab_projects;
public static String BuildPathsBlock_tab_libraries;
public static String BuildPathsBlock_tab_order;
+ public static String BuildPathsBlock_tab_modules;
public static String BuildPathsBlock_classpath_label;
public static String BuildPathsBlock_classpath_up_button;
public static String BuildPathsBlock_classpath_down_button;
@@ -513,7 +514,33 @@
public static String AddReadsBlock_sourceModule_label;
public static String AddReadsBlock_targetModule_label;
//
-
+ public static String ModuleDependenciesPage_modules_label;
+ public static String ModuleDependenciesPage_addSystemModule_button;
+ public static String ModuleDependenciesPage_details_label;
+ // buttons:
+ public static String ModuleDependenciesPage_modules_remove_button;
+ public static String ModuleDependenciesPage_modules_read_button;
+ public static String ModuleDependenciesPage_modules_expose_package_button;
+ public static String ModuleDependenciesPage_modules_patch_button;
+ public static String ModuleDependenciesPage_modules_edit_button;
+ // dialogs:
+ public static String ModuleSelectionDialog_add_button;
+ public static String ModuleSelectionDialog_addSystemModules_message;
+ public static String ModuleSelectionDialog_addSystemModules_title;
+
+ public static String ModuleDependenciesPage_removeModule_dialog_title;
+ public static String ModuleDependenciesPage_removingModule_message;
+ public static String ModuleDependenciesPage_removingModuleTransitive_message;
+ public static String ModuleDependenciesPage_remove_button;
+ public static String ModuleDependenciesPage_cancel_button;
+ public static String ModuleDependenciesPage_removeCurrentModule_error;
+ public static String ModuleDependenciesPage_removeModule_error_with_hint;
+ public static String ModuleDependenciesPage_removeSystemModule_error_hint;
+ // detail tree:
+ public static String ModuleDependenciesAdapter_configured_node;
+ public static String ModuleDependenciesAdapter_declared_node;
+ //
+
public static String EditVariableEntryDialog_title;
public static String EditVariableEntryDialog_filename_varlabel;
public static String EditVariableEntryDialog_filename_variable_button;
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/NewWizardMessages.properties b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/NewWizardMessages.properties
index 5b7d3e8..34aac17 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/NewWizardMessages.properties
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/NewWizardMessages.properties
@@ -277,6 +277,7 @@
BuildPathsBlock_tab_projects=&Projects
BuildPathsBlock_tab_libraries=&Libraries
BuildPathsBlock_tab_order=&Order and Export
+BuildPathsBlock_tab_modules=&Module Dependencies
BuildPathsBlock_classpath_label=Build class path order and e&xported entries:\n(Exported entries are contributed to dependent projects)
@@ -550,6 +551,32 @@
AddReadsBlock_sourceModule_label=Source module
AddReadsBlock_targetModule_label=Target module
+ModuleDependenciesPage_modules_label=All Modules
+ModuleDependenciesPage_addSystemModule_button=Add System Module ...
+ModuleDependenciesPage_details_label=Details
+# buttons
+ModuleDependenciesPage_modules_remove_button=&Remove
+ModuleDependenciesPage_modules_read_button=Rea&d Module
+ModuleDependenciesPage_modules_expose_package_button=E&xpose Package
+ModuleDependenciesPage_modules_patch_button=&Patch from ...
+ModuleDependenciesPage_modules_edit_button=&Edit
+# dialogs:
+ModuleSelectionDialog_add_button=&Add
+ModuleSelectionDialog_addSystemModules_message=Please select modules to be added to the module path.\nTransitively required modules will be automatically selected.
+ModuleSelectionDialog_addSystemModules_title=Select system modules to add
+
+ModuleDependenciesPage_removeModule_dialog_title=Remove Module
+ModuleDependenciesPage_removingModule_message=Removing module {0}
+ModuleDependenciesPage_removingModuleTransitive_message=Removing module {0} and modules depending on it:\n
+ModuleDependenciesPage_remove_button=Remove
+ModuleDependenciesPage_cancel_button=Cancel
+ModuleDependenciesPage_removeCurrentModule_error=Cannot remove the current module
+ModuleDependenciesPage_removeModule_error_with_hint=Cannot remove module {0}{1}
+ModuleDependenciesPage_removeSystemModule_error_hint=\nOnly system modules can be removed here.\n\
+To remove other modules please remove them from the ModulePath (tab Projects or Libraries).
+# detail tree:
+ModuleDependenciesAdapter_configured_node=Configured
+ModuleDependenciesAdapter_declared_node=Declared
# ------- EditVariableEntryDialog -------
EditVariableEntryDialog_title=Edit Variable Entry
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/BuildPathsBlock.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/BuildPathsBlock.java
index 84f6ebf..79fd90d 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/BuildPathsBlock.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/BuildPathsBlock.java
@@ -143,6 +143,7 @@
private BuildPathBasePage fSourceContainerPage;
private ProjectsWorkbookPage fProjectsPage;
private LibrariesWorkbookPage fLibrariesPage;
+ private ModuleDependenciesPage fModulesPage;
private BuildPathBasePage fCurrPage;
@@ -271,10 +272,18 @@
item.setData(ordpage);
item.setControl(ordpage.getControl(folder));
+ fModulesPage= new ModuleDependenciesPage(fClassPathList, fPageContainer);
+ item= new TabItem(folder, SWT.NONE);
+ item.setText(NewWizardMessages.BuildPathsBlock_tab_modules);
+ item.setImage(JavaPluginImages.get(JavaPluginImages.IMG_OBJS_MODULE));
+ item.setData(fModulesPage);
+ item.setControl(fModulesPage.getControl(folder));
+
if (fCurrJProject != null) {
fSourceContainerPage.init(fCurrJProject);
fLibrariesPage.init(fCurrJProject);
fProjectsPage.init(fCurrJProject);
+ fModulesPage.init(fCurrJProject);
fIs9OrHigher= JavaModelUtil.is9OrHigher(fCurrJProject);
}
@@ -357,6 +366,7 @@
fSourceContainerPage.init(fCurrJProject);
fProjectsPage.init(fCurrJProject);
fLibrariesPage.init(fCurrJProject);
+ fModulesPage.init(fCurrJProject);
fIs9OrHigher= JavaModelUtil.is9OrHigher(fCurrJProject);
}
@@ -392,6 +402,7 @@
// update the library and project page if fis9OrHigher changed
fLibrariesPage.init(fCurrJProject);
fProjectsPage.init(fCurrJProject);
+ fModulesPage.init(fCurrJProject);
fIs9OrHigher= is9OrHigherAfter;
}
doStatusLineUpdate();
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/CPListElement.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/CPListElement.java
index ee7321c..865a6c3 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/CPListElement.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/CPListElement.java
@@ -47,6 +47,8 @@
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.LimitModules;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModuleAddExport;
+import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModuleAddExpose;
+import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModuleAddOpens;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModuleAddReads;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModulePatch;
@@ -251,6 +253,10 @@
if (!encodedExports.isEmpty()) {
res.add(JavaCore.newClasspathAttribute(IClasspathAttribute.ADD_EXPORTS, encodedExports));
}
+ String encodedOpens= ModuleEncapsulationDetail.encodeFiltered(detailValue, ModuleAddOpens.class);
+ if (!encodedOpens.isEmpty()) {
+ res.add(JavaCore.newClasspathAttribute(IClasspathAttribute.ADD_OPENS, encodedOpens));
+ }
String encodedReads= ModuleEncapsulationDetail.encodeFiltered(detailValue, ModuleAddReads.class);
if (!encodedReads.isEmpty()) {
res.add(JavaCore.newClasspathAttribute(IClasspathAttribute.ADD_READS, encodedReads));
@@ -784,7 +790,9 @@
if (IClasspathAttribute.PATCH_MODULE.equals(otherAttrib.getName())) {
details.add(ModulePatch.fromString(attribElem, otherAttrib.getValue()));
} else if (IClasspathAttribute.ADD_EXPORTS.equals(otherAttrib.getName())) {
- details.addAll(ModuleAddExport.fromMultiString(attribElem, otherAttrib.getValue()));
+ details.addAll(ModuleAddExpose.fromMultiString(attribElem, otherAttrib.getValue(), true));
+ } else if (IClasspathAttribute.ADD_OPENS.equals(otherAttrib.getName())) {
+ details.addAll(ModuleAddExpose.fromMultiString(attribElem, otherAttrib.getValue(), false));
} else if (IClasspathAttribute.ADD_READS.equals(otherAttrib.getName())) {
details.addAll(ModuleAddReads.fromMultiString(attribElem, otherAttrib.getValue()));
} else if (IClasspathAttribute.LIMIT_MODULES.equals(otherAttrib.getName())) {
@@ -799,7 +807,7 @@
private static boolean isModuleAttribute(String attributeName) {
return Stream.of(IClasspathAttribute.MODULE, IClasspathAttribute.LIMIT_MODULES,
- IClasspathAttribute.PATCH_MODULE, IClasspathAttribute.ADD_EXPORTS, IClasspathAttribute.ADD_READS)
+ IClasspathAttribute.PATCH_MODULE, IClasspathAttribute.ADD_EXPORTS, IClasspathAttribute.ADD_OPENS, IClasspathAttribute.ADD_READS)
.anyMatch(attributeName::equals);
}
@@ -880,6 +888,8 @@
buf.append(IClasspathAttribute.PATCH_MODULE+':'+detail.toString()).append(';');
if (detail instanceof ModuleAddExport)
buf.append(IClasspathAttribute.ADD_EXPORTS+':'+detail.toString()).append(';');
+ if (detail instanceof ModuleAddOpens)
+ buf.append(IClasspathAttribute.ADD_OPENS+':'+detail.toString()).append(';');
if (detail instanceof ModuleAddReads)
buf.append(IClasspathAttribute.ADD_READS+':'+detail.toString()).append(';');
if (detail instanceof LimitModules)
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/LibrariesWorkbookPage.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/LibrariesWorkbookPage.java
index 33f5278..97788d5 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/LibrariesWorkbookPage.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/LibrariesWorkbookPage.java
@@ -371,7 +371,7 @@
}
- private boolean isJREContainer(IPath path) {
+ static boolean isJREContainer(IPath path) {
if (path == null)
return false;
String[] segments= path.segments();
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleAddExportsBlock.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleAddExportsBlock.java
index 112f7c8..fcc1a04 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleAddExportsBlock.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleAddExportsBlock.java
@@ -56,29 +56,37 @@
import org.eclipse.jdt.internal.ui.wizards.IStatusChangeListener;
import org.eclipse.jdt.internal.ui.wizards.NewWizardMessages;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModuleAddExport;
+import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModuleAddExpose;
+import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModuleAddOpens;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.DialogField;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.IDialogFieldListener;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.LayoutUtil;
+import org.eclipse.jdt.internal.ui.wizards.dialogfields.SelectionButtonDialogFieldGroup;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.StringDialogField;
/**
- * UI to define one additional exports (add-exports).
+ * UI to define one additional exports (add-exports) or opens (add-opens).
*/
public class ModuleAddExportsBlock {
+ private static final String[] EXPORTS_OPENS_LABELS= new String[] {"exports", "opens"}; //$NON-NLS-1$ //$NON-NLS-2$
+ private static final int IDX_EXPORTS= 0;
+ private static final int IDX_OPENS= 1;
+
private final IStatusChangeListener fContext;
private StringDialogField fSourceModule;
private StringDialogField fPackage;
private StringDialogField fTargetModules;
+ private SelectionButtonDialogFieldGroup fExposeKindButtons;
private IStatus fSourceModuleStatus;
private IStatus fPackageStatus;
private Control fSWTWidget;
- private final ModuleAddExport fInitialValue;
+ private final ModuleAddExpose fInitialValue;
private IJavaElement[] fSourceJavaElements;
@@ -87,7 +95,7 @@
* @param sourceJavaElements java element representing the source modules from where packages should be exported
* @param initialValue The value to edit
*/
- public ModuleAddExportsBlock(IStatusChangeListener context, IJavaElement[] sourceJavaElements, ModuleAddExport initialValue) {
+ public ModuleAddExportsBlock(IStatusChangeListener context, IJavaElement[] sourceJavaElements, ModuleAddExpose initialValue) {
fContext= context;
fInitialValue= initialValue;
fSourceJavaElements= sourceJavaElements;
@@ -109,6 +117,10 @@
fTargetModules= new StringDialogField();
fTargetModules.setDialogFieldListener(adapter);
fTargetModules.setLabelText(NewWizardMessages.AddExportsBlock_targetModules_label);
+
+ fExposeKindButtons= new SelectionButtonDialogFieldGroup(SWT.RADIO, EXPORTS_OPENS_LABELS, 2);
+ fExposeKindButtons.setSelection(IDX_EXPORTS, initialValue instanceof ModuleAddExport);
+ fExposeKindButtons.setSelection(IDX_OPENS, initialValue instanceof ModuleAddOpens);
setDefaults();
}
@@ -195,13 +207,16 @@
return sourceModule+'/'+pack+'='+targetModules;
}
- public ModuleAddExport getExport(CPListElementAttribute parentAttribute) {
+ public ModuleAddExpose getExport(CPListElementAttribute parentAttribute) {
String sourceModule= getSourceModuleText();
String pack= getPackageText();
String targetModules= getTargetModulesText();
if (sourceModule.isEmpty() || pack.isEmpty() || targetModules.isEmpty())
return null;
- return new ModuleAddExport(sourceModule, pack, targetModules, parentAttribute);
+ if (fExposeKindButtons.isSelected(IDX_EXPORTS))
+ return new ModuleAddExport(sourceModule, pack, targetModules, parentAttribute);
+ else
+ return new ModuleAddOpens(sourceModule, pack, targetModules, parentAttribute);
}
/**
@@ -262,6 +277,8 @@
BidiUtils.applyBidiProcessing(targetModulesField, StructuredTextTypeHandlerFactory.JAVA);
DialogField.createEmptySpace(composite, 2);
+
+ fExposeKindButtons.doFillIntoGrid(composite, 2);
Dialog.applyDialogFont(composite);
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleAddExportsDialog.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleAddExportsDialog.java
index 5f346bc..82db503 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleAddExportsDialog.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleAddExportsDialog.java
@@ -31,7 +31,7 @@
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.wizards.IStatusChangeListener;
import org.eclipse.jdt.internal.ui.wizards.NewWizardMessages;
-import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModuleAddExport;
+import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModuleAddExpose;
/**
* A dialog to configure add-exports of a library.
@@ -49,7 +49,7 @@
* @param sourceJavaElements java elements representing the source modules from where packages should be exported
* @param value The value to edit.
*/
- public ModuleAddExportsDialog(Shell parent, IJavaElement[] sourceJavaElements, ModuleAddExport value) {
+ public ModuleAddExportsDialog(Shell parent, IJavaElement[] sourceJavaElements, ModuleAddExpose value) {
super(parent);
IStatusChangeListener listener= new IStatusChangeListener() {
@@ -121,7 +121,7 @@
*
* @return the configured export value, or {@code null} if no export was configured.
*/
- public ModuleAddExport getExport(CPListElementAttribute parentAttribute) {
+ public ModuleAddExpose getExport(CPListElementAttribute parentAttribute) {
return fAddExportsBlock.getExport(parentAttribute);
}
}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleDependenciesAdapter.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleDependenciesAdapter.java
new file mode 100644
index 0000000..67a7d04
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleDependenciesAdapter.java
@@ -0,0 +1,730 @@
+/*******************************************************************************
+ * 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.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+
+import com.ibm.icu.text.MessageFormat;
+
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Shell;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.ResourcesPlugin;
+
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.window.Window;
+
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IModuleDescription;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.ui.JavaPluginImages;
+import org.eclipse.jdt.internal.ui.viewsupport.ImageDescriptorRegistry;
+import org.eclipse.jdt.internal.ui.viewsupport.JavaElementImageProvider;
+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.ModuleDependenciesPage.DecoratedImageDescriptor;
+import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModuleAddExport;
+import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModuleAddExpose;
+import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModuleAddOpens;
+import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModuleAddReads;
+import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModulePatch;
+import org.eclipse.jdt.internal.ui.wizards.dialogfields.DialogField;
+import org.eclipse.jdt.internal.ui.wizards.dialogfields.IDialogFieldListener;
+import org.eclipse.jdt.internal.ui.wizards.dialogfields.ITreeListAdapter;
+import org.eclipse.jdt.internal.ui.wizards.dialogfields.TreeListDialogField;
+
+/**
+ * Implementation of the right-hand pane of the {@link ModuleDependenciesPage}.
+ */
+class ModuleDependenciesAdapter implements IDialogFieldListener, ITreeListAdapter<Object> {
+
+ private static final int IDX_REMOVE= 0;
+
+ private static final int IDX_READ_MODULE= 2;
+ private static final int IDX_EXPOSE_PACKAGE= 3;
+
+ private static final int IDX_PATCH= 5;
+
+ private static final int IDX_EDIT= 7;
+
+ abstract static class Details {
+
+ /** the module selected in the LHS pane, for which details are being shown / edited in this RHS pane. */
+ protected final IModuleDescription fFocusModule;
+ /** the classpath element by which the current project refers to the focus module. */
+ protected final CPListElement fElem;
+
+ public Details(IModuleDescription focusModule, CPListElement elem) {
+ fFocusModule= focusModule;
+ fElem= elem;
+ }
+
+ /**
+ * Answer the module of the current IJavaProject for which the build path is being configured.
+ * @return the module of the current project, or {@code null}.
+ */
+ protected IModuleDescription getContextModule() {
+ try {
+ IModuleDescription moduleDescription= fElem.getJavaProject().getModuleDescription();
+ if (moduleDescription != null) {
+ return moduleDescription;
+ }
+ } catch (JavaModelException jme) {
+ JavaPlugin.log(jme.getStatus());
+ }
+ return null;
+ }
+ protected String getContextModuleName() {
+ try {
+ IModuleDescription moduleDescription= fElem.getJavaProject().getModuleDescription();
+ if (moduleDescription != null) {
+ return moduleDescription.getElementName();
+ }
+ } catch (JavaModelException jme) {
+ JavaPlugin.log(jme.getStatus());
+ }
+ return ""; //$NON-NLS-1$
+ }
+
+ protected IJavaProject getContextProject() {
+ return fElem.getJavaProject();
+ }
+ }
+
+ /** Synthetic tree node as a parent for details that are declared by the focus module (in its module-info) */
+ static class DeclaredDetails extends Details {
+
+ public DeclaredDetails(IModuleDescription mod, CPListElement elem) {
+ super(mod, elem);
+ }
+
+ /**
+ * Answer all packages accessible to the context module (exports/opens)
+ * @return accessible packages represented by {@link AccessiblePackage}.
+ */
+ public Object[] getPackages() {
+ try {
+ if (fFocusModule != null && !fFocusModule.isAutoModule()) {
+ IModuleDescription contextModule= getContextModule();
+ String[] exported= fFocusModule.getExportedPackageNames(contextModule);
+ List<AccessiblePackage> result= new ArrayList<>();
+ for (String export : exported) {
+ result.add(new AccessiblePackage(export, AccessiblePackage.Kind.Exports, this));
+ }
+ String[] opened= fFocusModule.getOpenedPackageNames(contextModule);
+ for (String open : opened) {
+ result.add(new AccessiblePackage(open, AccessiblePackage.Kind.Opens, this));
+ }
+ return result.toArray();
+ }
+ } catch (JavaModelException e) {
+ JavaPlugin.log(e);
+ }
+ return new Object[0];
+ }
+ }
+
+ /** Synthetic tree node. */
+ static class ConfiguredDetails extends Details {
+ private ModuleKind fKind;
+
+ public ConfiguredDetails(IModuleDescription focusModule, CPListElement elem, ModuleKind moduleKind) {
+ super(focusModule, elem);
+ fKind= moduleKind;
+ }
+
+ public Object[] getChildren() {
+ if (fKind == ModuleKind.System) {
+ // aggregate attribute is in the parent (corresponding to the JRE)
+ Object parent= fElem.getParentContainer();
+ if (parent instanceof CPListElement) {
+ Object attribute= ((CPListElement) parent).getAttribute(CPListElement.MODULE);
+ if (attribute instanceof ModuleEncapsulationDetail[]) {
+ return convertEncapsulationDetails((ModuleEncapsulationDetail[]) attribute, fFocusModule.getElementName());
+ }
+ }
+ }
+ Object attribute= fElem.getAttribute(CPListElement.MODULE);
+ if (attribute instanceof ModuleEncapsulationDetail[]) {
+ return convertEncapsulationDetails((ModuleEncapsulationDetail[]) attribute, null);
+ }
+ return fElem.getChildren(true);
+ }
+
+ private DetailNode<?>[] convertEncapsulationDetails(ModuleEncapsulationDetail[] attribute, String filterModule) {
+ List<DetailNode<?>> filteredDetails= new ArrayList<>();
+ for (ModuleEncapsulationDetail detail : attribute) {
+ if (detail instanceof ModuleAddExpose) {
+ ModuleAddExpose moduleAddExpose= (ModuleAddExpose) detail;
+ if (filterModule == null || filterModule.equals(moduleAddExpose.fSourceModule)) {
+ AccessiblePackage.Kind kind= moduleAddExpose instanceof ModuleAddExport ? AccessiblePackage.Kind.Exports : AccessiblePackage.Kind.Opens;
+ filteredDetails.add(new AccessiblePackage(moduleAddExpose.fPackage, kind, this));
+ }
+ } else if (detail instanceof ModuleAddReads) {
+ ModuleAddReads moduleAddReads= (ModuleAddReads) detail;
+ if (filterModule == null || filterModule.equals(moduleAddReads.fSourceModule)) {
+ filteredDetails.add(new ReadModule(moduleAddReads.fTargetModule, this));
+ }
+ } else if (detail instanceof ModulePatch) {
+ ModulePatch modulePatch= (ModulePatch) detail;
+ if (filterModule == null || filterModule.equals(modulePatch.fModule)) {
+ try {
+ if (modulePatch.fPaths != null) {
+ IPath path= new Path(modulePatch.fPaths);
+ IFolder folder= ResourcesPlugin.getWorkspace().getRoot().getFolder(path);
+ IJavaElement elem= JavaCore.create(folder.getProject()).getPackageFragmentRoot(folder);
+ if (elem instanceof IPackageFragmentRoot) {
+ filteredDetails.add(new PatchModule((IPackageFragmentRoot) elem, this));
+ }
+ } else {
+ for (IClasspathEntry entry : fElem.getJavaProject().getRawClasspath()) {
+ if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
+ for (IPackageFragmentRoot root : fElem.getJavaProject().findPackageFragmentRoots(entry)) {
+ if (root.getKind() == IPackageFragmentRoot.K_SOURCE)
+ filteredDetails.add(new PatchModule(root, this));
+ }
+ }
+ }
+ }
+ } catch (JavaModelException e) {
+ JavaPlugin.log(e);
+ }
+ }
+ }
+ }
+ return filteredDetails.toArray(new DetailNode<?>[filteredDetails.size()]);
+ }
+ public void removeAll() {
+ if (fKind == ModuleKind.System) {
+ // aggregate attribute is in the parent (corresponding to the JRE)
+ Object parent= fElem.getParentContainer();
+ if (parent instanceof CPListElement) {
+ CPListElement jreElement= (CPListElement) parent;
+ Object attribute= jreElement.getAttribute(CPListElement.MODULE);
+ if (attribute instanceof ModuleEncapsulationDetail[]) {
+ // need to filter so we remove only affected details:
+ ModuleEncapsulationDetail[] filtered= Arrays.stream((ModuleEncapsulationDetail[]) attribute)
+ .filter(d -> !d.affects(fFocusModule.getElementName()))
+ .toArray(ModuleEncapsulationDetail[]::new);
+ jreElement.setAttribute(CPListElement.MODULE, filtered);
+ return;
+ }
+ }
+ }
+ Object attribute= fElem.getAttribute(CPListElement.MODULE);
+ if (attribute instanceof ModuleEncapsulationDetail[]) {
+ fElem.setAttribute(CPListElement.MODULE, new ModuleEncapsulationDetail[0]);
+ }
+ }
+ public boolean addOrEditAccessiblePackage(AccessiblePackage selectedPackage, Shell shell) {
+ Object container= fElem.getParentContainer();
+ CPListElement element= (container instanceof CPListElement) ? (CPListElement) container : fElem;
+ CPListElementAttribute moduleAttribute= element.findAttributeElement(CPListElement.MODULE);
+
+ IPackageFragmentRoot packRoot= (IPackageFragmentRoot) fFocusModule.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
+ String packageName= selectedPackage != null ? selectedPackage.getName() : ""; //$NON-NLS-1$
+
+ ModuleAddExpose initial= (selectedPackage != null)
+ ? selectedPackage.convertToCP(moduleAttribute)
+ : new ModuleAddExport(fFocusModule.getElementName(), packageName, getContextModuleName(), moduleAttribute);
+
+ ModuleAddExportsDialog dialog= new ModuleAddExportsDialog(shell, new IJavaElement[] { packRoot }, initial);
+ if (dialog.open() == Window.OK) {
+ ModuleAddExpose expose= dialog.getExport(moduleAttribute);
+ if (expose != null) {
+ Object attribute= moduleAttribute.getValue();
+ ModuleEncapsulationDetail[] arrayValue= null;
+ if (attribute instanceof ModuleEncapsulationDetail[]) {
+ arrayValue= (ModuleEncapsulationDetail[]) attribute;
+ if (selectedPackage != null) {
+ // editing: replace existing entry
+ for (int i= 0; i < arrayValue.length; i++) {
+ ModuleEncapsulationDetail detail= arrayValue[i];
+ if (detail.equals(initial)) {
+ arrayValue[i]= expose;
+ break;
+ }
+ }
+ } else {
+ arrayValue= Arrays.copyOf(arrayValue, arrayValue.length+1);
+ arrayValue[arrayValue.length-1]= expose;
+ }
+ } else {
+ arrayValue= new ModuleEncapsulationDetail[] { expose };
+ }
+ element.setAttribute(CPListElement.MODULE, arrayValue);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void remove(List<Object> selectedElements) {
+ CPListElementAttribute moduleAttribute;
+ if (fKind == ModuleKind.System) {
+ moduleAttribute= ((CPListElement) fElem.getParentContainer()).findAttributeElement(CPListElement.MODULE);
+ } else {
+ moduleAttribute= fElem.findAttributeElement(CPListElement.MODULE);
+ }
+ if (moduleAttribute == null) {
+ // TODO report
+ return;
+ }
+ if (!(moduleAttribute.getValue() instanceof ModuleEncapsulationDetail[])) {
+ // TODO report
+ return;
+ }
+ List<ModuleEncapsulationDetail> details= new ArrayList<>(Arrays.asList((ModuleEncapsulationDetail[]) moduleAttribute.getValue()));
+ for (Object node : selectedElements) {
+ if (node instanceof DetailNode<?>) {
+ ModuleEncapsulationDetail med= ((DetailNode<?>) node).convertToCP(moduleAttribute);
+ if (med != null) {
+ details.remove(med);
+ }
+ } else if (node instanceof ConfiguredDetails) {
+ ((ConfiguredDetails) node).removeAll();
+ return; // covers all details, changes in 'details' are irrelevant
+ }
+ }
+ moduleAttribute.setValue(details.toArray(new ModuleEncapsulationDetail[details.size()]));
+ }
+
+ public boolean addReads(Shell shell) {
+ Object container= fElem.getParentContainer();
+ CPListElement element= (container instanceof CPListElement) ? (CPListElement) container : fElem;
+ CPListElementAttribute moduleAttribute= element.findAttributeElement(CPListElement.MODULE);
+ if (moduleAttribute == null) {
+ return false; // TODO report
+ }
+
+ List<String> irrelevantModules;
+ try {
+ irrelevantModules= Arrays.asList(fFocusModule.getRequiredModuleNames());
+ } catch (JavaModelException e) {
+ JavaPlugin.log(e.getStatus());
+ irrelevantModules= Collections.emptyList();
+ }
+ irrelevantModules= new ArrayList<>(irrelevantModules);
+ irrelevantModules.add(fFocusModule.getElementName());
+
+ ModuleSelectionDialog dialog= new ModuleSelectionDialog(shell, fElem.getJavaProject(), null, irrelevantModules, HashSet::new);
+ if (dialog.open() != 0) {
+ return false;
+ }
+
+ List<IModuleDescription> result= dialog.getResult();
+ editModularityDetails(element, moduleAttribute, result,
+ null,
+ mod -> new ModuleAddReads(fFocusModule.getElementName(), mod.getElementName(), moduleAttribute));
+ return true;
+ }
+
+ public boolean addPatch(Shell shell, Map<String, String> patchMap) {
+ Object container= fElem.getParentContainer();
+ CPListElement element= (container instanceof CPListElement) ? (CPListElement) container : fElem;
+ CPListElementAttribute moduleAttribute= element.findAttributeElement(CPListElement.MODULE);
+ if (moduleAttribute == null) {
+ return false; // TODO report
+ }
+ IJavaProject contextProject= getContextProject();
+ ModulePatchSourceSelectionDialog dialog= new ModulePatchSourceSelectionDialog(shell, fFocusModule, contextProject);
+ if (dialog.open() != 0) {
+ return false;
+ }
+ List<IPackageFragmentRoot> result= dialog.getResult();
+ for (IPackageFragmentRoot root : result) {
+ String rootPath= root.getPath().toString();
+ if (patchMap.containsKey(rootPath)) {
+ if (!MessageDialog.openQuestion(shell, "Patch module conflict",
+ MessageFormat.format("The source folder {0} was declared to patch module {1}, change to replacing module {2} instead?",
+ root.getPath(), patchMap.get(rootPath), fFocusModule.getElementName()))) {
+ return false;
+ }
+ }
+ }
+ editModularityDetails(element, moduleAttribute, result,
+ (r,med) -> med instanceof ModulePatch && r.getPath().toString().equals(((ModulePatch)med).fPaths),
+ root -> new ModulePatch(fFocusModule.getElementName(), root.getPath().toString(), moduleAttribute));
+ return true;
+ }
+
+ private <T> void editModularityDetails(CPListElement element, CPListElementAttribute moduleAttribute, List<T> result,
+ BiPredicate<T,ModuleEncapsulationDetail> shouldReplace, Function<T,ModuleEncapsulationDetail> factory) {
+ ModuleEncapsulationDetail[] arrayValue= null;
+ int idx= 0;
+ Object attribute= moduleAttribute.getValue();
+ if (attribute instanceof ModuleEncapsulationDetail[]) {
+ List<T> remaining= new ArrayList<>();
+ arrayValue= (ModuleEncapsulationDetail[]) attribute;
+ if (shouldReplace != null) {
+ allResults:
+ for (T newValue : result) {
+ for (int i= 0; i < arrayValue.length; i++) {
+ if (shouldReplace.test(newValue, arrayValue[i])) {
+ arrayValue[i]= factory.apply(result.get(0));
+ continue allResults;
+ }
+ }
+ remaining.add(newValue);
+ }
+ if (remaining.isEmpty())
+ return;
+ result= remaining;
+ }
+ idx= arrayValue.length;
+ arrayValue= Arrays.copyOf(arrayValue, arrayValue.length+result.size());
+ } else {
+ arrayValue= new ModuleEncapsulationDetail[result.size()];
+ }
+ for (T detail : result) {
+ arrayValue[idx++]= factory.apply(detail);
+ }
+ element.setAttribute(CPListElement.MODULE, arrayValue);
+ }
+ }
+
+ abstract static class DetailNode<D extends ModuleEncapsulationDetail> {
+ protected String fName;
+ protected Details fParent;
+
+ protected DetailNode(Details parent) {
+ fParent= parent;
+ }
+ public String getName() {
+ return fName;
+ }
+ public boolean isIsConfigured() {
+ return fParent instanceof ConfiguredDetails;
+ }
+ public abstract D convertToCP(CPListElementAttribute attribElem);
+ }
+
+ /** Declare that the package denoted by {@link #fName} is accessible (exported/opened) to the current context module. */
+ static class AccessiblePackage extends DetailNode<ModuleAddExpose> {
+ enum Kind { Exports, Opens;
+ ImageDescriptor getDecoration() {
+ switch (this) {
+ case Exports: return JavaPluginImages.DESC_OVR_EXPORTS;
+ case Opens: return JavaPluginImages.DESC_OVR_OPENS;
+ default: return null;
+ }
+ }
+ }
+ private Kind fKind;
+ public AccessiblePackage(String name, Kind kind, Details parent) {
+ super(parent);
+ fName= name;
+ fKind= kind;
+ }
+ public Kind getKind() {
+ return fKind;
+ }
+ @Override
+ public ModuleAddExpose convertToCP(CPListElementAttribute attribElem) {
+ if (fParent instanceof ConfiguredDetails) {
+ String targetModule= fParent.getContextModuleName();
+ if (targetModule != null) {
+ switch (fKind) {
+ case Exports:
+ return new ModuleAddExport(fParent.fFocusModule.getElementName(), fName, targetModule, attribElem);
+ case Opens:
+ return new ModuleAddOpens(fParent.fFocusModule.getElementName(), fName, targetModule, attribElem);
+ default:
+ break;
+ }
+ }
+ }
+ return null; // TODO: report
+ }
+ }
+
+ /** Declare that the module given by {@link #fName} is read by the selected focus module. */
+ static class ReadModule extends DetailNode<ModuleAddReads> {
+
+ public ReadModule(String targetModule, Details parent) {
+ super(parent);
+ fName= targetModule;
+ }
+
+ @Override
+ public ModuleAddReads convertToCP(CPListElementAttribute attribElem) {
+ if (fParent instanceof ConfiguredDetails) {
+ return new ModuleAddReads(fParent.fFocusModule.getElementName(), fName, attribElem);
+ }
+ return null; // TODO: report
+ }
+ }
+ /** Declare that the selected focus module is patched by the content of the given package fragment root. */
+ static class PatchModule extends DetailNode<ModulePatch> {
+ private IPackageFragmentRoot fRoot;
+
+ public PatchModule(IPackageFragmentRoot root, Details parent) {
+ super(parent);
+ fRoot= root;
+ fName= root.getPath().makeRelative().toString();
+ }
+
+ @Override
+ public ModulePatch convertToCP(CPListElementAttribute attribElem) {
+ return new ModulePatch(fParent.fFocusModule.getElementName(), attribElem); // assumes root is "the source folder" of the current context project
+ }
+ }
+
+ static class ModularityDetailsLabelProvider extends CPListLabelProvider {
+ private ImageDescriptorRegistry fRegistry= JavaPlugin.getImageDescriptorRegistry();
+ private JavaElementImageProvider fImageLabelProvider= new JavaElementImageProvider();
+ @Override
+ public String getText(Object element) {
+ if (element instanceof DeclaredDetails) {
+ return NewWizardMessages.ModuleDependenciesAdapter_declared_node;
+ }
+ if (element instanceof ConfiguredDetails) {
+ return NewWizardMessages.ModuleDependenciesAdapter_configured_node;
+ }
+ if (element instanceof DetailNode) {
+ return ((DetailNode<?>) element).getName();
+ }
+ return super.getText(element);
+ }
+ @Override
+ public Image getImage(Object element) {
+ if (element instanceof CPListElement) {
+ return fRegistry.get(JavaPluginImages.DESC_OBJS_MODULE);
+ }
+ if (element instanceof AccessiblePackage) {
+ AccessiblePackage.Kind kind= ((AccessiblePackage) element).getKind();
+ ImageDescriptor imgDesc= new DecoratedImageDescriptor(JavaPluginImages.DESC_OBJS_PACKAGE, kind.getDecoration(), true);
+ return JavaPlugin.getImageDescriptorRegistry().get(imgDesc);
+ }
+ if (element instanceof ReadModule) {
+ ImageDescriptor imgDesc= new DecoratedImageDescriptor(JavaPluginImages.DESC_OBJS_MODULE,
+ JavaPluginImages.DESC_OVR_READS, true);
+ return JavaPlugin.getImageDescriptorRegistry().get(imgDesc);
+ }
+ if (element instanceof PatchModule) {
+ return fImageLabelProvider.getImageLabel(((PatchModule) element).fRoot, 0);
+ }
+ return super.getImage(element);
+ }
+ }
+
+ static class ElementSorter extends CPListElementSorter {
+ @Override
+ public int compare(Viewer viewer, Object e1, Object e2) {
+ // sorting for root nodes: CPListElement > DeclaredDetails > ConfiguredDetails
+ if (e1 instanceof DeclaredDetails) {
+ return e2 instanceof ConfiguredDetails ? -1 : 1;
+ }
+ if (e1 instanceof ConfiguredDetails) {
+ return e2 instanceof DeclaredDetails ? 1 : -1;
+ }
+ if (e2 instanceof DeclaredDetails || e2 instanceof ConfiguredDetails) {
+ return -1;
+ }
+ return super.compare(viewer, e1, e2);
+ }
+ }
+
+ public static void enableDefaultButtons(TreeListDialogField<?> list, boolean enable, boolean removeEnabled) {
+ list.enableButton(IDX_REMOVE, removeEnabled);
+ list.enableButton(IDX_EXPOSE_PACKAGE, enable);
+ list.enableButton(IDX_READ_MODULE, enable);
+ list.enableButton(IDX_PATCH, enable);
+ }
+
+ private final ModuleDependenciesPage fModuleDependenciesPage; // parent structure
+ private TreeListDialogField<Object> fDetailsList; // RHS widget managed by this class
+
+ public ModuleDependenciesAdapter(ModuleDependenciesPage moduleDependenciesPage) {
+ fModuleDependenciesPage= moduleDependenciesPage;
+ }
+
+ public void setList(TreeListDialogField<Object> detailsList) {
+ fDetailsList= detailsList;
+ fDetailsList.enableButton(IDX_REMOVE, false);
+ fDetailsList.enableButton(IDX_EXPOSE_PACKAGE, false);
+ fDetailsList.enableButton(IDX_READ_MODULE, false);
+ fDetailsList.enableButton(IDX_PATCH, false);
+ fDetailsList.enableButton(IDX_EDIT, false);
+ }
+
+ // -------- IListAdapter --------
+ @Override
+ public void customButtonPressed(TreeListDialogField<Object> field, int index) {
+ AccessiblePackage selectedPackage= null;
+ List<Object> selectedElements= field.getSelectedElements();
+ switch (index) {
+ case IDX_REMOVE:
+ if (selectedElements.size() == 0) {
+ // no detail selected, remove the module(s) (with question):
+ fModuleDependenciesPage.removeModules();
+ } else {
+ getConfiguredDetails().remove(selectedElements);
+ }
+ field.refresh();
+ break;
+ case IDX_EDIT:
+ if (selectedElements.size() == 1 && selectedElements.get(0) instanceof AccessiblePackage) {
+ selectedPackage= (AccessiblePackage) selectedElements.get(0);
+ }
+ // FIXME: can no longer use fallthrough, when editing other details
+ //$FALL-THROUGH$
+ case IDX_EXPOSE_PACKAGE:
+ if (getConfiguredDetails().addOrEditAccessiblePackage(selectedPackage, fModuleDependenciesPage.getShell())) {
+ field.refresh();
+ }
+ break;
+ case IDX_READ_MODULE:
+ if (getConfiguredDetails().addReads(fModuleDependenciesPage.getShell())) {
+ field.refresh();
+ }
+ break;
+ case IDX_PATCH:
+ if (getConfiguredDetails().addPatch(fModuleDependenciesPage.getShell(), fModuleDependenciesPage.fPatchMap)) {
+ field.refresh();
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Non-existent button index "+index); //$NON-NLS-1$
+ }
+ }
+
+ private ConfiguredDetails getConfiguredDetails() {
+ for (Object object : fDetailsList.getElements()) {
+ if (object instanceof ConfiguredDetails)
+ return (ConfiguredDetails) object;
+ }
+ throw new IllegalStateException("detail list has no ConfiguredDetails element"); //$NON-NLS-1$
+ }
+
+ @Override
+ public void selectionChanged(TreeListDialogField<Object> field) {
+ List<Object> selected= fDetailsList.getSelectedElements();
+ boolean enable= false;
+ if (selected.size() == 1) {
+ Object selectedNode= selected.get(0);
+ enable= isConfigurableNode(selectedNode);
+ fDetailsList.enableButton(IDX_EDIT, enable && selectedNode instanceof DetailNode<?>);
+ return;
+ } else {
+ enable= allAreConfigurable(selected);
+ fDetailsList.enableButton(IDX_EDIT, false);
+ }
+ fDetailsList.enableButton(IDX_EXPOSE_PACKAGE, enable);
+ fDetailsList.enableButton(IDX_READ_MODULE, enable);
+ fDetailsList.enableButton(IDX_PATCH, enable);
+ fDetailsList.enableButton(IDX_REMOVE, enable);
+ }
+
+ private boolean allAreConfigurable(List<Object> selected) {
+ for (Object node : selected) {
+ if (!isConfigurableNode(node))
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isConfigurableNode(Object node) {
+ if (node instanceof ConfiguredDetails) {
+ return true;
+ }
+ if (node instanceof DeclaredDetails) {
+ return false;
+ }
+ if (node instanceof DetailNode) {
+ return ((DetailNode<?>) node).isIsConfigured();
+ }
+ return true;
+ }
+
+ @Override
+ public void doubleClicked(TreeListDialogField<Object> field) {
+ List<Object> selectedElements= fDetailsList.getSelectedElements();
+ if (selectedElements.size() == 1) {
+ Object selected= selectedElements.get(0);
+ if (selected instanceof ReadModule) {
+ String moduleName= ((ReadModule) selected).getName();
+ fModuleDependenciesPage.setSelectionToModule(moduleName);
+ } else {
+ TreeViewer treeViewer= fDetailsList.getTreeViewer();
+ boolean isExpanded= treeViewer.getExpandedState(selectedElements.get(0));
+ if (isExpanded) {
+ treeViewer.collapseToLevel(selectedElements.get(0), 1);
+ } else {
+ treeViewer.expandToLevel(selectedElements.get(0), 1);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void keyPressed(TreeListDialogField<Object> field, KeyEvent event) {
+// libaryPageKeyPressed(field, event);
+ }
+
+ @Override
+ public Object[] getChildren(TreeListDialogField<Object> field, Object element) {
+ if (element instanceof CPListElement) { // assumed to be root
+ // no direct children
+ } else if (element instanceof DeclaredDetails) {
+ return ((DeclaredDetails) element).getPackages();
+ } else if (element instanceof ConfiguredDetails) {
+ return ((ConfiguredDetails) element).getChildren();
+ }
+ return new Object[0];
+ }
+
+ @Override
+ public Object getParent(TreeListDialogField<Object> field, Object element) {
+ if (element instanceof CPListElementAttribute) {
+ return ((CPListElementAttribute) element).getParent();
+ }
+ // TODO
+ return null;
+ }
+
+ @Override
+ public boolean hasChildren(TreeListDialogField<Object> field, Object element) {
+ Object[] children= getChildren(field, element);
+ return children != null && children.length > 0;
+ }
+ // ---------- IDialogFieldListener --------
+
+ @Override
+ public void dialogFieldChanged(DialogField field) {
+// libaryPageDialogFieldChanged(field);
+ }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleDependenciesList.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleDependenciesList.java
new file mode 100644
index 0000000..95cc61a
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleDependenciesList.java
@@ -0,0 +1,204 @@
+/*******************************************************************************
+ * 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.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+
+import org.eclipse.jface.layout.PixelConverter;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TableViewer;
+
+import org.eclipse.jdt.core.IModuleDescription;
+
+import org.eclipse.jdt.ui.JavaElementImageDescriptor;
+
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.ui.JavaPluginImages;
+import org.eclipse.jdt.internal.ui.viewsupport.JavaElementImageProvider;
+import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleDialog.ListContentProvider;
+
+/**
+ * List widget for the left-hand pane showing all modules in the module graph
+ */
+class ModuleDependenciesList {
+
+ static class FocusAwareStringComparator implements Comparator<String> {
+ private String fFocusString;
+
+ public FocusAwareStringComparator(String focusString) {
+ fFocusString= focusString;
+ }
+ @Override
+ public int compare(String o1, String o2) {
+ if (o1.equals(fFocusString)) {
+ return -1;
+ }
+ if (o2.equals(fFocusString)) {
+ return 1;
+ }
+ return o1.compareTo(o2);
+ }
+ }
+
+ static ImageDescriptor DESC_OBJ_MODULE = new JavaElementImageDescriptor(JavaPluginImages.DESC_OBJS_MODULE, 0,
+ JavaElementImageProvider.BIG_SIZE);
+
+ static class ModulesLabelProvider extends LabelProvider implements ITableLabelProvider {
+ Function<String,ModuleKind> fGetModuleKind;
+
+ public ModulesLabelProvider(Function<String, ModuleKind> getModuleKind) {
+ fGetModuleKind= getModuleKind;
+ }
+
+ @Override
+ public Image getColumnImage(Object element, int columnIndex) {
+ ModuleKind kind= fGetModuleKind.apply((String) element);
+ ImageDescriptor imgDesc= new ModuleDependenciesPage.DecoratedImageDescriptor(
+ DESC_OBJ_MODULE, kind.getDecoration(), kind != ModuleKind.Focus
+ );
+ return JavaPlugin.getImageDescriptorRegistry().get(imgDesc);
+ }
+
+ @Override
+ public String getColumnText(Object element, int columnIndex) {
+ return element.toString();
+ }
+ }
+
+ enum ModuleKind {
+ Normal, Focus, System, UpgradedSystem, Automatic;
+
+ public ImageDescriptor getDecoration() {
+ switch (this) {
+ case Focus:
+ return JavaPluginImages.DESC_OVR_FOCUS;
+ case Automatic:
+ return JavaPluginImages.DESC_OVR_AUTO_MOD;
+ case System:
+ return JavaPluginImages.DESC_OVR_SYSTEM_MOD;
+ //$CASES-OMITTED$
+ default:
+ return null;
+ }
+ }
+ }
+ public final List<String> fNames= new ArrayList<>();
+ private FocusAwareStringComparator fNamesComparator;
+ public final Map<String,CPListElement> fModule2Element= new HashMap<>();
+ private List<String> fInitialNames= new ArrayList<>();
+ private TableViewer fViewer;
+ private Map<CPListElement,String> fElem2ModName= new HashMap<>();
+ private Map<CPListElement,IModuleDescription> fModules= new HashMap<>();
+ private Map<CPListElement,ModuleKind> fKinds= new HashMap<>();
+
+ public void createViewer(Composite left, PixelConverter converter) {
+ TableViewer tableViewer= new TableViewer(left, SWT.MULTI | SWT.BORDER);
+ tableViewer.setContentProvider(new ListContentProvider());
+ tableViewer.setLabelProvider(new ModulesLabelProvider(this::getModuleKind));
+ tableViewer.setInput(fNames);
+
+ GridData gd= new GridData(SWT.FILL, SWT.FILL, true, true);
+ gd.widthHint= converter.convertWidthInCharsToPixels(30);
+ gd.heightHint= converter.convertHeightInCharsToPixels(6);
+ tableViewer.getControl().setLayoutData(gd);
+ fViewer= tableViewer;
+ }
+
+ public void setSelectionChangedListener(BiConsumer<List<CPListElement>,IModuleDescription> listener) {
+ fViewer.addSelectionChangedListener(e -> listener.accept(getSelectedElements(), getSelectedModule()));
+ }
+
+ public void addModule(IModuleDescription module, CPListElement cpe, ModuleKind kind) {
+ String moduleName = module.getElementName();
+ fNames.add(moduleName);
+ fModule2Element.put(moduleName, cpe);
+ fElem2ModName.put(cpe, moduleName);
+ fKinds.put(cpe, kind);
+ switch (kind) {
+ case System:
+ break; // system modules are already stored inside the CPListElement
+ case Focus:
+ fNamesComparator= new FocusAwareStringComparator(moduleName);
+ //$FALL-THROUGH$
+ //$CASES-OMITTED$
+ default:
+ fModules.put(cpe, module);
+ break;
+ }
+ }
+
+ public void captureInitial() {
+ fInitialNames.clear();
+ fInitialNames.addAll(fNames);
+ }
+
+ public boolean isModified() {
+ return !fInitialNames.equals(fNames);
+ }
+
+ public void refresh() {
+ fNames.sort(fNamesComparator);
+ fViewer.refresh();
+ }
+
+ public ModuleKind getModuleKind(String name) {
+ CPListElement element= fModule2Element.get(name);
+ if (element != null) {
+ return fKinds.get(element);
+ }
+ return ModuleKind.Normal;
+ }
+
+ public ModuleKind getModuleKind(CPListElement element) {
+ return getModuleKind(fElem2ModName.get(element));
+ }
+
+ public List<CPListElement> getSelectedElements() {
+ List<CPListElement> selectedElements= new ArrayList<>();
+ for (Object selected : fViewer.getStructuredSelection().toList()) {
+ selectedElements.add(fModule2Element.get(selected));
+ }
+ return selectedElements;
+ }
+
+ private IModuleDescription getSelectedModule() {
+ List<CPListElement> selectedElems= getSelectedElements();
+ if (selectedElems.size() == 1) {
+ CPListElement selectedElem= selectedElems.get(0);
+ if (selectedElem.getModule() != null) {
+ return selectedElem.getModule(); // system module
+ }
+ return fModules.get(selectedElem);
+ }
+ return null;
+ }
+
+ public void setSelectionToModule(String moduleName) {
+ fViewer.setSelection(new StructuredSelection(moduleName), true);
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleDependenciesPage.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleDependenciesPage.java
new file mode 100644
index 0000000..d045188
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleDependenciesPage.java
@@ -0,0 +1,610 @@
+/*******************************************************************************
+ * 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.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import com.ibm.icu.text.MessageFormat;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Point;
+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.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+
+import org.eclipse.core.resources.IProject;
+
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.layout.PixelConverter;
+import org.eclipse.jface.resource.CompositeImageDescriptor;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.StructuredSelection;
+
+import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer;
+
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IModuleDescription;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.ui.viewsupport.JavaElementImageProvider;
+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.ModuleEncapsulationDetail.LimitModules;
+import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModulePatch;
+import org.eclipse.jdt.internal.ui.wizards.dialogfields.CheckedListDialogField;
+import org.eclipse.jdt.internal.ui.wizards.dialogfields.DialogField;
+import org.eclipse.jdt.internal.ui.wizards.dialogfields.LayoutUtil;
+import org.eclipse.jdt.internal.ui.wizards.dialogfields.ListDialogField;
+import org.eclipse.jdt.internal.ui.wizards.dialogfields.TreeListDialogField;
+
+/*
+ * TODO: ("+" must have, "-" later)
+ * LHS:
+ * - module kind Upgrade of a System Library (incl. icon decoration)
+ * - indicator for module having tweaks (to be shown in the LHS)
+ * - better help on how to remove non-JRE modules (module-info, modulepath)
+ * RHS:
+ * + consider one more toplevel synth node: "Patched with/from" (whatever word we select, here and for the button)
+ * + its children would be IPackageFragmentRoot, or (Till's suggestions): IJavaProject
+ * + validate that no root tries to patch different modules at the same time
+ * + handle patch project w/o module-info
+ * + treat the patched module as the current context module (pinned)
+ * + qualified exports / opens
+ * + show Declared > reads (for requires)
+ * + fix decoration in ModulesSelectionDialog (when used for add reads)
+ * General:
+ * - distinguish test/main dependencies
+ * + special elements: ALL-UNNAMED, ALL-SYSTEM ...
+ * + Select All button in Add system module dialog
+ * - Help page and reference to it
+ */
+public class ModuleDependenciesPage extends BuildPathBasePage {
+
+ /** Composed image descriptor consisting of a base image and optionally a decoration overlay. */
+ static class DecoratedImageDescriptor extends CompositeImageDescriptor {
+ private ImageDescriptor fBaseImage;
+ private ImageDescriptor fOverlay;
+ private boolean fDrawAtOffset;
+ public DecoratedImageDescriptor(ImageDescriptor baseImage, ImageDescriptor overlay, boolean drawAtOffset) {
+ fBaseImage= baseImage;
+ fOverlay= overlay;
+ fDrawAtOffset= drawAtOffset;
+ }
+ @Override
+ protected void drawCompositeImage(int width, int height) {
+ drawImage(createCachedImageDataProvider(fBaseImage), 0, 0);
+ if (fOverlay != null) {
+ CachedImageDataProvider provider= createCachedImageDataProvider(fOverlay);
+ if (fDrawAtOffset) {
+ drawImage(provider, getSize().x - provider.getWidth(), 0);
+ } else {
+ drawImage(provider, 0, 0);
+ }
+ }
+ }
+ @Override
+ protected Point getSize() {
+ return JavaElementImageProvider.BIG_SIZE;
+ }
+ @Override
+ public int hashCode() {
+ final int prime= 31;
+ int result= 1;
+ result= prime * result + ((fBaseImage == null) ? 0 : fBaseImage.hashCode());
+ result= prime * result + ((fOverlay == null) ? 0 : fOverlay.hashCode());
+ return result;
+ }
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DecoratedImageDescriptor other= (DecoratedImageDescriptor) obj;
+ if (fBaseImage == null) {
+ if (other.fBaseImage != null)
+ return false;
+ } else if (!fBaseImage.equals(other.fBaseImage))
+ return false;
+ if (fOverlay == null) {
+ if (other.fOverlay != null)
+ return false;
+ } else if (!fOverlay.equals(other.fOverlay))
+ return false;
+ return true;
+ }
+ }
+
+ private final ListDialogField<CPListElement> fClassPathList; // shared with other pages
+ private IJavaProject fCurrJProject;
+
+ // LHS list:
+ private ModuleDependenciesList fModuleList;
+
+ // RHS tree:
+ private final TreeListDialogField<Object> fDetailsList;
+
+ // bi-directional dependency graph:
+ private Map<String,List<String>> fModule2RequiredModules;
+ private Map<String,List<String>> fModuleRequiredByModules;
+
+ public final Map<String,String> fPatchMap= new HashMap<>();
+
+ private Control fSWTControl;
+ private final IWorkbenchPreferenceContainer fPageContainer; // for switching page (not yet used)
+
+ public ModuleDependenciesPage(CheckedListDialogField<CPListElement> classPathList, IWorkbenchPreferenceContainer pageContainer) {
+ fClassPathList= classPathList;
+ fPageContainer= pageContainer;
+ fSWTControl= null;
+
+ String[] buttonLabels= new String[] {
+ NewWizardMessages.ModuleDependenciesPage_modules_remove_button,
+ /* */ null,
+ NewWizardMessages.ModuleDependenciesPage_modules_read_button,
+ NewWizardMessages.ModuleDependenciesPage_modules_expose_package_button,
+ /* */ null,
+ NewWizardMessages.ModuleDependenciesPage_modules_patch_button,
+ /* */ null,
+ NewWizardMessages.ModuleDependenciesPage_modules_edit_button
+ };
+
+ fModuleList= new ModuleDependenciesList();
+
+ ModuleDependenciesAdapter adapter= new ModuleDependenciesAdapter(this);
+ fDetailsList= new TreeListDialogField<>(adapter, buttonLabels, new ModuleDependenciesAdapter.ModularityDetailsLabelProvider());
+ fDetailsList.setDialogFieldListener(adapter);
+ fDetailsList.setLabelText(NewWizardMessages.ModuleDependenciesPage_details_label);
+
+ adapter.setList(fDetailsList);
+
+ fDetailsList.setViewerComparator(new ModuleDependenciesAdapter.ElementSorter());
+ }
+
+ @Override
+ public Control getControl(Composite parent) {
+ PixelConverter converter= new PixelConverter(parent);
+
+ Composite composite= new Composite(parent, SWT.NONE);
+ composite.setFont(parent.getFont());
+ GridLayout layout= new GridLayout(2, false);
+ layout.marginBottom= 0;
+ composite.setLayout(layout);
+ GridData gd= new GridData(SWT.FILL, SWT.FILL, true, true);
+ gd.minimumWidth= 0;
+ composite.setLayoutData(gd);
+
+ // === left: ===
+ Composite left= new Composite(composite, SWT.NONE);
+ layout= new GridLayout(1, false);
+ layout.marginBottom= 0;
+ left.setLayout(layout);
+ gd= new GridData(SWT.FILL, SWT.FILL, true, true);
+ gd.minimumWidth= 0;
+ left.setLayoutData(gd);
+
+ Label title= new Label(left, SWT.NONE);
+ title.setText(NewWizardMessages.ModuleDependenciesPage_modules_label);
+
+ fModuleList.createViewer(left, converter);
+ fModuleList.setSelectionChangedListener((elems, mod) -> selectModule(elems, mod));
+
+ Button addButton= new Button(left, SWT.NONE);
+ addButton.setText(NewWizardMessages.ModuleDependenciesPage_addSystemModule_button);
+ addButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> addSystemModules()));
+
+ // === right: ===
+ Composite right= new Composite(composite, SWT.NONE);
+ layout= new GridLayout(2, false);
+ layout.marginBottom= 0;
+ right.setLayout(layout);
+ gd= new GridData(SWT.FILL, SWT.FILL, true, true);
+ gd.minimumWidth= 0;
+ right.setLayoutData(gd);
+
+ LayoutUtil.doDefaultLayout(right, new DialogField[] { fDetailsList }, true, SWT.DEFAULT, SWT.DEFAULT);
+ LayoutUtil.setHorizontalGrabbing(fDetailsList.getTreeControl(null));
+
+ int buttonBarWidth= converter.convertWidthInCharsToPixels(24);
+ fDetailsList.setButtonsMinWidth(buttonBarWidth);
+
+ fDetailsList.setViewerComparator(new CPListElementSorter());
+
+ fSWTControl= composite;
+
+ return composite;
+ }
+
+ public Shell getShell() {
+ if (fSWTControl != null) {
+ return fSWTControl.getShell();
+ }
+ return JavaPlugin.getActiveWorkbenchShell();
+ }
+
+ @Override
+ public void init(IJavaProject jproject) {
+ fCurrJProject= jproject;
+ if (Display.getCurrent() != null) {
+ scanModules();
+ } else {
+ Display.getDefault().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ scanModules();
+ }
+ });
+ }
+ }
+
+ protected void scanModules() {
+ fModule2RequiredModules= new HashMap<>();
+ fModuleRequiredByModules= new HashMap<>();
+ Set<String> recordedModules = new HashSet<>();
+
+ List<CPListElement> cpelements= fClassPathList.getElements();
+
+ for (CPListElement cpe : cpelements) {
+ Object value= cpe.getAttribute(CPListElement.MODULE);
+ if (value instanceof ModuleEncapsulationDetail[]) {
+ for (ModuleEncapsulationDetail detail : (ModuleEncapsulationDetail[]) value) {
+ if (detail instanceof ModulePatch) {
+ ModulePatch patch= (ModulePatch) detail;
+ fPatchMap.put(patch.fPaths, patch.fModule);
+ }
+ }
+ }
+
+ switch (cpe.getEntryKind()) {
+ case IClasspathEntry.CPE_SOURCE:
+ IPackageFragmentRoot[] fragmentRoots= fCurrJProject.findPackageFragmentRoots(cpe.getClasspathEntry());
+ if (fragmentRoots != null && fragmentRoots.length == 1) {
+ for (IPackageFragmentRoot fragmentRoot : fragmentRoots) {
+ IModuleDescription module= fragmentRoot.getModuleDescription();
+ if (module != null) {
+ recordModule(module, recordedModules, cpe, ModuleKind.Focus);
+ break;
+ }
+ }
+ }
+ break;
+ case IClasspathEntry.CPE_PROJECT:
+ IProject project= fCurrJProject.getProject().getWorkspace().getRoot().getProject(cpe.getClasspathEntry().getPath().toString());
+ try {
+ IJavaProject jProject= JavaCore.create(project);
+ IModuleDescription module= jProject.getModuleDescription();
+ ModuleKind kind= ModuleKind.Normal;
+ if (module == null) {
+ module= JavaCore.getAutomaticModuleDescription(jProject);
+ kind= ModuleKind.Automatic;
+ }
+ if (module != null) {
+ recordModule(module, recordedModules, cpe, kind);
+ }
+ } catch (JavaModelException e) {
+ // ignore
+ }
+ break;
+ case IClasspathEntry.CPE_CONTAINER:
+ ModuleKind kind= LibrariesWorkbookPage.isJREContainer(cpe.getPath()) ? ModuleKind.System : ModuleKind.Normal;
+ for (Object object : cpe.getChildren(true)) {
+ if (object instanceof CPListElement) {
+ CPListElement childElement= (CPListElement) object;
+ IModuleDescription childModule= childElement.getModule();
+ if (childModule != null) {
+ fModuleList.addModule(childModule, childElement, kind);
+ }
+ }
+ }
+ if (kind == ModuleKind.System) {
+ // additionally capture dependency information about all system module disregarding --limit-modules
+ for (IPackageFragmentRoot packageRoot : fCurrJProject.findUnfilteredPackageFragmentRoots(cpe.getClasspathEntry())) {
+ IModuleDescription module= packageRoot.getModuleDescription();
+ if (module != null) {
+ recordModule(module, recordedModules, null/*don't add to fModuleList*/, kind);
+ }
+ }
+ }
+ break;
+ default: // LIBRARY & VARIABLE:
+ for (IPackageFragmentRoot packageRoot : fCurrJProject.findPackageFragmentRoots(cpe.getClasspathEntry())) {
+ IModuleDescription module= packageRoot.getModuleDescription();
+ kind= ModuleKind.Normal;
+ if (module == null) {
+ try {
+ module= JavaCore.getAutomaticModuleDescription(packageRoot);
+ kind= ModuleKind.Automatic;
+ } catch (JavaModelException | IllegalArgumentException e) {
+ // ignore
+ }
+ }
+ if (module != null) {
+ recordModule(module, recordedModules, cpe, kind);
+ break;
+ }
+ }
+ }
+ }
+ fModuleList.captureInitial();
+ fModuleList.refresh();
+ }
+
+ private void recordModule(IModuleDescription module, Set<String> moduleNames, CPListElement cpe, ModuleKind kind) {
+ if (cpe != null) {
+ fModuleList.addModule(module, cpe, kind);
+ }
+ String moduleName= module.getElementName();
+ if (moduleNames.add(moduleName)) {
+ try {
+ for (String required : module.getRequiredModuleNames()) {
+ List<String> otherModules= fModule2RequiredModules.get(moduleName);
+ if (otherModules == null) {
+ otherModules= new ArrayList<>();
+ fModule2RequiredModules.put(moduleName, otherModules);
+ }
+ otherModules.add(required);
+
+ otherModules= fModuleRequiredByModules.get(required);
+ if (otherModules == null) {
+ otherModules= new ArrayList<>();
+ fModuleRequiredByModules.put(required, otherModules);
+ }
+ otherModules.add(moduleName);
+ }
+ } catch (JavaModelException e) {
+ JavaPlugin.log(e);
+ }
+ }
+ }
+
+
+ @Override
+ public List<?> getSelection() {
+ return fDetailsList.getSelectedElements();
+ }
+
+ @Override
+ public void setSelection(List<?> selElements, boolean expand) {
+ fDetailsList.selectElements(new StructuredSelection(selElements));
+ if (expand) {
+ for (int i= 0; i < selElements.size(); i++) {
+ fDetailsList.expandElement(selElements.get(i), 1);
+ }
+ }
+ }
+
+ public void setSelectionToModule(String moduleName) {
+ int idx= fModuleList.fNames.indexOf(moduleName);
+ if (idx != -1) {
+ fModuleList.setSelectionToModule(moduleName);
+ }
+ }
+
+ private void selectModule(List<CPListElement> elements, IModuleDescription module) {
+ fDetailsList.removeAllElements();
+ if (elements.size() == 1) {
+ CPListElement element= elements.get(0);
+ fDetailsList.addElement(element);
+ fDetailsList.addElement(new ModuleDependenciesAdapter.DeclaredDetails(module, element));
+ ModuleKind moduleKind= fModuleList.getModuleKind(element);
+ if (moduleKind != ModuleKind.Focus) {
+ ModuleDependenciesAdapter.ConfiguredDetails configured= new ModuleDependenciesAdapter.ConfiguredDetails(module, element, moduleKind);
+ fDetailsList.addElement(configured);
+ fDetailsList.expandElement(configured, 1);
+ }
+ ModuleDependenciesAdapter.enableDefaultButtons(fDetailsList, true, !elements.isEmpty());
+ } else {
+ ModuleDependenciesAdapter.enableDefaultButtons(fDetailsList, false, !elements.isEmpty());
+ }
+ }
+
+ @Override
+ public boolean isEntryKind(int kind) {
+ return true;
+ }
+
+ @Override
+ public void setFocus() {
+ fDetailsList.setFocus();
+ }
+
+ void addSystemModules() {
+ CPListElement cpListElement= findSystemLibraryElement();
+ ModuleSelectionDialog dialog= new ModuleSelectionDialog(getShell(), fCurrJProject, cpListElement.getClasspathEntry(), fModuleList.fNames, this::computeForwardClosure);
+ if (dialog.open() == IDialogConstants.OK_ID) {
+ for (IModuleDescription addedModule : dialog.getResult()) {
+ fModuleList.addModule(addedModule, getOrCreateModuleCPE(cpListElement, addedModule), ModuleKind.System);
+ }
+ updateLimitModules(cpListElement.findAttributeElement(CPListElement.MODULE));
+ fModuleList.refresh();
+ }
+ }
+
+ CPListElement getOrCreateModuleCPE(CPListElement parentCPE, IModuleDescription module) {
+ CPListElement element= fModuleList.fModule2Element.get(module.getElementName());
+ if (element != null) {
+ return element;
+ }
+ try {
+ IClasspathEntry entry= fCurrJProject.getClasspathEntryFor(module.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT).getPath());
+ return CPListElement.create(parentCPE, entry, module, true, fCurrJProject);
+ } catch (JavaModelException e) {
+ JavaPlugin.log(e.getStatus());
+ return null;
+ }
+ }
+ private CPListElement findSystemLibraryElement() {
+ for (CPListElement cpListElement : fClassPathList.getElements()) {
+ if (LibrariesWorkbookPage.isJREContainer(cpListElement.getPath()))
+ return cpListElement;
+ }
+ return null;
+ }
+
+ void removeModules() {
+ List<CPListElement> selectedElements= fModuleList.getSelectedElements();
+ List<String> selectedModuleNames= new ArrayList<>();
+ Set<String> allModulesToRemove = new HashSet<>();
+ for (CPListElement selectedElement : selectedElements) {
+ if (fModuleList.getModuleKind(selectedElement) == ModuleKind.Focus) {
+ MessageDialog.openError(getShell(), NewWizardMessages.ModuleDependenciesPage_removeModule_dialog_title,
+ NewWizardMessages.ModuleDependenciesPage_removeCurrentModule_error);
+ return;
+ }
+ IModuleDescription mod = selectedElement.getModule();
+ if (mod == null) {
+ MessageDialog.openError(getShell(), NewWizardMessages.ModuleDependenciesPage_removeModule_dialog_title,
+ MessageFormat.format(NewWizardMessages.ModuleDependenciesPage_removeModule_error_with_hint,
+ selectedElement.getPath().lastSegment(), NewWizardMessages.ModuleDependenciesPage_removeSystemModule_error_hint));
+ // TODO: offer to switch to the corresponding tab?
+ return;
+ }
+ String moduleName= mod.getElementName();
+ if (moduleName.equals("java.base")) { //$NON-NLS-1$
+ MessageDialog.openError(getShell(), NewWizardMessages.ModuleDependenciesPage_removeModule_dialog_title,
+ MessageFormat.format(NewWizardMessages.ModuleDependenciesPage_removeModule_error_with_hint, moduleName, "")); //$NON-NLS-1$
+ return;
+ }
+ selectedModuleNames.add(moduleName);
+ collectModulesToRemove(moduleName, allModulesToRemove);
+ }
+ String seedModules= String.join(", ", selectedModuleNames); //$NON-NLS-1$
+ if (allModulesToRemove.size() == selectedModuleNames.size()) {
+ if (confirmRemoveModule(MessageFormat.format(NewWizardMessages.ModuleDependenciesPage_removingModule_message, seedModules))) {
+ fModuleList.fNames.removeAll(selectedModuleNames);
+ fModuleList.refresh();
+ }
+ } else {
+ StringBuilder message = new StringBuilder(
+ MessageFormat.format(NewWizardMessages.ModuleDependenciesPage_removingModuleTransitive_message, seedModules));
+ // append sorted list minus the selected module:
+ message.append(allModulesToRemove.stream()
+ .filter(m -> !seedModules.contains(m))
+ .sorted()
+ .collect(Collectors.joining("\n\t", "\t", ""))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ if (!confirmRemoveModule(message.toString()))
+ return;
+ fModuleList.fNames.removeAll(allModulesToRemove);
+ fModuleList.refresh();
+ }
+ for (CPListElement elem: selectedElements) {
+ Object container= elem.getParentContainer();
+ if (container instanceof CPListElement) {
+ CPListElement containerElement= (CPListElement) container;
+ if (LibrariesWorkbookPage.isJREContainer(containerElement.getPath())) {
+ CPListElementAttribute attribute= containerElement.findAttributeElement(CPListElement.MODULE);
+ updateLimitModules(attribute);
+ break;
+ }
+ }
+ }
+ }
+
+ private Set<String> computeForwardClosure(List<String> seeds) {
+ Set<String> closure= new HashSet<>();
+ collectForwardClosure(seeds, closure);
+ return closure;
+ }
+ private void collectForwardClosure(List<String> seeds, Set<String> closure) {
+ for (String seed : seeds) {
+ if (closure.add(seed) && !fModuleList.fNames.contains(seed)) {
+ List<String> deps= fModule2RequiredModules.get(seed);
+ if (deps != null) {
+ collectForwardClosure(deps, closure);
+ }
+ }
+ }
+ }
+
+ private void collectModulesToRemove(String mod, Set<String> modulesToRemove) {
+ if (fModuleList.fNames.contains(mod) && modulesToRemove.add(mod)) {
+ List<String> requireds= fModuleRequiredByModules.get(mod);
+ if (requireds != null) {
+ for (String required : requireds) {
+ collectModulesToRemove(required, modulesToRemove);
+ }
+ }
+ }
+ }
+
+ private boolean confirmRemoveModule(String message) {
+ int answer= MessageDialog.open(MessageDialog.QUESTION, getShell(), NewWizardMessages.ModuleDependenciesPage_removeModule_dialog_title, message, SWT.NONE, NewWizardMessages.ModuleDependenciesPage_remove_button, NewWizardMessages.ModuleDependenciesPage_cancel_button);
+ return answer == 0;
+ }
+
+ private void updateLimitModules(CPListElementAttribute moduleAttribute) {
+ LimitModules limitModules= new ModuleEncapsulationDetail.LimitModules(reduceNames(fModuleList.fNames), moduleAttribute);
+ Object value= moduleAttribute.getValue();
+ if (value instanceof ModuleEncapsulationDetail[]) {
+ ModuleEncapsulationDetail[] details= (ModuleEncapsulationDetail[]) value;
+ for (int i= 0; i < details.length; i++) {
+ if (details[i] instanceof LimitModules) {
+ // replace existing --limit-modules
+ details[i]= limitModules;
+ moduleAttribute.setValue(details);
+ return;
+ }
+ }
+ if (details.length > 0) {
+ // append to existing list of other details:
+ ModuleEncapsulationDetail[] newDetails= Arrays.copyOf(details, details.length+1);
+ newDetails[newDetails.length-1]= limitModules;
+ moduleAttribute.setValue(newDetails);
+ return;
+ }
+ }
+ // set as singleton detail:
+ moduleAttribute.setValue(new ModuleEncapsulationDetail[] { limitModules });
+ }
+
+ List<String> reduceNames(List<String> names) {
+ List<String> reduced= new ArrayList<>();
+ outer:
+ for (String name : names) {
+ if (fModuleList.getModuleKind(name) == ModuleKind.System) {
+ List<String> dominators= fModuleRequiredByModules.get(name);
+ if (dominators != null) {
+ for (String dominator : dominators) {
+ if (fModuleList.fNames.contains(dominator)) {
+ continue outer;
+ }
+ }
+ }
+ reduced.add(name);
+ }
+ }
+ return reduced;
+ }
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleDialog.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleDialog.java
index 130d440..9a4500f 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleDialog.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleDialog.java
@@ -85,6 +85,7 @@
import org.eclipse.jdt.internal.ui.wizards.NewWizardMessages;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.LimitModules;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModuleAddExport;
+import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModuleAddExpose;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModuleAddReads;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.ModuleEncapsulationDetail.ModulePatch;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.DialogField;
@@ -184,7 +185,7 @@
private final SelectionButtonDialogField fIsPatchCheckbox;
private final StringDialogField fPatchedModule;
- private final ListDialogField<ModuleAddExport> fAddExportsList;
+ private final ListDialogField<ModuleAddExpose> fAddExportsList;
private final ListDialogField<ModuleAddReads> fAddReadsList;
@@ -225,7 +226,7 @@
fPatchedModule.setLabelText(NewWizardMessages.ModuleDialog_patched_module_label);
fPatchedModule.setDialogFieldListener(field -> validateDetails(field));
- fAddExportsList= createDetailListContents(entryToEdit, NewWizardMessages.ModuleDialog_exports_label, new AddExportsAdapter(), ModuleAddExport.class);
+ fAddExportsList= createDetailListContents(entryToEdit, NewWizardMessages.ModuleDialog_exports_label, new AddExportsAdapter(), ModuleAddExpose.class);
fAddReadsList= createDetailListContents(entryToEdit, NewWizardMessages.ModuleDialog_reads_label, new AddReadsAdapter(), ModuleAddReads.class);
initializeValues();
@@ -635,7 +636,7 @@
}
}
if (status.isOK()) {
- for (ModuleAddExport export : fAddExportsList.getElements()) {
+ for (ModuleAddExpose export : fAddExportsList.getElements()) {
if (!packages.add(export.fPackage)) {
status.setError(Messages.format(NewWizardMessages.ModuleDialog_duplicatePackage_error, export.fPackage));
break;
@@ -917,24 +918,24 @@
abstract void editEntry(ListDialogField<T> field, T detail);
}
- private class AddExportsAdapter extends ListAdapter<ModuleAddExport> {
+ private class AddExportsAdapter extends ListAdapter<ModuleAddExpose> {
@Override
- void addEntry(ListDialogField<ModuleAddExport> field) {
+ void addEntry(ListDialogField<ModuleAddExpose> field) {
ModuleAddExport initialValue= new ModuleAddExport(getSourceModuleName(), NO_NAME, getCurrentModuleName(), null);
ModuleAddExportsDialog dialog= new ModuleAddExportsDialog(getShell(), fJavaElements, initialValue);
if (dialog.open() == Window.OK) {
- ModuleAddExport export= dialog.getExport(fCurrCPElement.findAttributeElement(CPListElement.MODULE));
+ ModuleAddExpose export= dialog.getExport(fCurrCPElement.findAttributeElement(CPListElement.MODULE));
if (export != null)
field.addElement(export);
}
}
@Override
- void editEntry(ListDialogField<ModuleAddExport> field, ModuleAddExport export) {
+ void editEntry(ListDialogField<ModuleAddExpose> field, ModuleAddExpose export) {
ModuleAddExportsDialog dialog= new ModuleAddExportsDialog(getShell(), fJavaElements, export);
if (dialog.open() == Window.OK) {
- ModuleAddExport newExport= dialog.getExport(fCurrCPElement.findAttributeElement(CPListElement.MODULE));
+ ModuleAddExpose newExport= dialog.getExport(fCurrCPElement.findAttributeElement(CPListElement.MODULE));
if (newExport != null) {
field.replaceElement(export, newExport);
} else {
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleEncapsulationDetail.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleEncapsulationDetail.java
index de89fee..73b6131 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleEncapsulationDetail.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleEncapsulationDetail.java
@@ -103,39 +103,96 @@
}
public final String fModule;
+ public final String fPaths;
- public ModulePatch(String module, CPListElementAttribute attribElem) {
- fModule= module;
+ public ModulePatch(String value, CPListElementAttribute attribElem) {
+ int eqIdx= value.indexOf('=');
+ if (eqIdx == -1) {
+ fModule= value;
+ fPaths= null; // FIXME: find path to encl. project (src folder??)
+ } else {
+ fModule= value.substring(0, eqIdx);
+ fPaths= value.substring(eqIdx + 1);
+ }
+ fAttribElem= attribElem;
+ }
+
+ public ModulePatch(String moduleName, String path, CPListElementAttribute attribElem) {
+ fModule= moduleName;
+ fPaths= path;
fAttribElem= attribElem;
}
@Override
+ public boolean affects(String module) {
+ return module.equals(fModule);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime= 31;
+ int result= 1;
+ result= prime * result + ((fModule == null) ? 0 : fModule.hashCode());
+ result= prime * result + ((fPaths == null) ? 0 : fPaths.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ModulePatch other= (ModulePatch) obj;
+ if (fModule == null) {
+ if (other.fModule != null)
+ return false;
+ } else if (!fModule.equals(other.fModule))
+ return false;
+ if (fPaths == null) {
+ if (other.fPaths != null)
+ return false;
+ } else if (!fPaths.equals(other.fPaths))
+ return false;
+ return true;
+ }
+
+ @Override
public String toString() {
+ if (fPaths != null) {
+ return fModule + '=' + fPaths;
+ }
return fModule;
}
}
- /**
- * Node in the tree of CPListElement et al, representing an add-exports module directive.
- */
- static class ModuleAddExport extends ModuleEncapsulationDetail {
+ /** Shared implementation for ModuleAddExports & ModuleAddOpens (same structure). */
+ abstract static class ModuleAddExpose extends ModuleEncapsulationDetail {
- public static ModuleAddExport fromString(CPListElementAttribute attribElem, String value) {
+ public static ModuleAddExpose fromString(CPListElementAttribute attribElem, String value, boolean isExports) {
int slash= value.indexOf('/');
int equals= value.indexOf('=');
if (slash != -1 && equals != -1 && equals > slash) {
- return new ModuleAddExport(value.substring(0, slash),
+ if (isExports)
+ return new ModuleAddExport(value.substring(0, slash),
value.substring(slash+1, equals),
value.substring(equals+1),
attribElem);
+ else
+ return new ModuleAddOpens(value.substring(0, slash),
+ value.substring(slash+1, equals),
+ value.substring(equals+1),
+ attribElem);
}
return null;
}
- public static Collection<ModuleAddExport> fromMultiString(CPListElementAttribute attribElem, String values) {
- List<ModuleAddExport> exports= new ArrayList<>();
+ public static Collection<ModuleAddExpose> fromMultiString(CPListElementAttribute attribElem, String values, boolean isExports) {
+ List<ModuleAddExpose> exports= new ArrayList<>();
for (String value : values.split(":")) { //$NON-NLS-1$
- ModuleAddExport export= fromString(attribElem, value);
+ ModuleAddExpose export= fromString(attribElem, value, isExports);
if (export != null)
exports.add(export);
}
@@ -146,7 +203,7 @@
public final String fPackage;
public final String fTargetModules;
- public ModuleAddExport(String sourceModule, String aPackage, String targetModules, CPListElementAttribute attribElem) {
+ public ModuleAddExpose(String sourceModule, String aPackage, String targetModules, CPListElementAttribute attribElem) {
fSourceModule= sourceModule;
fPackage= aPackage;
fTargetModules= targetModules;
@@ -154,12 +211,72 @@
}
@Override
+ public boolean affects(String module) {
+ return module.equals(fSourceModule);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime= 31;
+ int result= 1;
+ result= prime * result + ((fPackage == null) ? 0 : fPackage.hashCode());
+ result= prime * result + ((fSourceModule == null) ? 0 : fSourceModule.hashCode());
+ result= prime * result + ((fTargetModules == null) ? 0 : fTargetModules.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ModuleAddExpose other= (ModuleAddExpose) obj;
+ if (fPackage == null) {
+ if (other.fPackage != null)
+ return false;
+ } else if (!fPackage.equals(other.fPackage))
+ return false;
+ if (fSourceModule == null) {
+ if (other.fSourceModule != null)
+ return false;
+ } else if (!fSourceModule.equals(other.fSourceModule))
+ return false;
+ if (fTargetModules == null) {
+ if (other.fTargetModules != null)
+ return false;
+ } else if (!fTargetModules.equals(other.fTargetModules))
+ return false;
+ return true;
+ }
+
+ @Override
public String toString() {
return fSourceModule+'/'+fPackage+'='+fTargetModules;
}
}
/**
+ * Node in the tree of CPListElement et al, representing an add-exports module directive.
+ */
+ static class ModuleAddExport extends ModuleAddExpose {
+ public ModuleAddExport(String sourceModule, String aPackage, String targetModules, CPListElementAttribute attribElem) {
+ super(sourceModule, aPackage, targetModules, attribElem);
+ }
+ }
+
+ /**
+ * Node in the tree of CPListElement et al, representing an add-opens module directive.
+ */
+ static class ModuleAddOpens extends ModuleAddExpose {
+ public ModuleAddOpens(String sourceModule, String aPackage, String targetModules, CPListElementAttribute attribElem) {
+ super(sourceModule, aPackage, targetModules, attribElem);
+ }
+ }
+
+ /**
* Node in the tree of CPListElement et al, representing an add-reads module directive.
*/
static class ModuleAddReads extends ModuleEncapsulationDetail {
@@ -194,6 +311,42 @@
}
@Override
+ public boolean affects(String module) {
+ return module.equals(fSourceModule);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime= 31;
+ int result= 1;
+ result= prime * result + ((fSourceModule == null) ? 0 : fSourceModule.hashCode());
+ result= prime * result + ((fTargetModule == null) ? 0 : fTargetModule.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ModuleAddReads other= (ModuleAddReads) obj;
+ if (fSourceModule == null) {
+ if (other.fSourceModule != null)
+ return false;
+ } else if (!fSourceModule.equals(other.fSourceModule))
+ return false;
+ if (fTargetModule == null) {
+ if (other.fTargetModule != null)
+ return false;
+ } else if (!fTargetModule.equals(other.fTargetModule))
+ return false;
+ return true;
+ }
+
+ @Override
public String toString() {
return fSourceModule+'='+fTargetModule;
}
@@ -219,8 +372,14 @@
fAttribElem= attribElem;
}
@Override
+ public boolean affects(String module) {
+ return false; // no change on the module, just on the module graph / set of root modules
+ }
+ @Override
public String toString() {
return String.join(",", fExplicitlyIncludedModules); //$NON-NLS-1$
}
}
+
+ public abstract boolean affects(String module);
}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModulePatchSourceSelectionDialog.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModulePatchSourceSelectionDialog.java
new file mode 100644
index 0000000..13a23ad
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModulePatchSourceSelectionDialog.java
@@ -0,0 +1,206 @@
+/*******************************************************************************
+ * 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.Iterator;
+import java.util.List;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+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.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
+
+import org.eclipse.jface.dialogs.TrayDialog;
+import org.eclipse.jface.layout.PixelConverter;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerComparator;
+
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IModuleDescription;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+
+import org.eclipse.jdt.ui.JavaElementLabelProvider;
+
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.ui.wizards.NewWizardMessages;
+import org.eclipse.jdt.internal.ui.wizards.dialogfields.TreeListDialogField;
+
+public class ModulePatchSourceSelectionDialog extends TrayDialog {
+
+ /**
+ * Selective tree showing only java projects and their source folders.
+ */
+ class SourcesContentProvider implements ITreeContentProvider {
+ @Override
+ public Object[] getElements(Object inputElement) {
+ if (inputElement instanceof List<?>)
+ return ((List<?>) inputElement).toArray();
+ return null;
+ }
+ @Override
+ public Object[] getChildren(Object parentElement) {
+ if (parentElement instanceof IJavaProject) {
+ List<IPackageFragmentRoot> sourceRoots= new ArrayList<>();
+ IJavaProject parentProject= (IJavaProject) parentElement;
+ collectSourceFolders(sourceRoots, parentProject);
+ return sourceRoots.toArray();
+ }
+ return null;
+ }
+ @Override
+ public Object getParent(Object element) {
+ if (element instanceof IPackageFragmentRoot) {
+ return ((IPackageFragmentRoot) element).getJavaProject();
+ }
+ return null;
+ }
+ @Override
+ public boolean hasChildren(Object element) {
+ if (element instanceof IJavaProject) {
+ return getChildren(element) != null;
+ }
+ return false;
+ }
+ }
+
+ class ContextProjectFirstComparator extends ViewerComparator {
+ @Override
+ public int compare(Viewer viewer, Object e1, Object e2) {
+ if (e1.equals(fContextProject)) {
+ return -1;
+ }
+ if (e1.equals(fContextProject)) {
+ return 1;
+ }
+ return super.compare(viewer, e1, e2);
+ }
+ }
+
+ private IModuleDescription fFocusModule;
+ private List<IJavaProject> fProjects;
+ private IJavaProject fContextProject;
+
+ private TreeListDialogField<IJavaProject> fTreeField;
+ private TreeViewer fViewer;
+ private List<IPackageFragmentRoot> fResult;
+
+ protected ModulePatchSourceSelectionDialog(Shell shell, IModuleDescription focusModule, IJavaProject contextProject) {
+ super(shell);
+ fFocusModule= focusModule;
+ fContextProject= contextProject;
+ findProjects();
+ }
+
+ private void findProjects() {
+ fProjects= new ArrayList<>();
+ IProject toSkip= null;
+ IPackageFragmentRoot focusRoot= (IPackageFragmentRoot) fFocusModule.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
+ try {
+ if (focusRoot != null) {
+ if (focusRoot.getKind() == IPackageFragmentRoot.K_SOURCE) {
+ toSkip= focusRoot.getJavaProject().getProject();
+ }
+ } else if (fFocusModule.isAutoModule()) {
+ if (fFocusModule.getJavaProject() != null) {
+ toSkip= fFocusModule.getJavaProject().getProject();
+ }
+ }
+ } catch (JavaModelException e1) {
+ JavaPlugin.log(e1.getStatus());
+ }
+ for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
+ if (toSkip != null && toSkip.equals(project)) {
+ continue;
+ }
+ try {
+ if (project.hasNature(JavaCore.NATURE_ID)) {
+ IJavaProject jProj= JavaCore.create(project);
+ fProjects.add(jProj);
+ }
+ } catch (CoreException e) {
+ JavaPlugin.log(e.getStatus());
+ }
+ }
+ }
+
+ static void collectSourceFolders(List<IPackageFragmentRoot> sourceRoots, IJavaProject project) {
+ try {
+ for (IClasspathEntry entry : project.getRawClasspath()) {
+ if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
+ for (IPackageFragmentRoot packageFragmentRoot : project.findPackageFragmentRoots(entry)) {
+ sourceRoots.add(packageFragmentRoot);
+ }
+ }
+ }
+ } catch (JavaModelException e) {
+ JavaPlugin.log(e.getStatus());
+ }
+ }
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ Composite composite= (Composite) super.createDialogArea(parent);
+ Label message= new Label(composite, SWT.NONE);
+ message.setText(NewWizardMessages.ModuleSelectionDialog_addSystemModules_message);
+
+ TreeViewer treeViewer= new TreeViewer(composite, SWT.MULTI | SWT.BORDER);
+ treeViewer.setContentProvider(new SourcesContentProvider());
+ treeViewer.setLabelProvider(new JavaElementLabelProvider());
+ treeViewer.setComparator(new ContextProjectFirstComparator());
+ treeViewer.addSelectionChangedListener(this::selectionChanged);
+ treeViewer.setInput(fProjects);
+
+ 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);
+ treeViewer.getControl().setLayoutData(gd);
+
+ fViewer= treeViewer;
+ return composite;
+ }
+
+ void selectionChanged(SelectionChangedEvent event) {
+ IStructuredSelection selection= event.getStructuredSelection();
+ fResult= new ArrayList<>();
+ for (Iterator<Object> iterator= selection.iterator(); iterator.hasNext(); ) {
+ Object object= iterator.next();
+ if (object instanceof IPackageFragmentRoot) {
+ fResult.add((IPackageFragmentRoot) object);
+ } else if (object instanceof IJavaProject) {
+ collectSourceFolders(fResult, (IJavaProject) object);
+ }
+ }
+ }
+
+ public List<IPackageFragmentRoot> getResult() {
+ return fResult;
+ }
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleSelectionDialog.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleSelectionDialog.java
new file mode 100644
index 0000000..ac00b69
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleSelectionDialog.java
@@ -0,0 +1,238 @@
+/*******************************************************************************
+ * 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());
+ }
+}