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