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/auto_mod_ovr@2x.png b/org.eclipse.jdt.ui/icons/full/ovr16/auto_mod_ovr@2x.png
new file mode 100644
index 0000000..f553792
--- /dev/null
+++ b/org.eclipse.jdt.ui/icons/full/ovr16/auto_mod_ovr@2x.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/exports_pkg_ovr@2x.png b/org.eclipse.jdt.ui/icons/full/ovr16/exports_pkg_ovr@2x.png
new file mode 100644
index 0000000..833b292
--- /dev/null
+++ b/org.eclipse.jdt.ui/icons/full/ovr16/exports_pkg_ovr@2x.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/opens_pkg_ovr@2x.png b/org.eclipse.jdt.ui/icons/full/ovr16/opens_pkg_ovr@2x.png
new file mode 100644
index 0000000..534ab36
--- /dev/null
+++ b/org.eclipse.jdt.ui/icons/full/ovr16/opens_pkg_ovr@2x.png
Binary files differ
diff --git a/org.eclipse.jdt.ui/icons/full/ovr16/patch_ovr.png b/org.eclipse.jdt.ui/icons/full/ovr16/patch_ovr.png
new file mode 100644
index 0000000..7f83c13
--- /dev/null
+++ b/org.eclipse.jdt.ui/icons/full/ovr16/patch_ovr.png
Binary files differ
diff --git a/org.eclipse.jdt.ui/icons/full/ovr16/patch_ovr@2x.png b/org.eclipse.jdt.ui/icons/full/ovr16/patch_ovr@2x.png
new file mode 100644
index 0000000..2250137
--- /dev/null
+++ b/org.eclipse.jdt.ui/icons/full/ovr16/patch_ovr@2x.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/reads_mod_ovr@2x.png b/org.eclipse.jdt.ui/icons/full/ovr16/reads_mod_ovr@2x.png
new file mode 100644
index 0000000..65067b2
--- /dev/null
+++ b/org.eclipse.jdt.ui/icons/full/ovr16/reads_mod_ovr@2x.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/icons/full/ovr16/system_mod_ovr@2x.png b/org.eclipse.jdt.ui/icons/full/ovr16/system_mod_ovr@2x.png
new file mode 100644
index 0000000..8989e9f
--- /dev/null
+++ b/org.eclipse.jdt.ui/icons/full/ovr16/system_mod_ovr@2x.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..59df294 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,12 @@
 	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$
+	public static final ImageDescriptor DESC_OVR_PATCH= createUnManagedCached(T_OVR, "patch_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..db0dc39 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,47 @@
 	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 ModuleSelectionDialog_selectModule_title;
+	public static String ModuleSelectionDialog_selectReadModule_message;
+	public static String ModuleSelectionDialog_searchModules_job;
+	public static String ModuleSelectionDialog_searchModules_temp_message;
+	public static String ModuleSelectionDialog_selectAll_button;
+
+	public static String ModulePatchSourceSelectionDialog_patchSourceLocation_message;
+	public static String ModulePatchSourceSelectionDialog_patchSourceLocation_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_nonModularProject_dummy;
+	public static String ModuleDependenciesPage_removeCurrentModule_error;
+	public static String ModuleDependenciesPage_removeModule_error_with_hint;
+	public static String ModuleDependenciesPage_removeSystemModule_error_hint;
+	public static String ModuleDependenciesAdapter_patchConflict_title;
+	public static String ModuleDependenciesAdapter_patchConflict_message;
+	public static String ModuleDependenciesAdapter_project_kind;
+	public static String ModuleDependenciesAdapter_sourceFolder_kind;
+	public static String ModuleDependenciesAdapter_configure_error;
+	// detail tree:
+	public static String ModuleDependenciesAdapter_declared_node;
+	public static String ModuleDependenciesAdapter_configured_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..742ff3a 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,48 @@
 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 with...
+ModuleDependenciesPage_modules_edit_button=&Edit...
+# dialogs:
+ModuleSelectionDialog_add_button=&Add
+ModuleSelectionDialog_addSystemModules_message=Select modules to be added to the module path.\n\
+Transitively required modules will be automatically selected.
+ModuleSelectionDialog_addSystemModules_title=Select system modules to add
+ModuleSelectionDialog_selectModule_title=Select module
+ModuleSelectionDialog_selectReadModule_message=Select module to read
+ModuleSelectionDialog_searchModules_job=Searching modules in workspace
+ModuleSelectionDialog_searchModules_temp_message=Searching modules in workspace...
+ModuleSelectionDialog_selectAll_button=Select all
+
+ModulePatchSourceSelectionDialog_patchSourceLocation_message=Select source locations (projects or source folders) that should be associated with the selected module ''{0}''
+ModulePatchSourceSelectionDialog_patchSourceLocation_title=Select source locations for patching
+
+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_nonModularProject_dummy=Non-modular Project
+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).
+ModuleDependenciesAdapter_patchConflict_message=The {0} {1} was declared to patch module ''{2}''.\n\
+Do you want to remove this and associate {3} with module ''{4}'' instead?
+ModuleDependenciesAdapter_patchConflict_title=Patch module conflict
+ModuleDependenciesAdapter_project_kind=project
+ModuleDependenciesAdapter_sourceFolder_kind=source folder
+ModuleDependenciesAdapter_configure_error=Error configuring module
+# detail tree:
+ModuleDependenciesAdapter_declared_node=Declared details of {0}
+ModuleDependenciesAdapter_configured_node=Configured details
 # ------- 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..cab371b 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));
@@ -453,8 +459,10 @@
 		}
 	}
 
