blob: 316066fa1e266ff8b5a8b27249874a75a351f1cf [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2019 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.ui.text.correction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import org.eclipse.swt.graphics.Image;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
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.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.manipulation.TypeNameMatchCollector;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.TypeNameMatch;
import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jdt.ui.text.java.ClasspathFixProcessor;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.text.correction.proposals.AddModuleRequiresCorrectionProposal;
/**
* Default contribution to org.eclipse.jdt.ui.classpathFixProcessors
*/
public class DefaultClasspathFixProcessor extends ClasspathFixProcessor {
protected static class DefaultClasspathFixProposal extends ClasspathFixProposal {
private String fName;
private Change fChange;
private String fDescription;
private int fRelevance;
public DefaultClasspathFixProposal(String name, Change change, String description, int relevance) {
fName= name;
fChange= change;
fDescription= description;
fRelevance= relevance;
}
@Override
public String getAdditionalProposalInfo() {
return fDescription;
}
@Override
public Change createChange(IProgressMonitor monitor) {
return fChange;
}
@Override
public String getDisplayString() {
return fName;
}
@Override
public Image getImage() {
return JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
}
@Override
public int getRelevance() {
return fRelevance;
}
}
@Override
public ClasspathFixProposal[] getFixImportProposals(IJavaProject project, String missingType) throws CoreException {
ArrayList<DefaultClasspathFixProposal> res= new ArrayList<>();
if (!missingType.startsWith(DefaultModulepathFixProcessor.MODULE_SEARCH)) {
collectProposals(project, missingType, res);
}
return res.toArray(new ClasspathFixProposal[res.size()]);
}
private void collectProposals(IJavaProject project, String name, Collection<DefaultClasspathFixProposal> proposals) throws CoreException {
int idx= name.lastIndexOf('.');
char[] packageName= idx != -1 ? name.substring(0, idx).toCharArray() : null; // no package provided
char[] typeName= name.substring(idx + 1).toCharArray();
if (typeName.length == 1 && typeName[0] == '*') {
typeName= null;
}
IJavaSearchScope scope= SearchEngine.createWorkspaceScope();
ArrayList<TypeNameMatch> res= new ArrayList<>();
TypeNameMatchCollector requestor= new TypeNameMatchCollector(res);
int matchMode= SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE;
new SearchEngine().searchAllTypeNames(packageName, matchMode, typeName,
matchMode, IJavaSearchConstants.TYPE, scope, requestor,
IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, null);
if (res.isEmpty()) {
return;
}
IModuleDescription currentModuleDescription= null;
if (JavaModelUtil.is9OrHigher(project)) {
currentModuleDescription= project.getModuleDescription();
if (currentModuleDescription != null && !currentModuleDescription.exists()) {
currentModuleDescription= null;
}
}
HashMap<IClasspathEntry, TypeNameMatch> classPathEntryToTypeNameMatch= new HashMap<>();
HashMap<TypeNameMatch, String> typeNameMatchToModuleName= new HashMap<>();
HashSet<IClasspathEntry> classpaths= new HashSet<>();
HashSet<TypeNameMatch> typesWithModule= new HashSet<>();
if (currentModuleDescription != null) {
for (TypeNameMatch curr : res) {
IType type= curr.getType();
if (type != null) {
IPackageFragmentRoot root= (IPackageFragmentRoot) type.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
try {
IClasspathEntry entry= root.getRawClasspathEntry();
if (entry == null) {
continue;
}
IModuleDescription projectModule= null;
String moduleName= null;
projectModule= root.getModuleDescription();
if (projectModule != null && projectModule.exists()) {
moduleName= projectModule.getElementName();
}
if (classpaths.add(entry)) {
classPathEntryToTypeNameMatch.put(entry, curr);
typesWithModule.add(curr);
if (moduleName != null) {
typeNameMatchToModuleName.put(curr, moduleName);
}
} else {
Object typeNameMatch= classPathEntryToTypeNameMatch.get(entry);
if (typeNameMatch != null) {
if (moduleName != null) {
Object modName= typeNameMatchToModuleName.get(typeNameMatch);
if (!moduleName.equals(modName)) {
// remove classpath module if there are multiple type matches
// which belong to the same class path but different modules
typesWithModule.remove(typeNameMatch);
classPathEntryToTypeNameMatch.remove(entry);
}
} else {
// remove classpath module if there are multiple type matches
// which belong to the same class path but one has module and the other does not
typesWithModule.remove(typeNameMatch);
classPathEntryToTypeNameMatch.remove(entry);
}
}
}
} catch (JavaModelException e) {
// ignore
}
}
}
}
HashSet<Object> addedClaspaths= new HashSet<>();
for (TypeNameMatch curr : res) {
IType type= curr.getType();
if (type != null) {
IPackageFragmentRoot root= (IPackageFragmentRoot) type.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
try {
IClasspathEntry entry= root.getRawClasspathEntry();
if (entry == null) {
continue;
}
Change cuChange= null;
String moduleName= null;
boolean isModule= false;
if (typesWithModule.contains(curr)) {
moduleName= typeNameMatchToModuleName.get(curr);
if (moduleName != null && currentModuleDescription != null) {
ICompilationUnit currentCU= currentModuleDescription.getCompilationUnit();
isModule= true;
String[] args= { moduleName };
final String changeName= Messages.format(CorrectionMessages.UnresolvedElementsSubProcessor_add_requires_module_info, args);
final String changeDescription= Messages.format(CorrectionMessages.UnresolvedElementsSubProcessor_add_requires_module_description, args);
AddModuleRequiresCorrectionProposal moduleRequiresProposal= new AddModuleRequiresCorrectionProposal(moduleName, changeName, changeDescription, currentCU, 0);
cuChange= moduleRequiresProposal.getChange();
if (cuChange != null) {
cuChange.initializeValidationData(new NullProgressMonitor());
}
}
}
IJavaProject other= root.getJavaProject();
int entryKind= entry.getEntryKind();
if ((entry.isExported() || entryKind == IClasspathEntry.CPE_SOURCE) && addedClaspaths.add(other)) {
IClasspathEntry newEntry= null;
if (isModule) {
IClasspathAttribute[] extraAttributes= new IClasspathAttribute[] {
JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, "true") //$NON-NLS-1$
};
newEntry= JavaCore.newProjectEntry(other.getPath(), null, true, extraAttributes, false);
} else {
newEntry= JavaCore.newProjectEntry(other.getPath());
}
Change change= ClasspathFixProposal.newAddClasspathChange(project, newEntry);
if (change != null) {
String[] args= { BasicElementLabels.getResourceName(other.getElementName()), BasicElementLabels.getResourceName(project.getElementName()) };
String label= Messages.format(CorrectionMessages.ReorgCorrectionsSubProcessor_addcp_project_description, args);
String desc= label;
if (cuChange != null) {
String additionalLabel= cuChange.getName();
additionalLabel= additionalLabel.substring(0, 1).toLowerCase() + additionalLabel.substring(1);
change= new CompositeChange(change.getName(), new Change[] { change, cuChange });
String[] arguments= { label, additionalLabel };
label= Messages.format(CorrectionMessages.UnresolvedElementsSubProcessor_combine_two_proposals_info, arguments);
desc= label;
}
DefaultClasspathFixProposal proposal= new DefaultClasspathFixProposal(label, change, desc, IProposalRelevance.ADD_PROJECT_TO_BUILDPATH);
proposals.add(proposal);
}
}
if (entryKind == IClasspathEntry.CPE_CONTAINER) {
IPath entryPath= entry.getPath();
if (isNonProjectSpecificContainer(entryPath)) {
addLibraryProposal(project, root, entry, addedClaspaths, proposals, cuChange);
} else {
try {
IClasspathContainer classpathContainer= JavaCore.getClasspathContainer(entryPath, root.getJavaProject());
if (classpathContainer != null) {
IClasspathEntry entryInContainer= JavaModelUtil.findEntryInContainer(classpathContainer, root.getPath());
if (entryInContainer != null) {
addLibraryProposal(project, root, entryInContainer, addedClaspaths, proposals, cuChange);
}
}
} catch (CoreException e) {
// ignore
}
}
} else if ((entryKind == IClasspathEntry.CPE_LIBRARY || entryKind == IClasspathEntry.CPE_VARIABLE)) {
addLibraryProposal(project, root, entry, addedClaspaths, proposals, cuChange);
}
} catch (JavaModelException e) {
// ignore
}
}
}
}
protected void addLibraryProposal(IJavaProject project, IPackageFragmentRoot root, IClasspathEntry entry, Collection<Object> addedClaspaths, Collection<DefaultClasspathFixProposal> proposals,
Change additionalChange) throws JavaModelException {
if (isJREContainer(entry.getPath()) && hasJREInClassPath(project)) {
return;
}
if (addedClaspaths.add(entry)) {
String label= getAddClasspathLabel(entry, root, project);
if (label != null) {
Change change= ClasspathFixProposal.newAddClasspathChange(project, entry);
if (change != null) {
if (additionalChange != null) {
String additionalLabel= additionalChange.getName();
additionalLabel= additionalLabel.substring(0, 1).toLowerCase() + additionalLabel.substring(1);
String[] arguments= { label, additionalLabel };
label= Messages.format(CorrectionMessages.UnresolvedElementsSubProcessor_combine_two_proposals_info, arguments);
change= new CompositeChange(change.getName(), new Change[] { change, additionalChange });
}
DefaultClasspathFixProposal proposal= new DefaultClasspathFixProposal(label, change, label, IProposalRelevance.ADD_TO_BUILDPATH);
proposals.add(proposal);
}
}
}
}
protected boolean isNonProjectSpecificContainer(IPath containerPath) {
if (containerPath.segmentCount() > 0) {
String id= containerPath.segment(0);
if (id.equals(JavaCore.USER_LIBRARY_CONTAINER_ID) || id.equals(JavaRuntime.JRE_CONTAINER)) {
return true;
}
}
return false;
}
protected boolean isJREContainer(IPath containerPath) {
if (containerPath != null && containerPath.segmentCount() > 0) {
String id= containerPath.segment(0);
if (id.equals(JavaRuntime.JRE_CONTAINER)) {
return true;
}
}
return false;
}
protected boolean hasJREInClassPath(IJavaProject javaProject) {
if (javaProject != null) {
try {
for (IClasspathEntry oldClasspath : javaProject.getRawClasspath()) {
if (isJREContainer(oldClasspath.getPath())) {
return true;
}
}
} catch (JavaModelException e) {
// do nothing
}
}
return false;
}
protected static String getAddClasspathLabel(IClasspathEntry entry, IPackageFragmentRoot root, IJavaProject project) {
switch (entry.getEntryKind()) {
case IClasspathEntry.CPE_LIBRARY:
if (root.isArchive()) {
String[] args= { JavaElementLabels.getElementLabel(root, JavaElementLabels.REFERENCED_ROOT_POST_QUALIFIED), BasicElementLabels.getJavaElementName(project.getElementName()) };
return Messages.format(CorrectionMessages.ReorgCorrectionsSubProcessor_addcp_archive_description, args);
} else {
String[] args= { JavaElementLabels.getElementLabel(root, JavaElementLabels.REFERENCED_ROOT_POST_QUALIFIED), BasicElementLabels.getJavaElementName(project.getElementName()) };
return Messages.format(CorrectionMessages.ReorgCorrectionsSubProcessor_addcp_classfolder_description, args);
}
case IClasspathEntry.CPE_VARIABLE: {
String[] args= { JavaElementLabels.getElementLabel(root, 0), BasicElementLabels.getJavaElementName(project.getElementName()) };
return Messages.format(CorrectionMessages.ReorgCorrectionsSubProcessor_addcp_variable_description, args);
}
case IClasspathEntry.CPE_CONTAINER:
try {
String[] args= { JavaElementLabels.getContainerEntryLabel(entry.getPath(), root.getJavaProject()), BasicElementLabels.getJavaElementName(project.getElementName()) };
return Messages.format(CorrectionMessages.ReorgCorrectionsSubProcessor_addcp_library_description, args);
} catch (JavaModelException e) {
// ignore
}
break;
default:
break;
}
return null;
}
}