Skip to content

Commit

Permalink
Improved Import-Package QuickFix
Browse files Browse the repository at this point in the history
Made the quickfix work not only for import-statements, but also for
method-invocatoins, field access and type references.
  • Loading branch information
cpfeiffer committed Jun 28, 2012
1 parent 9004975 commit c63ace8
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@

import java.util.*;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.search.*;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.osgi.service.resolver.*;
import org.eclipse.pde.core.plugin.IPluginModelBase;
Expand Down Expand Up @@ -48,6 +50,16 @@ public static abstract class AbstractClassResolutionCollector {
*/
abstract public void addResolutionModification(IProject project, ExportPackageDescription desc);

/**
* Adds an export package proposal. Subclasses should implement the actual adding to the collection.
*/
public Object addExportPackageResolutionModification(IPackageFragment aPackage) {
if (aPackage.exists()) {
return JavaResolutionFactory.createExportPackageProposal(aPackage.getResource().getProject(), aPackage, JavaResolutionFactory.TYPE_JAVA_COMPLETION, 100);
}
return null;
}

/*
* Optimization for case where users is only interested in Import-Package and therefore can quit after first dependency is found
*/
Expand Down Expand Up @@ -83,12 +95,22 @@ public void run(IProgressMonitor monitor) {
typeName = null;
}

if (packageName != null && !isImportedPackage(packageName)) {
Set validPackages = getValidPackages(packageName);
Set packagesToExport = new HashSet();
Collection validPackages = getValidPackages(typeName, packageName, packagesToExport, monitor);
if (validPackages != null) {

if (validPackages.isEmpty()) {
for (Iterator it = packagesToExport.iterator(); it.hasNext();) {
IPackageFragment packageFragment = (IPackageFragment) it.next();
fCollector.addExportPackageResolutionModification(packageFragment);
}
return;
}

Iterator validPackagesIter = validPackages.iterator();
Set visiblePkgs = null;

while (validPackagesIter.hasNext() && !fCollector.isDone()) {
boolean allowMultipleFixes = packageName == null;
while (validPackagesIter.hasNext() && (allowMultipleFixes || !fCollector.isDone())) {
// since getting visible packages is not very efficient, only do it once and cache result
if (visiblePkgs == null) {
visiblePkgs = getVisiblePackages();
Expand All @@ -104,37 +126,147 @@ public void run(IProgressMonitor monitor) {
}
}

private boolean isImportedPackage(String packageName) {
IPluginModelBase model = PluginRegistry.findModel(fProject.getProject());
private Collection getValidPackages(String typeName, String packageName, Set packagesToExport, IProgressMonitor monitor) {
SubMonitor subMonitor = SubMonitor.convert(monitor, 3);

Collection validPackages = null;
ImportPackageSpecification[] importPkgs = null;
IPluginModelBase model = PluginRegistry.findModel(fProject);
if (model != null && model.getBundleDescription() != null) {
ImportPackageSpecification[] importPkgs = model.getBundleDescription().getImportPackages();
for (int i = 0; i < importPkgs.length; i++) {
if (importPkgs[i].getName().equals(packageName)) {
return true;
importPkgs = model.getBundleDescription().getImportPackages();
}
subMonitor.worked(1);

if (importPkgs != null) {
if (packageName != null) {
if (!isImportedPackage(packageName, importPkgs)) {
validPackages = getValidPackages(packageName);
}
subMonitor.worked(1);
} else {
// find possible types in the global packages
validPackages = findValidPackagesContainingSimpleType(typeName, importPkgs, packagesToExport, subMonitor.newChild(1));
}
}
return validPackages;
}

/**
* Finds all exported packages containing the simple type aTypeName. The packages
* will be filtered from the given packages which are already imported, and all
* system packages.
*
* If no exported package is left, packagesToExport will be filled with those
* packages that would have been returned, if they were exported.
* @param aTypeName the simple type to search for
* @param importPkgs the packages which are already imported
* @param packagesToExport return parameter that will be filled with packages to export
* if no valid package to import was found
* @param monitor
* @return the set of packages to import
*/
private Collection findValidPackagesContainingSimpleType(String aTypeName, ImportPackageSpecification[] importPkgs, Set packagesToExport, IProgressMonitor monitor) {
SubMonitor subMonitor = SubMonitor.convert(monitor);

IPluginModelBase[] activeModels = PluginRegistry.getActiveModels();
Set javaProjects = new HashSet(activeModels.length * 2);

for (int i = 0; i < activeModels.length; i++) {
IResource resource = activeModels[i].getUnderlyingResource();
if (resource != null && resource.isAccessible()) {
IJavaProject javaProject = JavaCore.create(resource.getProject());
if (javaProject.exists()) {
javaProjects.add(javaProject);
}
}
return false;
}
// if no BundleDescription, we return true so we don't create any proposals. This is the safe way out if no BundleDescription is available.
return true;
final IJavaProject currentJavaProject = JavaCore.create(fProject);
javaProjects.remove(currentJavaProject); // no need to search in current project itself

try {
IJavaSearchScope searchScope = SearchEngine.createJavaSearchScope((IJavaElement[]) javaProjects.toArray(new IJavaElement[javaProjects.size()]));

final Map packages = new HashMap();
SearchRequestor requestor = new SearchRequestor() {

public void acceptSearchMatch(SearchMatch aMatch) throws CoreException {
Object element = aMatch.getElement();
if (element instanceof IType) {
IType type = (IType) element;
if (!currentJavaProject.equals(type.getJavaProject())) {
IPackageFragment packageFragment = type.getPackageFragment();
if (packageFragment.exists()) {
packages.put(packageFragment.getElementName(), packageFragment);
}
}
}
}
};

SearchPattern typePattern = SearchPattern.createPattern(aTypeName, IJavaSearchConstants.TYPE, IJavaSearchConstants.DECLARATIONS, SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE);
new SearchEngine().search(typePattern, new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()}, searchScope, requestor, subMonitor.newChild(1));

if (!packages.isEmpty()) {
// transform to ExportPackageDescriptions
Map exportDescriptions = new HashMap(packages.size());

// remove system packages if they happen to be included. Adding a system package won't resolve anything, since package package already comes from JRE
ExportPackageDescription[] systemPackages = PDECore.getDefault().getModelManager().getState().getState().getSystemPackages();
for (int i = 0; i < systemPackages.length; i++) {
packages.remove(systemPackages[i].getName());
}
// also remove packages that are already imported
for (int i = 0; i < importPkgs.length; i++) {
packages.remove(importPkgs[i].getName());
}

// finally create the list of ExportPackageDescriptions
ExportPackageDescription[] knownPackages = PDECore.getDefault().getModelManager().getState().getState().getExportedPackages();
for (int i = 0; i < knownPackages.length; i++) {
if (packages.containsKey(knownPackages[i].getName())) {
exportDescriptions.put(knownPackages[i].getName(), knownPackages[i]);
}
}
if (exportDescriptions.isEmpty()) {
// no packages to import found, maybe there are packages to export
packagesToExport.addAll(packages.values());
}

return exportDescriptions.values();
}

return Collections.EMPTY_SET;
} catch (CoreException ex) {
// ignore, return an empty set
return Collections.EMPTY_SET;
}
}

private static Set getValidPackages(String pkgName) {
private boolean isImportedPackage(String packageName, ImportPackageSpecification[] importPkgs) {
for (int i = 0; i < importPkgs.length; i++) {
if (importPkgs[i].getName().equals(packageName)) {
return true;
}
}
return false;
}

private static Collection getValidPackages(String pkgName) {
ExportPackageDescription[] knownPackages = PDECore.getDefault().getModelManager().getState().getState().getExportedPackages();
Set validPackages = new HashSet();
Map validPackages = new HashMap();
for (int i = 0; i < knownPackages.length; i++) {
if (knownPackages[i].getName().equals(pkgName)) {
validPackages.add(knownPackages[i]);
validPackages.put(knownPackages[i].getName(), knownPackages[i]);
}
}
// remove system packages if they happen to be included. Adding a system package won't resolve anything, since package package already comes from JRE
if (!validPackages.isEmpty()) {
knownPackages = PDECore.getDefault().getModelManager().getState().getState().getSystemPackages();
for (int i = 0; i < knownPackages.length; i++) {
validPackages.remove(knownPackages[i]);
validPackages.remove(knownPackages[i].getName());
}
}
return validPackages;
return validPackages.values();
}

private Set getVisiblePackages() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ public IJavaCompletionProposal[] getCorrections(IInvocationContext context, IPro
switch (id) {
case IProblem.ForbiddenReference :
handleAccessRestrictionProblem(context, locations[i], results);
case IProblem.ImportNotFound :
case IProblem.ImportNotFound : // fall through
case IProblem.UndefinedName : // fall through
case IProblem.UndefinedType : // fall through
case IProblem.UnresolvedVariable :
handleImportNotFound(context, locations[i], results);

}
Expand All @@ -56,6 +59,16 @@ private void handleAccessRestrictionProblem(IInvocationContext context, IProblem
referencedElement = ((Type) node).resolveBinding();
} else if (node instanceof Name) {
referencedElement = ((Name) node).resolveBinding();
} else if (node instanceof MethodInvocation) {
IMethodBinding tempMethod = ((MethodInvocation) node).resolveMethodBinding();
if (tempMethod != null) {
referencedElement = tempMethod.getDeclaringClass();
}
} else if (node instanceof FieldAccess) {
IVariableBinding tempVariable = ((FieldAccess) node).resolveFieldBinding();
if (tempVariable != null) {
referencedElement = tempVariable.getDeclaringClass();
}
}
if (referencedElement != null) {
// get the project that contains the reference element
Expand Down Expand Up @@ -139,7 +152,24 @@ private void handleAccessRestrictionByImportPackage(IProject currentProject, Exp
}
}
if (!supplierImported) {
Object proposal = JavaResolutionFactory.createRequireBundleProposal(currentProject, desc, JavaResolutionFactory.TYPE_JAVA_COMPLETION, 16);
// add import-package, if possible
boolean proposeRequireBundle = false;

ImportPackageSpecification[] importPackages = bd.getImportPackages();
for (int i = 0; i < importPackages.length; i++) {
if (desc.getName().equals(importPackages[i].getName())) {
// already imported, try require-bundle
proposeRequireBundle = true;
break;
}
}

Object proposal = null;
if (proposeRequireBundle) {
proposal = JavaResolutionFactory.createRequireBundleProposal(currentProject, desc, JavaResolutionFactory.TYPE_JAVA_COMPLETION, 16);
} else {
proposal = JavaResolutionFactory.createImportPackageProposal(currentProject, desc, JavaResolutionFactory.TYPE_JAVA_COMPLETION, 16);
}
if (proposal != null)
results.add(proposal);
}
Expand All @@ -159,8 +189,13 @@ private void handleImportNotFound(IInvocationContext context, IProblemLocation p
if (node == null) {
if (selectedNode instanceof SimpleName) {
ITypeBinding typeBinding = ((SimpleName) selectedNode).resolveTypeBinding();
className = typeBinding.getBinaryName();
packageName = typeBinding.getPackage().getName();
if (typeBinding != null) {
className = typeBinding.getBinaryName();
packageName = typeBinding.getPackage().getName();
}
if (className == null) { // fallback if the type cannot be resolved
className = ((SimpleName) selectedNode).getIdentifier();
}
}
} else if (node instanceof ImportDeclaration) {
// Find import declaration which is the problem
Expand All @@ -172,7 +207,7 @@ private void handleImportNotFound(IInvocationContext context, IProblemLocation p
result.add(JavaResolutionFactory.createSearchRepositoriesProposal(packageName));
}

if (className != null && packageName != null) {
if (className != null) {
IProject project = cu.getJavaElement().getJavaProject().getProject();
// only try to find proposals on Plug-in Projects
if (!WorkspaceModelManager.isPluginProject(project))
Expand All @@ -191,7 +226,7 @@ private void handleImportNotFound(IInvocationContext context, IProblemLocation p
}

/*
* Custom AbstractClassResolutionCollector which will only add one IJavaCompletionProposal for adding an Import-Package entry
* Custom AbstractClassResolutionCollector which will only add one IJavaCompletionProposal for adding an Import-Package or Export-Package entry
*/
private AbstractClassResolutionCollector createCollector(final Collection result) {
return new AbstractClassResolutionCollector() {
Expand All @@ -206,7 +241,16 @@ public void addResolutionModification(IProject project, ExportPackageDescription
}
}

// we want to finish after we add the first Import-Package Change
public Object addExportPackageResolutionModification(IPackageFragment aPackage) {
Object proposal = super.addExportPackageResolutionModification(aPackage);
if (proposal != null) {
result.add(proposal);
isDone = true;
}
return proposal;
}

// we want to finish after we add the first Import- or Export-Package Change
public boolean isDone() {
return isDone;
}
Expand All @@ -229,7 +273,10 @@ private static ASTNode getParent(ASTNode node) {
public boolean hasCorrections(ICompilationUnit unit, int problemId) {
switch (problemId) {
case IProblem.ForbiddenReference :
case IProblem.ImportNotFound :
case IProblem.UndefinedName : // fall through
case IProblem.ImportNotFound : // fall through
case IProblem.UndefinedType :
case IProblem.UnresolvedVariable :
IJavaElement parent = unit.getParent();
if (parent != null) {
IJavaProject project = parent.getJavaProject();
Expand Down

0 comments on commit c63ace8

Please sign in to comment.