-	private void createAttributeElement(String key, Object value, boolean builtIn) {
-		fChildren.add(new CPListElementAttribute(this, key, value, builtIn));
+	public CPListElementAttribute createAttributeElement(String key, Object value, boolean builtIn) {
+		CPListElementAttribute attribute= new CPListElementAttribute(this, key, value, builtIn);
+		fChildren.add(attribute);
+		return attribute;
 	}
 
 	private static boolean isFiltered(Object entry, String[] filteredKeys) {
@@ -782,9 +790,11 @@
 			for (int j= 0; j < extraAttributes.length; j++) {
 				IClasspathAttribute otherAttrib= extraAttributes[j];
 				if (IClasspathAttribute.PATCH_MODULE.equals(otherAttrib.getName())) {
-					details.add(ModulePatch.fromString(attribElem, otherAttrib.getValue()));
+					details.addAll(ModulePatch.fromMultiString(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 +809,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 +890,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..72e80c2 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
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2017 GK Software AG, and others.
+ * Copyright (c) 2017, 2019 GK Software AG, and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -15,6 +15,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -56,41 +57,52 @@
 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;
+	private Collection<String> fPossibleTargetModules;
 
 	/**
 	 * @param context listeners for status updates
 	 * @param sourceJavaElements java element representing the source modules from where packages should be exported
+	 * @param possibleTargetModules modules to be offered in content assist, or {@code null}
 	 * @param initialValue The value to edit
 	 */
-	public ModuleAddExportsBlock(IStatusChangeListener context, IJavaElement[] sourceJavaElements, ModuleAddExport initialValue) {
+	public ModuleAddExportsBlock(IStatusChangeListener context, IJavaElement[] sourceJavaElements, Collection<String> possibleTargetModules, ModuleAddExpose initialValue) {
 		fContext= context;
 		fInitialValue= initialValue;
 		fSourceJavaElements= sourceJavaElements;
+		fPossibleTargetModules= possibleTargetModules;
 
 		fSourceModuleStatus= new StatusInfo();
 		fPackageStatus= new StatusInfo();
@@ -109,6 +121,11 @@
 		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);
+		fExposeKindButtons.setDialogFieldListener(adapter);
 
 		setDefaults();
 	}
@@ -121,7 +138,7 @@
 			}
 			fPackage.setText(fInitialValue.fPackage);
 			fTargetModules.setText(fInitialValue.fTargetModules);
-			fTargetModules.setEnabled(false);
+			fTargetModules.setEnabled(fPossibleTargetModules != null);
 		}
 	}
 
@@ -195,13 +212,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())
+		if (sourceModule.isEmpty() || pack.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);
 	}
 
 	/**
@@ -261,7 +281,13 @@
 		LayoutUtil.setHorizontalGrabbing(targetModulesField);
 		BidiUtils.applyBidiProcessing(targetModulesField, StructuredTextTypeHandlerFactory.JAVA);
 
+		if (fPossibleTargetModules != null) {
+			ModuleDialog.configureModuleContentAssist(fTargetModules.getTextControl(parent), fPossibleTargetModules);
+		}
+
 		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..ec211a4 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
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2017 GK Software AG, and others.
+ * Copyright (c) 2017, 2019 GK Software AG, and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -13,6 +13,8 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.ui.wizards.buildpaths;
 
+import java.util.Collection;
+
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
@@ -31,7 +33,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.
@@ -47,9 +49,10 @@
 	 *
 	 * @param parent Parent shell for the dialog
 	 * @param sourceJavaElements java elements representing the source modules from where packages should be exported
+	 * @param possibleTargetModules modules to be offered in content assist, or {@code null}
 	 * @param value The value to edit.
 	 */
-	public ModuleAddExportsDialog(Shell parent, IJavaElement[] sourceJavaElements, ModuleAddExport value) {
+	public ModuleAddExportsDialog(Shell parent, IJavaElement[] sourceJavaElements, Collection<String> possibleTargetModules, ModuleAddExpose value) {
 		super(parent);
 
 		IStatusChangeListener listener= new IStatusChangeListener() {
@@ -58,7 +61,7 @@
 				updateStatus(status);
 			}
 		};
-		fAddExportsBlock= new ModuleAddExportsBlock(listener, sourceJavaElements, value);
+		fAddExportsBlock= new ModuleAddExportsBlock(listener, sourceJavaElements, possibleTargetModules, value);
 
 		setTitle(NewWizardMessages.AddExportsDialog_title);
 		if (sourceJavaElements == null)
@@ -121,7 +124,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..1a2354d
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleDependenciesAdapter.java
@@ -0,0 +1,947 @@
+/*******************************************************************************
+ * 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.io.File;
+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.Map.Entry;
+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.KeyEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Shell;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Status;
+
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+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.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;
+
+	/** Supertype of the two synthetic toplevel tree nodes {@link DeclaredDetails} and {@link ConfiguredDetails} */ 
+	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, fFocusModule.getElementName(), this));
+					}
+					String[] opened= fFocusModule.getOpenedPackageNames(contextModule);
+					for (String open : opened) {
+						result.add(new AccessiblePackage(open, AccessiblePackage.Kind.Opens, fFocusModule.getElementName(), 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;
+		private ModuleDependenciesPage fDependenciesPage;
+	
+		public ConfiguredDetails(IModuleDescription focusModule, CPListElement elem, ModuleKind moduleKind, ModuleDependenciesPage dependenciesPage) {
+			super(focusModule, elem);
+			fKind= moduleKind;
+			fDependenciesPage= dependenciesPage;
+		}
+		
+		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);
+			}
+			if (fKind == ModuleKind.Focus) {
+				return new Object[0];
+			}
+			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, moduleAddExpose.fTargetModules, 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)) {
+						if (modulePatch.fPaths != null) {
+							for (String path : modulePatch.getPathArray()) {
+								Path iPath= new Path(path);
+								if (iPath.segmentCount() == 1) { // is a project
+									IProject project= ResourcesPlugin.getWorkspace().getRoot().getProject(iPath.segment(0));
+									filteredDetails.add(new PatchModule(JavaCore.create(project), this));
+								} else {
+									IFolder folder= ResourcesPlugin.getWorkspace().getRoot().getFolder(iPath);
+									IJavaElement elem= JavaCore.create(folder.getProject()).getPackageFragmentRoot(folder);
+									if (elem instanceof IPackageFragmentRoot) {
+										filteredDetails.add(new PatchModule(elem, this));
+									}
+								}
+							}
+						} else {
+							// compatibility with old format not defining paths:
+							filteredDetails.add(new PatchModule(fElem.getJavaProject(), this));
+						}
+					}
+				}
+			}
+			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;
+			try {
+				initial= (selectedPackage != null)
+						? selectedPackage.convertToCP(moduleAttribute)
+						: new ModuleAddExport(fFocusModule.getElementName(), packageName, getContextModuleName(), moduleAttribute);
+			} catch (JavaModelException e) {
+				JavaPlugin.log(e);
+				MessageDialog.openError(shell, NewWizardMessages.ModuleDependenciesAdapter_configure_error, e.getMessage());
+				return false;
+			}
+
+			Set<String> possibleTargetModules= new HashSet<>(fDependenciesPage.getAllModules());
+			possibleTargetModules.remove(fFocusModule.getElementName());
+			ModuleAddExportsDialog dialog= new ModuleAddExportsDialog(shell, new IJavaElement[] { packRoot }, possibleTargetModules, 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 boolean remove(List<Object> selectedElements) {
+			try {
+				return internalRemove(selectedElements);
+			} catch (JavaModelException e) {
+				JavaPlugin.log(e);
+				MessageDialog.openError(fDependenciesPage.getShell(), NewWizardMessages.ModuleDependenciesAdapter_configure_error, e.getMessage());
+				return false;
+			}
+		}
+		private boolean internalRemove(List<Object> selectedElements) throws JavaModelException {
+			CPListElementAttribute moduleAttribute;
+			if (fKind == ModuleKind.System) {
+				moduleAttribute= ((CPListElement) fElem.getParentContainer()).findAttributeElement(CPListElement.MODULE);
+			} else {
+				moduleAttribute= fElem.findAttributeElement(CPListElement.MODULE);
+			}
+			if (moduleAttribute == null) {
+				throwNewJavaModelException("module attribute is unexpectecly missing for : "+fElem); //$NON-NLS-1$
+				return false; // not reached
+			}
+			Object value= moduleAttribute.getValue();
+			if (!(value instanceof ModuleEncapsulationDetail[])) {
+				throwNewJavaModelException("Value of module attribute has unexpected type: "+value); //$NON-NLS-1$
+			}
+			List<ModuleEncapsulationDetail> details= new ArrayList<>(Arrays.asList((ModuleEncapsulationDetail[]) value));
+			boolean patchUpdated= false;
+			for (Object node : selectedElements) {
+				if (node instanceof DetailNode<?>) {
+					if (node instanceof PatchModule) {
+						// need to merge details:
+						PatchModule patch= (PatchModule) node;
+						ModuleEncapsulationDetail.removePatchLocation(details, fFocusModule.getElementName(), patch.getPath());
+						patchUpdated= true;
+					} else {
+						ModuleEncapsulationDetail med= ((DetailNode<?>) node).convertToCP(moduleAttribute);
+						if (med != null) {
+							if (!details.remove(med)) {
+								throwNewJavaModelException("Detail "+med+" was not removed");  //$NON-NLS-1$//$NON-NLS-2$
+							}
+						}
+					}
+				} else if (node instanceof TargetModule) {
+					TargetModule targetModule= (TargetModule) node;
+					AccessiblePackage parent= targetModule.fParent;
+					parent.removeTargetModule(targetModule.fName);
+					for (int i=0; i<details.size(); i++) {
+						ModuleEncapsulationDetail detail= details.get(i);
+						if (parent.matches(detail)) {
+							details.set(i, parent.convertToCP(moduleAttribute));
+							break;
+						}
+					}
+				} else if (node instanceof ConfiguredDetails) {
+					((ConfiguredDetails) node).removeAll();
+					return true; // covers all details, changes in 'details' are irrelevant
+				}
+			}
+			moduleAttribute.setValue(details.toArray(new ModuleEncapsulationDetail[details.size()]));
+			if (patchUpdated) {
+				fDependenciesPage.buildPatchMap();
+			}
+			return true;
+		}
+
+		public boolean addReads(Shell shell) {
+			try {
+				return internalAddReads(shell);
+			} catch (JavaModelException e) {
+				JavaPlugin.log(e);
+				MessageDialog.openError(fDependenciesPage.getShell(), NewWizardMessages.ModuleDependenciesAdapter_configure_error, e.getMessage());
+				return false;
+			}
+		}
+		private boolean internalAddReads(Shell shell) throws JavaModelException {
+			Object container= fElem.getParentContainer();
+			CPListElement element= (container instanceof CPListElement) ? (CPListElement) container : fElem;
+			CPListElementAttribute moduleAttribute= element.findAttributeElement(CPListElement.MODULE);
+			if (moduleAttribute == null && fKind != ModuleKind.Focus) {
+				throwNewJavaModelException("module attribute is unexpectecly missing for : "+fElem); //$NON-NLS-1$
+				return false; // not reached
+			}
+
+			List<String> irrelevantModules;
+			try {
+				irrelevantModules= Arrays.asList(fFocusModule.getRequiredModuleNames());
+			} catch (JavaModelException e) {
+				JavaPlugin.log(e.getStatus()); // not fatal
+				irrelevantModules= Collections.emptyList();
+			}
+			irrelevantModules= new ArrayList<>(irrelevantModules);
+			irrelevantModules.add(fFocusModule.getElementName());
+
+			ModuleSelectionDialog dialog= ModuleSelectionDialog.forReads(shell, fElem.getJavaProject(), irrelevantModules);
+			if (dialog.open() != 0) {				
+				return false;
+			}
+
+			if (moduleAttribute == null) {
+				// initialize missing attribute for focus module (source folder)
+				moduleAttribute= fElem.createAttributeElement(CPListElement.MODULE, new ModuleEncapsulationDetail[0], true);
+			}
+
+			List<IModuleDescription> result= dialog.getResult();
+			ModuleEncapsulationDetail[] arrayValue= null;
+			int idx= 0;
+			Object attribute= moduleAttribute.getValue();
+			if (attribute instanceof ModuleEncapsulationDetail[]) {
+				arrayValue= (ModuleEncapsulationDetail[]) attribute;
+				idx= arrayValue.length;
+				arrayValue= Arrays.copyOf(arrayValue, arrayValue.length+result.size());
+			} else {
+				arrayValue= new ModuleEncapsulationDetail[result.size()];
+			}
+			for (IModuleDescription module : result) {
+				arrayValue[idx++]= new ModuleAddReads(fFocusModule.getElementName(), module.getElementName(), moduleAttribute);
+			}
+			element.setAttribute(CPListElement.MODULE, arrayValue);
+			return true;
+		}
+
+		public boolean addPatch(Shell shell, Map<String, String> patchMap) {
+			try {
+				return internalAddPatch(shell, patchMap);
+			} catch (JavaModelException e) {
+				JavaPlugin.log(e);
+				MessageDialog.openError(fDependenciesPage.getShell(), NewWizardMessages.ModuleDependenciesAdapter_configure_error, e.getMessage());
+				return false;
+			}
+		}
+		private boolean internalAddPatch(Shell shell, Map<String, String> patchMap) throws JavaModelException {
+			Object container= fElem.getParentContainer();
+			CPListElement element= (container instanceof CPListElement) ? (CPListElement) container : fElem;
+			CPListElementAttribute moduleAttribute= element.findAttributeElement(CPListElement.MODULE);
+			if (moduleAttribute == null) {
+				throwNewJavaModelException("module attribute is unexpectecly missing for : "+fElem); //$NON-NLS-1$
+				return false; // not reached
+			}
+			if (!(moduleAttribute.getValue() instanceof ModuleEncapsulationDetail[])) {
+				throwNewJavaModelException("Value of module attribute has unexpected type: "+moduleAttribute.getValue()); //$NON-NLS-1$
+				return false;
+			}
+
+			IJavaProject contextProject= getContextProject();
+			ModulePatchSourceSelectionDialog dialog= new ModulePatchSourceSelectionDialog(shell, fFocusModule, contextProject);
+			if (dialog.open() != 0) {
+				return false;
+			}
+			List<IJavaElement> result= dialog.getResult();
+			for (IJavaElement source : result) {
+				IPath sourcePath= source.getPath();
+				String sourcePathString= sourcePath.toString();
+				if (patchMap.containsKey(sourcePathString)) {
+					// direct conflict
+					if (!unpatchModule(shell, patchMap.get(sourcePathString), sourcePathString, sourcePathString)) {
+						return false;
+					}
+				} else {
+					if (sourcePath.segmentCount() > 1) {
+						String sourcePathString2= sourcePath.removeLastSegments(sourcePath.segmentCount()-1).toString();
+						if (patchMap.containsKey(sourcePathString2)) {
+							// conflict project (exist) vs. folder (new)
+							if (!unpatchModule(shell, patchMap.get(sourcePathString2), sourcePathString2, sourcePathString)) {
+								return false;
+							}
+						}
+					} else {
+						String pattern= sourcePathString+'/';
+						for (Entry<String, String> entry : patchMap.entrySet()) {
+							if (entry.getKey().startsWith(pattern)) {
+								// conflict folder (exist) vs. project (new)
+								if (!unpatchModule(shell, entry.getValue(), entry.getKey(), sourcePathString)) {
+									return false;
+								}
+								break;
+							}
+						}
+					}
+				}
+			}
+			String newPaths= result.stream()
+					.map(je -> je.getPath().toString())
+					.collect(Collectors.joining(File.pathSeparator));
+
+			Object attribute= moduleAttribute.getValue();
+			List<ModuleEncapsulationDetail> detailList= new ArrayList<>(Arrays.asList((ModuleEncapsulationDetail[]) attribute));
+			ModuleEncapsulationDetail.addPatchLocations(detailList, fFocusModule.getElementName(), newPaths, moduleAttribute);
+			element.setAttribute(CPListElement.MODULE, detailList.toArray(new ModuleEncapsulationDetail[detailList.size()]));
+			fDependenciesPage.buildPatchMap();
+			return true;
+		}
+
+		private boolean unpatchModule(Shell shell, String moduleToUnpatch, String oldPath, String newPath) {
+			String pathKind= oldPath.indexOf('/', 1) != -1 
+					? NewWizardMessages.ModuleDependenciesAdapter_sourceFolder_kind : NewWizardMessages.ModuleDependenciesAdapter_project_kind;
+			if (!MessageDialog.openQuestion(shell, NewWizardMessages.ModuleDependenciesAdapter_patchConflict_title, 
+					MessageFormat.format(NewWizardMessages.ModuleDependenciesAdapter_patchConflict_message,
+							pathKind, oldPath.substring(1), moduleToUnpatch, newPath.substring(1), fFocusModule.getElementName()))) {
+				return false;
+			}
+
+			CPListElementAttribute otherAttribute= fDependenciesPage.findModuleAttribute(
+					d -> d instanceof ModulePatch && ((ModulePatch) d).affects(moduleToUnpatch));
+			List<ModuleEncapsulationDetail> otherDetailList= new ArrayList<>(Arrays.asList((ModuleEncapsulationDetail[]) otherAttribute.getValue()));
+			ModuleEncapsulationDetail.removePatchLocation(otherDetailList, moduleToUnpatch, oldPath);
+			otherAttribute.setValue(otherDetailList.toArray(new ModuleEncapsulationDetail[otherDetailList.size()]));
+			return true;
+		}
+	}
+
+	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) throws JavaModelException;
+	}
+
+	/**
+	 * Declare that the package denoted by {@link #fName} is accessible (exported/opened) to the current context module,
+	 * or a list of target modules (given as comma separated string).
+	 */
+	static class AccessiblePackage extends DetailNode<ModuleAddExpose> {
+		// separator for target modules
+		private static final String COMMA= ","; //$NON-NLS-1$
+
+		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;
+		private String fTargetModules;
+
+		public AccessiblePackage(String name, Kind kind, String targetModules, Details parent) {
+			super(parent);
+			fName= name;
+			fKind= kind;
+			fTargetModules= targetModules;
+		}
+		public Kind getKind() {
+			return fKind;
+		}
+		public boolean matches(ModuleEncapsulationDetail detail) {
+			// match ignoring target modules:
+			if (!detail.affects(fParent.fFocusModule.getElementName())) {
+				return false;
+			}
+			switch (fKind) {
+				case Exports:
+					if (!(detail instanceof ModuleAddExport)) return false;
+					break;
+				case Opens:
+					if (!(detail instanceof ModuleAddOpens)) return false;
+					break;
+				default: return false;
+			}
+			ModuleAddExpose expose= (ModuleAddExpose) detail;
+			return expose.fPackage.equals(fName);
+		}
+		public Object[] getTargetModules() {
+			if (fTargetModules != null && !fTargetModules.isEmpty()) {
+				String[] targets= fTargetModules.split(COMMA);
+				Object[] result= new Object[targets.length];
+				for (int i= 0; i < targets.length; i++) {
+					result[i]= new TargetModule(this, targets[i]);
+				}
+				return result;
+			}
+			return null;
+		}
+		public boolean removeTargetModule(String module) {
+			if (fTargetModules == null) {
+				return false;
+			}
+			List<String> targets= new ArrayList<>(Arrays.asList(fTargetModules.split(COMMA)));
+			if (targets.remove(module)) {
+				fTargetModules= String.join(COMMA, targets);
+			}
+			return false;
+		}
+		@Override
+		public ModuleAddExpose convertToCP(CPListElementAttribute attribElem) throws JavaModelException {
+			if (fParent instanceof ConfiguredDetails) {
+				if (fTargetModules != null) {
+					switch (fKind) {
+						case Exports:
+							return new ModuleAddExport(fParent.fFocusModule.getElementName(), fName, fTargetModules, attribElem);
+						case Opens:
+							return new ModuleAddOpens(fParent.fFocusModule.getElementName(), fName, fTargetModules, attribElem);
+						default:
+							break;
+					}
+				}
+			}
+			throwNewJavaModelException("Failed to convert attribute "+attribElem+" with value "+attribElem.getValue());  //$NON-NLS-1$//$NON-NLS-2$
+			return null; // never reached
+		}
+	}
+
+	/** children of AccessiblePackage to denote a referenced target module. */
+	static class TargetModule {
+		final AccessiblePackage fParent;
+		final String fName;
+
+		public TargetModule(AccessiblePackage parent, String name) {
+			fParent= parent;
+			fName= name;
+		}
+
+		public String getText() {
+			return "to "+fName; //$NON-NLS-1$ (don't translate this keyword)
+		}
+	}
+
+	/** 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) throws JavaModelException {
+			if (fParent instanceof ConfiguredDetails) {
+				return new ModuleAddReads(fParent.fFocusModule.getElementName(), fName, attribElem);
+			}
+			throw new JavaModelException(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), "Failed to convert attribute "+attribElem+" with value "+attribElem.getValue()));  //$NON-NLS-1$//$NON-NLS-2$
+		}
+	}
+	/** Declare that the selected focus module is patched by the content of the given java element
+	 * (should be either an {@link IJavaProject} or an {@link IPackageFragmentRoot}). */
+	static class PatchModule extends DetailNode<ModulePatch> {
+		IJavaElement fSource;
+
+		public PatchModule(IJavaElement source, Details parent) {
+			super(parent);
+			fSource= source;
+			fName= source.getPath().makeRelative().toString();
+		}
+
+		public String getPath() {
+			return fSource.getPath().toString();
+		}
+
+		@Override
+		public ModulePatch convertToCP(CPListElementAttribute attribElem) {
+			return new ModulePatch(fParent.fFocusModule.getElementName(), getPath(), attribElem);
+		}
+	}
+
+	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 MessageFormat.format(NewWizardMessages.ModuleDependenciesAdapter_declared_node, ((DeclaredDetails) element).fFocusModule.getElementName());
+			}
+			if (element instanceof ConfiguredDetails) {
+				return NewWizardMessages.ModuleDependenciesAdapter_configured_node;
+			}
+			if (element instanceof DetailNode) {
+				return ((DetailNode<?>) element).getName();
+			}
+			if (element instanceof TargetModule) {
+				return ((TargetModule) element).getText();
+			}
+			return super.getText(element);
+		}
+		@Override
+		public Image getImage(Object element) {
+			if (element instanceof DeclaredDetails || element instanceof TargetModule) {
+				return fRegistry.get(JavaPluginImages.DESC_OBJS_MODULE);
+			}
+			if (element instanceof ConfiguredDetails) {
+				return fRegistry.get(JavaPluginImages.DESC_OBJS_MODULE_ATTRIB);
+			}
+			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) {
+				ImageDescriptor baseImg= fImageLabelProvider.getBaseImageDescriptor(((PatchModule) element).fSource, 0);
+				ImageDescriptor decoratedImgDesc= new DecoratedImageDescriptor(baseImg, JavaPluginImages.DESC_OVR_PATCH, true);
+				return JavaPlugin.getImageDescriptorRegistry().get(decoratedImgDesc);
+			}
+			return super.getImage(element);
+		}
+	}
+	
+	static class ElementSorter extends CPListElementSorter {
+		@Override
+		public int compare(Viewer viewer, Object e1, Object e2) {
+			// sorting for root nodes: DeclaredDetails > ConfiguredDetails
+			if (e1 instanceof DeclaredDetails) {
+				return e2 instanceof ConfiguredDetails ? -1 : 1;
+			}
+			if (e1 instanceof ConfiguredDetails) {
+				return e2 instanceof DeclaredDetails ? 1 : -1;
+			}
+			return super.compare(viewer, e1, e2);
+		}
+	}
+
+	public static void updateButtonEnablement(TreeListDialogField<?> list, boolean enableModify, boolean enableRemove) {
+		list.enableButton(IDX_REMOVE, enableRemove);
+		list.enableButton(IDX_EXPOSE_PACKAGE, enableModify);
+		list.enableButton(IDX_READ_MODULE, enableModify);
+		list.enableButton(IDX_PATCH, enableModify);
+	}
+
+	static void throwNewJavaModelException(String message) throws JavaModelException {
+		throw new JavaModelException(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), message));
+	}
+
+	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();
+		int numDetails= countConfiguredDetails();
+		switch (index) {
+			case IDX_REMOVE:
+				if (selectedElements.size() == 0) {
+					// no detail selected, remove the module(s) (with question):
+					fModuleDependenciesPage.removeModules();
+				} else {
+					if (getConfiguredDetails().remove(selectedElements)) {
+						field.refresh();
+					}
+				}
+				break;
+			case IDX_EDIT:
+				if (selectedElements.size() == 1 && selectedElements.get(0) instanceof AccessiblePackage) {
+					selectedPackage= (AccessiblePackage) selectedElements.get(0);
+				} else {
+					break;
+				}
+				//$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$
+		}
+		int newNum= countConfiguredDetails();
+		if ((numDetails == 0 || newNum == 0) && numDetails != newNum) { 
+			fModuleDependenciesPage.refreshModulesList(); // let ModuleDependenciesList react to changes in hasConfiguredDetails()
+		}
+	}
+
+	int countConfiguredDetails() {
+		for (Object object : fDetailsList.getElements()) {
+			if (object instanceof ConfiguredDetails)
+				return ((ConfiguredDetails) object).getChildren().length;
+		}
+		return 0;
+	}
+
+	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 AccessiblePackage);
+		} else {
+			enable= !fDetailsList.getElements().isEmpty() && allAreConfigurable(selected);
+			fDetailsList.enableButton(IDX_EDIT, false);
+		}
+		fDetailsList.enableButton(IDX_EXPOSE_PACKAGE, enable);
+		fDetailsList.enableButton(IDX_READ_MODULE, enable);
+		fDetailsList.enableButton(IDX_PATCH, enable);
+		if (enable) {
+			enable &= selected.size() > 0;
+			for (Object sel : selected) {
+				if (sel instanceof ConfiguredDetails) {
+					enable= false;
+					break;
+				}
+			}
+		}
+		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();
+		}
+		if (node instanceof TargetModule)  {
+			return true;
+		}
+		return false; // no other nodes
+	}
+
+	@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);
+				}
+				if (selected instanceof AccessiblePackage) {
+					if (getConfiguredDetails().addOrEditAccessiblePackage((AccessiblePackage) selected, fModuleDependenciesPage.getShell())) {
+						field.refresh();
+					}
+				}
+			}
+		}
+	}
+
+	@Override
+	public void keyPressed(TreeListDialogField<Object> field, KeyEvent event) {
+		if (field == fDetailsList) {
+			if (event.character == SWT.DEL && event.stateMask == 0 && fDetailsList.getButton(IDX_REMOVE).isEnabled()) {
+				List<Object> selectedElements= field.getSelectedElements();
+				if (getConfiguredDetails().remove(selectedElements)) {
+					field.refresh();
+				}
+			}
+		}
+	}
+
+	@Override
+	public Object[] getChildren(TreeListDialogField<Object> field, Object element) {
+		if (element instanceof DeclaredDetails) {
+			return ((DeclaredDetails) element).getPackages();
+		} else if (element instanceof ConfiguredDetails) {
+			return ((ConfiguredDetails) element).getChildren();
+		} else if (element instanceof AccessiblePackage) {
+			return ((AccessiblePackage) element).getTargetModules();
+		}
+		return new Object[0];
+	}
+
+	@Override
+	public Object getParent(TreeListDialogField<Object> field, Object element) {
+		if (element instanceof CPListElementAttribute) {
+			return ((CPListElementAttribute) element).getParent();
+		} else if (element instanceof DetailNode<?>) {
+			return ((DetailNode<?>) element).fParent;
+		}
+		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..5c8b301
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleDependenciesList.java
@@ -0,0 +1,239 @@
+/*******************************************************************************
+ * 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 java.util.function.Predicate;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+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.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);
+		}
+	}
+
+	public static final Point MEDIUM_SIZE= new Point(20, 16);
+
+	static ImageDescriptor DESC_OBJ_MODULE= new JavaElementImageDescriptor(JavaPluginImages.DESC_OBJS_MODULE, 0, MEDIUM_SIZE);
+	
+	static class ModulesLabelProvider extends LabelProvider implements ITableLabelProvider {
+		Function<String,ModuleKind> fGetModuleKind;
+		private Predicate<String> fHasConfiguredDetails;
+
+		public ModulesLabelProvider(Function<String, ModuleKind> getModuleKind, Predicate<String> hasConfiguredDetails) {
+			fGetModuleKind= getModuleKind;
+			fHasConfiguredDetails= hasConfiguredDetails;
+		}
+
+		@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) {
+			String moduleName= element.toString();
+			if (fHasConfiguredDetails.test(moduleName)) {
+				return "> " + moduleName; //$NON-NLS-1$
+			}
+			return moduleName;
+		}
+	}
+
+	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, this::hasConfiguredDetails));
+		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;
+	}
+
+	private boolean hasConfiguredDetails(String module) {
+		CPListElement element= fModule2Element.get(module);
+		if (element == null)
+			return false;
+		Object value= element.getAttribute(CPListElement.MODULE);
+		if (value instanceof ModuleEncapsulationDetail[] && ((ModuleEncapsulationDetail[]) value).length > 0) {
+			return true;
+		}
+		Object parent= element.getParentContainer();
+		if (parent instanceof CPListElement) {
+			element= (CPListElement) parent;
+			value= element.getAttribute(CPListElement.MODULE);
+			if (value instanceof ModuleEncapsulationDetail[]) {
+				for (ModuleEncapsulationDetail detail : (ModuleEncapsulationDetail[])value) {
+					if (detail.affects(module)) {
+						return true;
+					}
+				}
+			}
+		}
+		return false;
+	}
+
+	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 void setEnabled(boolean enable) {
+		fViewer.getControl().setEnabled(enable);
+	}
+
+	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..0cfbcd9
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleDependenciesPage.java
@@ -0,0 +1,663 @@
+/*******************************************************************************
+ * 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.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+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.corext.util.JavaModelUtil;
+
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+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)
+ * - better help on how to remove non-JRE modules (module-info, modulepath)
+ * RHS:
+ * - DeclaredDetails:
+ *   - show 'requires' module nodes (see DeclaredDetails.getPackages())
+ * - PatchModule:
+ *   - handle patch project w/o module-info (as soon as path-module is defined)
+ *     - treat the patched module as the current context module (pinned)
+ *   - prefer offering source folders of the context project for patch-module
+ * - editing of elements other than AccessiblePackage (see ModuleDependenciesAdapter.customButtonPressed())
+ * General:
+ * - distinguish test/main dependencies
+ * - special elements: ALL-UNNAMED, ALL-SYSTEM ...
+ * - Help pages and reference to it
+ *    (add to ModuleSelectionDialog.configureShell(), ModuleDependenciesPage.getControl())
+ * - Offer to switch to the corresponding tab when trying to remove a non-system module
+ *    (see error scenarii in #removeModules() and also field #fPageContainer).
+ */
+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 ModuleDependenciesList.MEDIUM_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;
+	private Button fAddSystemModuleButton;
+
+	// 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));
+		
+		fAddSystemModuleButton= new Button(left, SWT.NONE);
+		fAddSystemModuleButton.setText(NewWizardMessages.ModuleDependenciesPage_addSystemModule_button);
+		fAddSystemModuleButton.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() {
+		fModuleList.fNames.clear();
+		if (!JavaModelUtil.is9OrHigher(fCurrJProject)) {
+			fModuleList.fNames.add(NewWizardMessages.ModuleDependenciesPage_nonModularProject_dummy);
+			fModuleList.refresh();
+			fModuleList.setEnabled(false);
+			fAddSystemModuleButton.setEnabled(false);
+			fDetailsList.removeAllElements();
+			fDetailsList.refresh();
+			ModuleDependenciesAdapter.updateButtonEnablement(fDetailsList, false, false);
+			return;
+		}
+		fModuleList.setEnabled(true);
+		fAddSystemModuleButton.setEnabled(true);
+		fModule2RequiredModules= new HashMap<>();
+		fModuleRequiredByModules= new HashMap<>();
+		Set<String> recordedModules= new HashSet<>();
+
+		List<CPListElement> cpelements= fClassPathList.getElements();
+
+		for (CPListElement cpe : cpelements) {
+			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;
+					int shownModules= 0;
+					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);
+								shownModules++;
+							}
+						}
+					}
+					if (kind == ModuleKind.System) {
+						// additionally capture dependency information about all system module disregarding --limit-modules
+						IPackageFragmentRoot[] unfilteredPackageFragmentRoots= fCurrJProject.findUnfilteredPackageFragmentRoots(cpe.getClasspathEntry());
+						for (IPackageFragmentRoot packageRoot : unfilteredPackageFragmentRoots) {
+							IModuleDescription module= packageRoot.getModuleDescription();
+							if (module != null) {
+								recordModule(module, recordedModules, null/*don't add to fModuleList*/, kind);
+							}
+						}
+						if (unfilteredPackageFragmentRoots.length == shownModules) {
+							fAddSystemModuleButton.setEnabled(false);
+						}
+					}
+					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();
+		buildPatchMap();
+	}
+
+	public Collection<String> getAllModules() {
+		return fModuleList.fNames;
+	}
+
+	public void buildPatchMap() {
+		fPatchMap.clear();
+		for (CPListElement cpe : fClassPathList.getElements()) {
+			Object value= cpe.getAttribute(CPListElement.MODULE);
+			if (value instanceof ModuleEncapsulationDetail[]) {
+				for (ModuleEncapsulationDetail detail : (ModuleEncapsulationDetail[]) value) {
+					if (detail instanceof ModulePatch) {
+						ModulePatch patch= (ModulePatch) detail;
+						for (String path : patch.getPathArray()) {
+							fPatchMap.put(path, patch.fModule);
+						}
+					}
+				}
+			}
+		}
+	}
+
+	private void recordModule(IModuleDescription module, Set<String> moduleNames, CPListElement cpe, ModuleKind kind) {
+		if (module.getElementName().isEmpty()) return; // assume this to be an ill-configured auto module
+		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(new ModuleDependenciesAdapter.DeclaredDetails(module, element));
+			ModuleKind moduleKind= fModuleList.getModuleKind(element);
+			ModuleDependenciesAdapter.ConfiguredDetails configured= new ModuleDependenciesAdapter.ConfiguredDetails(module, element, moduleKind, this);
+			fDetailsList.addElement(configured);
+			fDetailsList.expandElement(configured, 1);
+		}
+		ModuleDependenciesAdapter.updateButtonEnablement(fDetailsList, elements.size() == 1, !elements.isEmpty());
+	}
+
+	@Override
+	public boolean isEntryKind(int kind) {
+		return true;
+	}
+
+	@Override
+	public void setFocus() {
+    	fDetailsList.setFocus();
+	}
+
+	public void refreshModulesList() {
+		fModuleList.refresh();
+	}
+
+	void addSystemModules() {
+		CPListElement cpListElement= findSystemLibraryElement();
+		ModuleSelectionDialog dialog= ModuleSelectionDialog.forSystemModules(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));
+				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;
+	}
+
+	/**
+	 * Find a module attribute in the current classpath that satisfies the given predicate. 
+	 * @param predicate this predicate must be fulfilled by any detail of a found module attribte
+	 * @return if a predicate match was found the enclosing module attribute will be returned, else {@code null}
+	 */
+	public CPListElementAttribute findModuleAttribute(Predicate<ModuleEncapsulationDetail> predicate) {
+		for (CPListElement element : fClassPathList.getElements()) {
+			CPListElementAttribute attribute= element.findAttributeElement(CPListElement.MODULE);
+			if (attribute != null && attribute.getValue() instanceof ModuleEncapsulationDetail[]) {
+				for (ModuleEncapsulationDetail detail : (ModuleEncapsulationDetail[]) attribute.getValue()) {
+					if (predicate.test(detail)) {
+						return attribute;
+					}
+				}
+			}
+		}
+		return null;
+	}
+}
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..a438e86 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
@@ -17,6 +17,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -85,6 +86,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 +186,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 +227,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();
@@ -522,7 +524,7 @@
 		}
 	}
 
