Skip to content

Commit

Permalink
Support for import-package and export-package on unresolveable types
Browse files Browse the repository at this point in the history
Developing OSGI bundles with import/export package is currently a PITA.
You cannot use "Organize Imports" or Ctrl-Space to add imports,
because packages need to be exported (from the supplier) and imported
from the consumer first. This slows down development a lot.

This change extends an existing quickfix to be invokeable on
missing/unresolveable types. Additionally, if the type found cannot be
imported, a quickfix is provided to export the type first.
  • Loading branch information
cpfeiffer committed Jun 7, 2012
1 parent bf661d8 commit 2e19266
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 19 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,11 +95,20 @@ public void run(IProgressMonitor monitor) {
typeName = null;
}

if (packageName != null && !isImportedPackage(packageName)) {
Set validPackages = getValidPackages(packageName);
Set packagesToExport = new HashSet();
List validPackages = getValidPackages(typeName, packageName, packagesToExport, monitor);
if (validPackages != null) {
Iterator validPackagesIter = validPackages.iterator();
Set visiblePkgs = null;

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

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

private boolean isImportedPackage(String packageName) {
private List getValidPackages(String typeName, String packageName, Set packagesToExport, IProgressMonitor monitor) {
SubMonitor subMonitor = SubMonitor.convert(monitor, 3);

List validPackages = null;
ImportPackageSpecification[] importPkgs = null;
IPluginModelBase model = PluginRegistry.findModel(fProject.getProject());
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 = new ArrayList(getValidPackages(packageName));
}
subMonitor.worked(1);
} else {
// find possible types in the global packages
validPackages = new ArrayList(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 Set findValidPackagesContainingSimpleType(String aTypeName, ImportPackageSpecification[] importPkgs, Set packagesToExport, IProgressMonitor monitor) {
SubMonitor subMonitor = SubMonitor.convert(monitor);

IPluginModelBase[] activeModels = PluginRegistry.getActiveModels();
Set projects = 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()) {
projects.add(javaProject);
}
}
}

try {
IJavaSearchScope searchScope = SearchEngine.createJavaSearchScope((IJavaElement[]) projects.toArray(new IJavaElement[projects.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;
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);
new SearchEngine().search(typePattern, new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()}, searchScope, requestor, subMonitor.newChild(1));

if (!packages.isEmpty()) {
// transform to ExportPackageDescriptions
Set exportDescriptions = new HashSet(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.add(knownPackages[i]);
}
}
if (exportDescriptions.isEmpty()) {
// no packages to import found, maybe there are packages to export
packagesToExport.addAll(packages.values());
}

return exportDescriptions;
}

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

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;
}
// 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;
return false;
}

private static Set getValidPackages(String pkgName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ 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 :
handleImportNotFound(context, locations[i], results);

}
Expand Down Expand Up @@ -159,8 +161,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 +179,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 +198,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 +213,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 +245,9 @@ 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 :
IJavaElement parent = unit.getParent();
if (parent != null) {
IJavaProject project = parent.getJavaProject();
Expand Down

0 comments on commit 2e19266

Please sign in to comment.