org.eclipse.pde.internal.ui.correction.java.FindClassResolutionsOperation.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.pde.internal.ui.correction.java.FindClassResolutionsOperation.java

Source

/*******************************************************************************
 * Copyright (c) 2008, 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.pde.internal.ui.correction.java;

import java.util.*;
import org.eclipse.core.resources.IProject;
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;
import org.eclipse.pde.core.plugin.PluginRegistry;
import org.eclipse.pde.internal.core.PDECore;

/**
 * This Operation is used to find possible resolutions to an unresolved class reference in a plug-in project.
 * When it is run, it will pass any ExportPackageDescriptions which provide the package to the AbstractClassResolutionCollector.
 * The AbstractClassResolutionCollector is responsible for creating the appropriate resolutions.
 *
 */
public class FindClassResolutionsOperation implements IRunnableWithProgress {

    String fClassName = null;
    IProject fProject = null;
    AbstractClassResolutionCollector fCollector = null;

    /**
     * This class is meant to be sub-classed for use with FindClassResolutionsOperation.  The subclass is responsible for creating 
     * corresponding proposals with the help of JavaResolutionFactory.
     * 
     * @see JavaResolutionFactory
     */
    public static abstract class AbstractClassResolutionCollector {

        /**
         * This method is meant to be sub-classed.  The subclass should decide if it wishes to create a proposals for either
         * Require-Bundle and/or Import-Package.  The proposals can be created with the help of the JavaResolutionFactory
         */
        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()) {
                IResource packageResource = aPackage.getResource();
                if (packageResource != null) {
                    return JavaResolutionFactory.createExportPackageProposal(packageResource.getProject(), aPackage,
                            JavaResolutionFactory.TYPE_JAVA_COMPLETION, 100);
                }
            }
            return null;
        }

        /**
         * Adds a require bundle proposal. Subclasses should implement the actual adding to the collection.
         */
        public Object addRequireBundleModification(IProject project, ExportPackageDescription desc, int relevance) {
            return JavaResolutionFactory.createRequireBundleProposal(project, desc,
                    JavaResolutionFactory.TYPE_JAVA_COMPLETION, relevance);
        }

        /**
         * Adds a search repositories proposal. Subclasses should implement the actual adding to the collection.
         */
        public Object addSearchRepositoriesModification(String packageName) {
            return JavaResolutionFactory.createSearchRepositoriesProposal(packageName);
        }

        /*
         * Optimization for case where users is only interested in Import-Package and therefore can quit after first dependency is found
         */
        public boolean isDone() {
            return false;
        }
    }

    /**
     * This class is used to try to find resolutions to unresolved java classes.  When either an Import-Package or Require-Bundle might 
     * resolve a class, the ExportPackageDescription which contains the package/bundle will be passed to the AbstractClassResoltuionCollector.
     * The collector is then responsible for creating an corresponding resolutions with the help of JavaResolutionFactory.
     * @param project the project which contains the unresolved class
     * @param className   the name of the class which is unresolved
     * @param collector a subclass of AbstractClassResolutionCollector to collect/handle possible resolutions 
     */
    public FindClassResolutionsOperation(IProject project, String className,
            AbstractClassResolutionCollector collector) {
        fProject = project;
        fClassName = className;
        fCollector = collector;
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.operation.IRunnableWithProgress#run(org.eclipse.core.runtime.IProgressMonitor)
     */
    public void run(IProgressMonitor monitor) {
        int idx = fClassName.lastIndexOf('.');
        String packageName = idx != -1 ? fClassName.substring(0, idx) : null;
        String typeName = fClassName.substring(idx + 1);
        if (typeName.length() == 1 && typeName.charAt(0) == '*') {
            typeName = null;
        }

        Set<IPackageFragment> packagesToExport = new HashSet<IPackageFragment>();
        Collection<ExportPackageDescription> validPackages = getValidPackages(typeName, packageName,
                packagesToExport, monitor);
        if (validPackages != null) {

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

            Iterator<ExportPackageDescription> validPackagesIter = validPackages.iterator();
            Set<ExportPackageDescription> visiblePkgs = null;
            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();
                }
                ExportPackageDescription currentPackage = validPackagesIter.next();
                // if package is already visible, skip over
                if (visiblePkgs.contains(currentPackage)) {
                    continue;
                }
                // if currentPackage will resolve class and is valid, pass it to collector
                fCollector.addResolutionModification(fProject, currentPackage);
            }

            // additionally add require bundle proposals
            Set<String> bundleNames = getCurrentBundleNames();
            for (validPackagesIter = validPackages.iterator(); validPackagesIter.hasNext();) {
                ExportPackageDescription currentPackage = validPackagesIter.next();
                BundleDescription desc = currentPackage.getExporter();
                // Ignore already required bundles and duplicate proposals (currently we do not consider version constraints)
                if (desc != null && !bundleNames.contains(desc.getName())) {
                    fCollector.addRequireBundleModification(fProject, currentPackage, 16);
                    bundleNames.add(desc.getName());
                }
            }
        }
    }

    private Collection<ExportPackageDescription> getValidPackages(String typeName, String packageName,
            Set<IPackageFragment> packagesToExport, IProgressMonitor monitor) {
        SubMonitor subMonitor = SubMonitor.convert(monitor, 3);

        Collection<ExportPackageDescription> validPackages = null;
        ImportPackageSpecification[] importPkgs = null;
        IPluginModelBase model = PluginRegistry.findModel(fProject);
        if (model != null && model.getBundleDescription() != null) {
            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<ExportPackageDescription> findValidPackagesContainingSimpleType(String aTypeName,
            ImportPackageSpecification[] importPkgs, Set<IPackageFragment> packagesToExport,
            IProgressMonitor monitor) {
        SubMonitor subMonitor = SubMonitor.convert(monitor);

        IPluginModelBase[] activeModels = PluginRegistry.getActiveModels();
        Set<IJavaProject> javaProjects = new HashSet<IJavaProject>(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);
                }
            }
        }
        final IJavaProject currentJavaProject = JavaCore.create(fProject);
        javaProjects.remove(currentJavaProject); // no need to search in current project itself

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

            final Map<String, IPackageFragment> packages = new HashMap<String, IPackageFragment>();
            SearchRequestor requestor = new SearchRequestor() {

                public void acceptSearchMatch(SearchMatch aMatch) throws CoreException {
                    Object element = aMatch.getElement();
                    if (element instanceof IType) {
                        IType type = (IType) element;
                        // Only try to import types we can access (Bug 406232)
                        if (Flags.isPublic(type.getFlags())) {
                            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<String, ExportPackageDescription> exportDescriptions = new HashMap<String, ExportPackageDescription>(
                        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.emptySet();
        } catch (CoreException ex) {
            // ignore, return an empty set
            return Collections.emptySet();
        }
    }

    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<ExportPackageDescription> getValidPackages(String pkgName) {
        ExportPackageDescription[] knownPackages = PDECore.getDefault().getModelManager().getState().getState()
                .getExportedPackages();
        Map<String, ExportPackageDescription> validPackages = new HashMap<String, ExportPackageDescription>();
        for (int i = 0; i < knownPackages.length; i++) {
            if (knownPackages[i].getName().equals(pkgName)) {
                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].getName());
            }
        }
        return validPackages.values();
    }

    private Set<ExportPackageDescription> getVisiblePackages() {
        IPluginModelBase base = PluginRegistry.findModel(fProject);
        if (base != null) {
            BundleDescription desc = base.getBundleDescription();

            StateHelper helper = Platform.getPlatformAdmin().getStateHelper();
            ExportPackageDescription[] visiblePkgs = helper.getVisiblePackages(desc);

            HashSet<ExportPackageDescription> set = new HashSet<ExportPackageDescription>();
            for (int i = 0; i < visiblePkgs.length; i++) {
                set.add(visiblePkgs[i]);
            }
            return set;
        }
        return Collections.emptySet();
    }

    /**
     * Returns the set of String bundle names that are in the project's list of required
     * bundles.
     * 
     * @return set of required bundle names, possibly empty
     */
    private Set<String> getCurrentBundleNames() {
        IPluginModelBase base = PluginRegistry.findModel(fProject);
        if (base != null) {
            Set<String> bundleNames = new HashSet<String>();
            BundleSpecification[] reqBundles = base.getBundleDescription().getRequiredBundles();
            for (int i = 0; i < reqBundles.length; i++) {
                bundleNames.add(reqBundles[i].getName());
            }
            return bundleNames;
        }
        return Collections.emptySet();

    }

}