-	public static void configureModuleContentAssist(Text textControl, Set<String> moduleNames) {
+	public static void configureModuleContentAssist(Text textControl, Collection<String> moduleNames) {
 		if (moduleNames.size() == 1) {
 			textControl.setText(moduleNames.iterator().next());
 		} else if (!moduleNames.isEmpty()) {
@@ -635,7 +637,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 +919,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);
+			ModuleAddExportsDialog dialog= new ModuleAddExportsDialog(getShell(), fJavaElements, null, 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) {
-			ModuleAddExportsDialog dialog= new ModuleAddExportsDialog(getShell(), fJavaElements, export);
+		void editEntry(ListDialogField<ModuleAddExpose> field, ModuleAddExpose export) {
+			ModuleAddExportsDialog dialog= new ModuleAddExportsDialog(getShell(), fJavaElements, null, 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..05620f5 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
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2017, 2018 GK Software SE, and others.
+ * Copyright (c) 2017, 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
@@ -13,10 +13,13 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.ui.wizards.buildpaths;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 import org.eclipse.core.runtime.IPath;
@@ -87,10 +90,12 @@
 	}
 
 	public static String encodeFiltered(ModuleEncapsulationDetail[] details, Class<?> detailClass) {
+		// ModulePatch uses File.pathSeparator internally, which conflicts with single ":" on unix.
+		String separator= detailClass == ModulePatch.class ? "::" : ":";  //$NON-NLS-1$//$NON-NLS-2$
 		return Arrays.stream(details)
 				.filter(detailClass::isInstance)
 				.map(ModuleEncapsulationDetail::toString)
-				.collect(Collectors.joining(":")); //$NON-NLS-1$
+				.collect(Collectors.joining(separator));
 	}
 
 	/**
@@ -98,44 +103,131 @@
 	 */
 	static class ModulePatch extends ModuleEncapsulationDetail {
 
+		public static Collection<ModulePatch> fromMultiString(CPListElementAttribute attribElem, String values) {
+			List<ModulePatch> patches= new ArrayList<>();
+			for (String value : values.split("::")) { // see comment in #encodeFiltered(..) //$NON-NLS-1$
+				ModulePatch patch= fromString(attribElem, value);
+				if (patch != null)
+					patches.add(patch);
+			}
+			return patches;
+		}
+
 		public static ModulePatch fromString(CPListElementAttribute attribElem, String value) {
 			return new ModulePatch(value, attribElem);
 		}
 
 		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= attribElem.getParent().getJavaProject().getPath().toString();
+			} else {
+				fModule= value.substring(0, eqIdx);
+				fPaths= value.substring(eqIdx + 1);
+			}
 			fAttribElem= attribElem;
 		}
 
+		public ModulePatch(String moduleName, String paths, CPListElementAttribute attribElem) {
+			fModule= moduleName;
+			fPaths= paths;
+			fAttribElem= attribElem;
+		}
+		
+		public ModulePatch addLocations(String newLocations) {
+			String mergedPaths= fPaths + File.pathSeparatorChar + newLocations;
+			return new ModulePatch(fModule, mergedPaths, fAttribElem);
+		}
+
+		public ModulePatch removeLocations(String locations) {
+			Set<String> toRemove= new HashSet<>(Arrays.asList(locations.split(File.pathSeparator)));
+			List<String> current= new ArrayList<>(Arrays.asList(fPaths.split(File.pathSeparator)));
+			current.removeAll(toRemove);
+			String newPaths= String.join(File.pathSeparator, current);
+			if (newPaths.isEmpty()) {
+				return null;
+			}
+			return new ModulePatch(fModule, newPaths, fAttribElem);
+		}
+
+		@Override
+		public boolean affects(String module) {
+			return module.equals(fModule);
+		}
+
+		public String[] getPathArray() {
+			return fPaths.split(File.pathSeparator);
+		}
+
+		@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 +238,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 +246,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 +346,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 +407,64 @@
 			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);
+
+	/**
+	 * Searches the given list of details for a {@link ModulePatch} element affecting the given {@code module}.
+	 * If found, replaces the found ModulePatch with a new value where given {@code newLocations} have been added.
+	 * If no matching {@link ModulePatch} if found, a new one will be created and added to the {@code details} list.
+	 * This operation modifies the given list of details.
+	 * @param details list representation of a module attribute value
+	 * @param module name of the module, for which a {@link ModulePatch} should be modified
+	 * @param newLocations paths (separated by {@link File#pathSeparator}) to be added to the {@link ModulePatch}.
+	 * @param attribElem parent attribute, to hold the resulting details (not updated during this operation)
+	 */
+	public static void addPatchLocations(List<ModuleEncapsulationDetail> details, String module, String newLocations, CPListElementAttribute attribElem) {
+		for (int i= 0; i < details.size(); i++) {
+			ModuleEncapsulationDetail detail= details.get(i);
+			if (detail instanceof ModulePatch) {
+				ModulePatch oldPatch= (ModulePatch) detail;
+				if (oldPatch.affects(module)) {
+					details.set(i, oldPatch.addLocations(newLocations));
+					return;
+				}
+			}
+		}
+		details.add(new ModulePatch(module, newLocations, attribElem));
+	}
+
+	/**
+	 * Searches the given list of details for a {@link ModulePatch} element affecting the given {@code module}.
+	 * If found, replaces the found {@link ModulePatch} with a new value where the given {@code paths} have been removed.
+	 * This operation modifies the given list of details.
+	 * @param details list representation of a module attribute value
+	 * @param module name of the module, for which a {@link ModulePatch} should be modified
+	 * @param paths paths (separated by {@link File#pathSeparator}) to be removed from the {@link ModulePatch}.
+	 */
+	public static void removePatchLocation(List<ModuleEncapsulationDetail> details, String module, String paths) {
+		for (int i= 0; i < details.size(); i++) {
+			ModuleEncapsulationDetail detail= details.get(i);
+			if (detail instanceof ModulePatch) {
+				ModulePatch oldPatch= (ModulePatch) detail;
+				if (oldPatch.affects(module)) {
+					ModulePatch updatedPatch= oldPatch.removeLocations(paths);
+					if (updatedPatch == null) {
+						details.remove(i);
+					} else {
+						details.set(i, updatedPatch);
+					}
+					break;
+				}
+			}
+		}
+	}
 }
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..1d635ab
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModulePatchSourceSelectionDialog.java
@@ -0,0 +1,234 @@
+/*******************************************************************************
+ * 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 com.ibm.icu.text.MessageFormat;
+
+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.IDialogConstants;
+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;
+
+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 List<IJavaElement> fResult;
+
+	protected ModulePatchSourceSelectionDialog(Shell shell, IModuleDescription focusModule, IJavaProject contextProject) {
+		super(shell);
+		fFocusModule= focusModule;
+		fContextProject= contextProject;
+		findProjects();
+	}
+
+	@Override
+	protected void configureShell(Shell newShell) {
+		super.configureShell(newShell);
+		newShell.setText(NewWizardMessages.ModulePatchSourceSelectionDialog_patchSourceLocation_title);
+	}
+
+	@Override
+	protected int getShellStyle() {
+		return super.getShellStyle() | SWT.RESIZE;
+	}
+
+	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);
+					if (jProj.getModuleDescription() == null) {
+						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)) {
+						if (packageFragmentRoot.getModuleDescription() == null) {
+							sourceRoots.add(packageFragmentRoot);
+						}
+					}
+				}
+			}
+		} catch (JavaModelException e) {
+			JavaPlugin.log(e.getStatus());
+		}
+	}
+
+	@Override
+	protected Control createDialogArea(Composite parent) {
+		Composite composite= (Composite) super.createDialogArea(parent);
+		PixelConverter converter= new PixelConverter(parent);
+		int widthHint= converter.convertWidthInCharsToPixels(60);
+		Label message= new Label(composite, SWT.LEFT + SWT.WRAP);
+		message.setText(MessageFormat.format(
+							NewWizardMessages.ModulePatchSourceSelectionDialog_patchSourceLocation_message,
+							fFocusModule.getElementName()));
+		GridData gdLabel= new GridData(SWT.FILL, SWT.NONE, true, false);
+		gdLabel.widthHint= widthHint;
+		message.setLayoutData(gdLabel);
+
+		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);
+
+		GridData gd= new GridData(SWT.FILL, SWT.FILL, true, true);
+		gd.widthHint= widthHint;
+		gd.heightHint= converter.convertHeightInCharsToPixels(20);
+		treeViewer.getControl().setLayoutData(gd);
+
+		return composite;
+	}
+
+	@Override
+	protected Control createButtonBar(Composite parent) {
+		Control control= super.createButtonBar(parent);
+		getButton(IDialogConstants.OK_ID).setEnabled(false); // until element(s) enabled
+		return control;
+
+	}
+	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) {
+				fResult.add((IJavaProject) object);
+			}
+		}
+		getButton(IDialogConstants.OK_ID).setEnabled(!fResult.isEmpty());
+	}
+
+	public List<IJavaElement> 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..f82a5ff
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/wizards/buildpaths/ModuleSelectionDialog.java
@@ -0,0 +1,318 @@
+/*******************************************************************************
+ * 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.HashSet;
+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.IPath;
+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.core.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
+
+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.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.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.JavaPlugin;
+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;
+import org.eclipse.jdt.internal.ui.wizards.dialogfields.SelectionButtonDialogField;
+
+public class ModuleSelectionDialog extends TrayDialog {
+
+	// widgets:
+	private String fTitle;
+	private String fMessage;
+	private TableViewer fViewer;
+	private Button fOkButton;
+	private Runnable fFlipMessage; // may show a wait message first, use this to flip to the normal message
+	private SelectionButtonDialogField fSelectAllCheckbox;
+	
+	boolean fInSetSelection= false; // to avoid re-entrance -> StackOverflow
+
+	// input data:
+	private IJavaProject fJavaProject;
+	private IClasspathEntry fJREEntry;
+
+	// internal storage and a 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<>();
+	private Map<String,ModuleKind> fKinds= 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 list of modules already shown in the LHS list ({@link ModuleDependenciesList})
+	 * @param closureComputation a function from module names to their full transitive closure over 'requires'. 
+	 * @return the configured dialog
+	 */
+	public static ModuleSelectionDialog forSystemModules(Shell shell, IJavaProject javaProject, IClasspathEntry jreEntry, List<String> shownModules, Function<List<String>, Set<String>> closureComputation) {
+		return new ModuleSelectionDialog(shell, javaProject, jreEntry, shownModules, closureComputation,
+				NewWizardMessages.ModuleSelectionDialog_addSystemModules_title, NewWizardMessages.ModuleSelectionDialog_addSystemModules_message);
+	}
+	/**
+	 * Let the user select a module from all modules found in the workspace, except those in {@code irrelevantModules}.
+	 * @param shell for showing the dialog
+	 * @param javaProject the java project whose build path is being configured
+	 * @param irrelevantModules list of modules not relevant for selection
+	 * @return the configured dialog
+	 */
+	public static ModuleSelectionDialog forReads(Shell shell, IJavaProject javaProject, List<String> irrelevantModules) {
+		return new ModuleSelectionDialog(shell, javaProject, null, irrelevantModules, HashSet::new,
+				NewWizardMessages.ModuleSelectionDialog_selectModule_title, NewWizardMessages.ModuleSelectionDialog_selectReadModule_message);
+	}
+	private ModuleSelectionDialog(Shell shell, IJavaProject javaProject, IClasspathEntry jreEntry, List<String> shownModules, 
+			Function<List<String>, Set<String>> closureComputation, String title, String message) {
+		super(shell);
+		fTitle= title;
+		fMessage= message;
+		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(NewWizardMessages.ModuleSelectionDialog_searchModules_job) {
+				@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) { // should cancelPressed() actively abort the search?
+			return Collections.emptyList();
+		}
+		// also search for automatic modules:
+		for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
+			IJavaProject jPrj= JavaCore.create(project);
+			if (jPrj.getModuleDescription() == null) {
+				checkAddModule(result, JavaCore.getAutomaticModuleDescription(jPrj));
+			}
+			for (IPackageFragmentRoot root : jPrj.getAllPackageFragmentRoots()) {
+				if (root.isArchive() && root.getModuleDescription() == null) {
+					checkAddModule(result, JavaCore.getAutomaticModuleDescription(root));
+				}
+			}
+		}
+		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()
+		fKinds.put(moduleDescription.getElementName(), getKind(moduleDescription));
+	}
+
+	private ModuleKind getKind(IModuleDescription moduleDescription) {
+		if (moduleDescription.isAutoModule()) {
+			return ModuleKind.Automatic;
+		}
+		IPackageFragmentRoot root= (IPackageFragmentRoot) moduleDescription.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
+		try {
+			IPath path= root.getRawClasspathEntry().getPath();
+			if (LibrariesWorkbookPage.isJREContainer(path)) {
+				return ModuleKind.System;
+			}
+		} catch (JavaModelException e) {
+			JavaPlugin.log(e);
+		}
+		return ModuleKind.Normal;
+	}
+
+	@Override
+	protected void configureShell(Shell newShell) {
+		super.configureShell(newShell);
+		newShell.setText(fTitle);
+	}
+	
+	@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(fKinds::get, s -> false));
+		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(NewWizardMessages.ModuleSelectionDialog_searchModules_temp_message);
+			fFlipMessage= () ->  {
+				message.setText(fMessage);
+			};
+		} else {
+			tableViewer.setInput(fAvailableModules);
+			message.setText(fMessage);
+		}
+		fViewer= tableViewer;
+		
+		fSelectAllCheckbox= new SelectionButtonDialogField(SWT.CHECK);
+		fSelectAllCheckbox.setLabelText(NewWizardMessages.ModuleSelectionDialog_selectAll_button);
+		fSelectAllCheckbox.setSelection(false);
+		fSelectAllCheckbox.setDialogFieldListener(field -> selectAll(fSelectAllCheckbox.isSelected()));
+		fSelectAllCheckbox.doFillIntoGrid(composite, 2);
+
+		return composite;
+	}
+
+	@Override
+	protected void createButtonsForButtonBar(Composite parent) {
+		fOkButton= createButton(parent, IDialogConstants.OK_ID,
+				NewWizardMessages.ModuleSelectionDialog_add_button, true);
+		fOkButton.setEnabled(false); // until elements have been selected
+		createButton(parent, IDialogConstants.CANCEL_ID,
+				IDialogConstants.CANCEL_LABEL, false);
+	}
+	private void selectAll(boolean selected) {
+		if (fInSetSelection) return;
+		if (selected) {
+			fViewer.setSelection(new StructuredSelection(fAvailableModules));
+		} else {
+			fViewer.setSelection(StructuredSelection.EMPTY);
+		}
+	}
+
+	private void selectionChanged(SelectionChangedEvent e) {
+		if (fInSetSelection) return;
+		fInSetSelection= true;
+		try {
+			IStructuredSelection selection= e.getStructuredSelection();
+			if (selection == null || selection.isEmpty()) {
+				fOkButton.setEnabled(false);
+				fSelectAllCheckbox.setSelection(false);
+				return;
+			}
+			List<String> selectedNames= selection.toList();
+			Set<String> closure= fClosureComputation.apply(selectedNames);
+			if (closure.size() > selectedNames.size()) {
+				// select all members of the closure:
+				fViewer.setSelection(new StructuredSelection(new ArrayList<>(closure)));
+			}
+			fOkButton.setEnabled(true);
+			fSelectedModules= new ArrayList<>(closure); // remember result
+			fSelectAllCheckbox.setSelection(closure.containsAll(fAvailableModules));
+		} finally {
+			fInSetSelection= false;
+		}
+	}
+
+	public List<IModuleDescription> getResult() {
+		return fSelectedModules.stream()
+				.filter(m -> !fAllIncluded.contains(m)) // skip modules that are already included
+				.map(fModulesByName::get)
+				.collect(Collectors.toList());
+	}
+}