blob: 973b243473303eaec31bb2a5369121818c0d4c14 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019 GK Software SE, and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Stephan Herrmann - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.ui.wizards.buildpaths;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
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.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.window.Window;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IModuleDescription;
import org.eclipse.jdt.core.IPackageFragment;
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.dialogs.StatusInfo;
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;
private static final int IDX_JPMS_OPTIONS= 9;
/** 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);
}
return null;
}
protected String getContextModuleName() {
try {
IModuleDescription moduleDescription= fElem.getJavaProject().getModuleDescription();
if (moduleDescription != null) {
return moduleDescription.getElementName();
}
} catch (JavaModelException jme) {
JavaPlugin.log(jme);
}
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);
}
public Object[] getChildren() {
List<Object> children= new ArrayList<>();
getRequires(children);
getPackages(children);
return children.toArray();
}
private void getRequires(List<Object> children) {
if (fFocusModule != null) {
if (!fFocusModule.isAutoModule()) {
try {
for (String required : fFocusModule.getRequiredModuleNames())
children.add(new ReadModule(required, this));
} catch (JavaModelException e) {
JavaPlugin.log(e);
}
}
}
}
private void getPackages(List<Object> result) {
try {
if (fFocusModule != null) {
if (fFocusModule.isAutoModule()) {
IPackageFragmentRoot root= (IPackageFragmentRoot) fFocusModule.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
if (root != null) {
// auto module exports all non-empty packages:
for (IJavaElement child : root.getChildren()) {
if (child instanceof IPackageFragment
&& !child.getElementName().isEmpty()
&& ((IPackageFragment) child).containsJavaResources())
{
result.add(new AccessiblePackage(child.getElementName(), AccessiblePackage.Kind.Exports, "", this)); //$NON-NLS-1$
}
}
}
} else {
IModuleDescription contextModule= getContextModule();
String[] exported= fFocusModule.getExportedPackageNames(contextModule);
for (String export : exported) {
result.add(new AccessiblePackage(export, AccessiblePackage.Kind.Exports, "", this)); //$NON-NLS-1$
}
String[] opened= fFocusModule.getOpenedPackageNames(contextModule);
for (String open : opened) {
result.add(new AccessiblePackage(open, AccessiblePackage.Kind.Opens, "", this)); //$NON-NLS-1$
}
}
}
} catch (JavaModelException e) {
JavaPlugin.log(e);
}
}
}
/** 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() {
// For containers like JRE / Libraries: aggregate attribute is in the parent
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);
IJavaElement jContainer= fFocusModule.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
if (jContainer == null) {
jContainer= fFocusModule.getJavaProject();
}
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[] { jContainer }, 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); // 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;
}
protected abstract int rank();
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;
}
@Override
protected int rank() {
return 2;
}
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
protected int rank() {
return 0;
}
@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();
}
@Override
protected int rank() {
return 1;
}
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;
}
if (e1 instanceof DetailNode<?> && e2 instanceof DetailNode<?>) {
int rank1= ((DetailNode<?>) e1).rank();
int rank2= ((DetailNode<?>) e2).rank();
if (rank1 < rank2) return -1;
if (rank1 > rank2) return 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();
}
}
validate();
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();
validate();
}
break;
case IDX_JPMS_OPTIONS:
fModuleDependenciesPage.showJMPSOptionsDialog();
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();
validate();
}
}
}
}
@Override
public Object[] getChildren(TreeListDialogField<Object> field, Object element) {
if (element instanceof DeclaredDetails) {
return ((DeclaredDetails) element).getChildren();
} 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;
}
void validate() {
IWorkspaceRoot wsRoot= ResourcesPlugin.getWorkspace().getRoot();
Map<IPath, Map<String,List<IResource>>> out2mod2src= new HashMap<>(); // output -> (module -> sources)
for (Entry<String, String> entry : fModuleDependenciesPage.fPatchMap.entrySet()) {
Path path= new Path(entry.getKey());
IResource resource= wsRoot.findMember(path);
IJavaProject javaProject= JavaCore.create(resource.getProject());
IPath output= findOutputFor(javaProject, resource.getFullPath());
if (output != null) {
Map<String, List<IResource>> module2source= out2mod2src.get(output);
if (module2source == null) {
out2mod2src.put(output, module2source= new HashMap<>());
}
List<IResource> sources= module2source.get(entry.getValue());
if (sources == null) {
module2source.put(entry.getValue(), sources= new ArrayList<>());
}
sources.add(resource);
}
}
for (Map<String, List<IResource>> mod2src : out2mod2src.values()) {
// does any output location map to more than one module?
if (mod2src.entrySet().size() > 1) {
fModuleDependenciesPage.setStatus(new StatusInfo(IStatus.ERROR,
MessageFormat.format(NewWizardMessages.ModuleDependenciesAdapter_patchOutputConflict_validationError,
mod2src.values().stream()
.flatMap(folders -> folders.stream().map(f -> f.getFullPath().makeRelative().toString()))
.collect(Collectors.joining(", "))))); //$NON-NLS-1$
return;
}
}
fModuleDependenciesPage.setStatus(StatusInfo.OK_STATUS);
}
private IPath findOutputFor(IJavaProject project, IPath source) {
try {
if (project.getPath().equals(source)) {
return project.getOutputLocation();
}
IClasspathEntry classpathEntry= project.getClasspathEntryFor(source);
if (classpathEntry != null) {
if (classpathEntry.getOutputLocation() != null)
return classpathEntry.getOutputLocation();
return project.getOutputLocation();
}
} catch (JavaModelException e) {
JavaPlugin.log(e);
}
return null;
}
// ---------- IDialogFieldListener --------
@Override
public void dialogFieldChanged(DialogField field) {
// libaryPageDialogFieldChanged(field);
}
}