com.google.gdt.eclipse.core.ClasspathUtilities.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gdt.eclipse.core.ClasspathUtilities.java

Source

/*******************************************************************************
 * Copyright 2011 Google Inc. All Rights Reserved.
 *
 * 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
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.google.gdt.eclipse.core;

import com.google.common.base.Predicate;
import com.google.gdt.eclipse.core.java.ClasspathChangedListener;

import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
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.internal.core.JavaProject;
import org.eclipse.jdt.internal.ui.actions.WorkbenchRunnableAdapter;
import org.eclipse.jdt.launching.IRuntimeClasspathEntry;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 
 */
@SuppressWarnings("restriction")
public final class ClasspathUtilities {

    /*
     * Implemented as a class to allow unit tests to mock out different class
     * loading scenarios.
     */
    /**
     * Finds classes in a class loader.
     */
    public static class ClassFinder {
        public boolean exists(ClassLoader classLoader, String className) {
            return find(classLoader, className) != null;
        }

        /**
         * @return the {@link Class} object for the specified <code>className</code>
         *         using the given {@link ClassLoader}, or null.
         */
        public Class<?> find(ClassLoader classLoader, String className) {
            try {
                return Class.forName(className, false, classLoader);
            } catch (ClassNotFoundException e) {
                return null;
            }
        }
    }

    private static final IClasspathEntry[] NO_ENTRIES = new IClasspathEntry[0];

    /**
     * Finds all the classpath containers in the specified project that match the
     * provided container ID.
     * 
     * @param javaProject the project to query
     * @param containerId The container ID we are trying to match.
     * @return an array of matching classpath containers.
     */
    public static IClasspathEntry[] findClasspathContainersWithContainerId(IJavaProject javaProject,
            final String containerId) throws JavaModelException {

        Predicate<IClasspathEntry> matchPredicate = new Predicate<IClasspathEntry>() {
            public boolean apply(IClasspathEntry entry) {
                if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
                    IPath containerPath = entry.getPath();
                    if (containerPath.segmentCount() > 0 && containerPath.segment(0).equals(containerId)) {
                        return true;
                    }
                }
                return false;
            }
        };

        IClasspathEntry[] classpathEntries = javaProject.getRawClasspath();
        int matchCount = 0;

        for (int i = 0; i < classpathEntries.length; i++) {
            if (matchPredicate.apply(classpathEntries[i])) {
                matchCount++;
            }
        }

        IClasspathEntry[] matchingClasspathEntries = new IClasspathEntry[matchCount];
        int matchingClasspathEntriesIdx = 0;
        for (int i = 0; i < classpathEntries.length; i++) {
            if (matchPredicate.apply(classpathEntries[i])) {
                matchingClasspathEntries[matchingClasspathEntriesIdx] = classpathEntries[i];
                matchingClasspathEntriesIdx++;
            }
        }

        return matchingClasspathEntries;
    }

    /**
     * Returns the {@link IClasspathEntry#CPE_CONTAINER} entry with the specified
     * container ID or <code>null</code> if one could not be found.
     * 
     * @param classpathEntries array of classpath entries
     * @param containerId container ID
     * @return {@link IClasspathEntry#CPE_CONTAINER} entry with the specified
     *         container ID or <code>null</code> if one could not be found
     */
    public static IClasspathEntry findClasspathEntryContainer(IClasspathEntry[] classpathEntries,
            String containerId) {
        int index = indexOfClasspathEntryContainer(classpathEntries, containerId);
        if (index >= 0) {
            return classpathEntries[index];
        }
        return null;
    }

    /**
     * Return the raw classpath entry on the project's classpath that contributes
     * the given type to the project.
     * 
     * @param javaProject The java project
     * @param fullyQualifiedName The fully-qualified type name
     * @return The raw classpath entry that contributes the type to the project,
     *         or <code>null</code> if no such classpath entry can be found.
     * @throws JavaModelException
     */
    public static IClasspathEntry findRawClasspathEntryFor(IJavaProject javaProject, String fullyQualifiedName)
            throws JavaModelException {
        IType type = javaProject.findType(fullyQualifiedName);
        if (type != null) {
            IPackageFragmentRoot packageFragmentRoot = (IPackageFragmentRoot) type
                    .getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);

            JavaProject jProject = (JavaProject) javaProject;

            IClasspathEntry[] rawClasspath = javaProject.getRawClasspath();
            for (IClasspathEntry rawClasspathEntry : rawClasspath) {
                IClasspathEntry[] resolvedClasspath = jProject
                        .resolveClasspath(new IClasspathEntry[] { rawClasspathEntry });
                IPackageFragmentRoot[] computePackageFragmentRoots = jProject
                        .computePackageFragmentRoots(resolvedClasspath, true, null);
                if (Arrays.asList(computePackageFragmentRoots).contains(packageFragmentRoot)) {
                    return rawClasspathEntry;
                }
            }

            return packageFragmentRoot.getRawClasspathEntry();
        }

        return null;
    }

    /**
     * Returns a String (in the format of the JVM classpath argument) which
     * contains the given classpath entries.
     * 
     * @param classpathEntries array of runtime classpath entries
     * @return flattened String of the given classpath entries in the format
     *         suitable for passing as a JVM argument
     */
    public static String flattenToClasspathString(List<IRuntimeClasspathEntry> classpathEntries) {
        StringBuilder sb = new StringBuilder();
        boolean needsSeparator = false;
        for (IRuntimeClasspathEntry r : classpathEntries) {
            if (needsSeparator) {
                sb.append(File.pathSeparatorChar);
            }
            needsSeparator = true;
            sb.append(r.getLocation());
        }

        return sb.toString();
    }

    public static IClasspathEntry getNullableRawClasspathEntryForPackageFragmentRoot(IPackageFragmentRoot root)
            throws JavaModelException {

        IClasspathEntry rawEntry = null;
        {
            JavaProject project = (JavaProject) root.getJavaProject();
            // force the reverse rawEntry cache to be populated
            project.getResolvedClasspath(true);
            @SuppressWarnings("rawtypes")
            Map rootPathToRawEntries = project.getPerProjectInfo().rootPathToRawEntries;
            if (rootPathToRawEntries != null) {
                rawEntry = (IClasspathEntry) rootPathToRawEntries.get(root.getPath());
            }
        }
        return rawEntry;
    }

    /**
     * Determines whether a ClasspathContainer exists on the provided project that
     * matches the provided container ID.
     * 
     * @param javaProject the project to query
     * @param containerId The container ID we are trying to match.
     * @return whether at least one classpath container exists that matches the
     *         provided ID.
     */
    public static boolean includesClasspathContainerWithContainerId(IJavaProject javaProject, String containerId)
            throws JavaModelException {
        IClasspathEntry[] classpathEntries = javaProject.getRawClasspath();
        int indexOfClasspathEntryContainer = ClasspathUtilities.indexOfClasspathEntryContainer(classpathEntries,
                containerId);
        return indexOfClasspathEntryContainer >= 0;
    }

    /**
     * Returns the first index of the specified
     * {@link IClasspathEntry#CPE_CONTAINER} entry with the specified container ID
     * or -1 if one could not be found.
     * 
     * @param classpathEntries array of classpath entries
     * @param containerId container ID
     * @return index of the specified {@link IClasspathEntry#CPE_CONTAINER} entry
     *         with the specified container ID or -1
     */
    public static int indexOfClasspathEntryContainer(IClasspathEntry[] classpathEntries, String containerId) {
        for (int i = 0; i < classpathEntries.length; ++i) {
            IClasspathEntry classpathEntry = classpathEntries[i];
            if (classpathEntry.getEntryKind() != IClasspathEntry.CPE_CONTAINER) {
                // Skip anything that is not a container
                continue;
            }

            IPath containerPath = classpathEntry.getPath();
            if (containerPath.segmentCount() > 0 && containerPath.segment(0).equals(containerId)) {
                return i;
            }
        }

        return -1;
    }

    /**
     * Replace an {@link IClasspathEntry#CPE_CONTAINER} entry with the given
     * container ID, with its corresponding resolved classpath entries.
     * 
     * @param javaProject java project
     * @param containerId container ID to replace
     * @return true if a container was replaced
     * 
     * @throws JavaModelException
     */
    public static boolean replaceContainerWithClasspathEntries(IJavaProject javaProject, String containerId)
            throws JavaModelException {
        IClasspathEntry[] classpathEntries = javaProject.getRawClasspath();
        int containerIndex = ClasspathUtilities.indexOfClasspathEntryContainer(classpathEntries, containerId);
        if (containerIndex != -1) {
            List<IClasspathEntry> newClasspathEntries = new ArrayList<IClasspathEntry>(
                    Arrays.asList(classpathEntries));
            IClasspathEntry classpathContainerEntry = newClasspathEntries.remove(containerIndex);
            IClasspathContainer classpathContainer = JavaCore
                    .getClasspathContainer(classpathContainerEntry.getPath(), javaProject);
            if (classpathContainer != null) {
                newClasspathEntries.addAll(containerIndex, Arrays.asList(classpathContainer.getClasspathEntries()));
                ClasspathUtilities.setRawClasspath(javaProject, newClasspathEntries);
                return true;
            }
        }
        return false;
    }

    /**
     * Replaces a project's classpath container entry with a new one or appends it
     * to the classpath if none were found.
     * 
     * @param javaProject The target project
     * @param containerId the identifier of the classpath container type
     * @param newContainerPath the path for the new classpath. Note: this path
     *          should be partial, not including the initial segment which should
     *          in all cases be the value in containerId
     * @throws JavaModelException thrown by the call to getRawClasspath()
     */
    public static void replaceOrAppendContainer(IJavaProject javaProject, String containerId,
            IPath newContainerPath, IProgressMonitor monitor) throws JavaModelException {
        IClasspathEntry[] classpathEntries = javaProject.getRawClasspath();
        int indexOfClasspathEntryContainer = ClasspathUtilities.indexOfClasspathEntryContainer(classpathEntries,
                containerId);
        IClasspathEntry newContainer = JavaCore.newContainerEntry(new Path(containerId).append(newContainerPath));
        List<IClasspathEntry> newClasspathEntries = new ArrayList<IClasspathEntry>(Arrays.asList(classpathEntries));
        if (indexOfClasspathEntryContainer >= 0) {
            // Replace the entry
            newClasspathEntries.set(indexOfClasspathEntryContainer, newContainer);
        } else {
            // Append the entry
            newClasspathEntries.add(newContainer);
        }

        javaProject.setRawClasspath(newClasspathEntries.toArray(new IClasspathEntry[newClasspathEntries.size()]),
                monitor);
    }

    /**
     * Use this method to set the raw classpath of an IJavaProject. This method
     * should be used in favor of IJavaProject.setRawClasspath(IJavaProject,
     * IClasspathEntry [], IProgressMonitor) due to an odd interaction with
     * certain source control systems, such as Perforce. See the following URL for
     * more information: <a
     * href="http://bugs.eclipse.org/bugs/show_bug.cgi?id=243692"
     * >http://bugs.eclipse.org/bugs/show_bug.cgi?id=243692</a>
     * 
     * <p>
     * Note that this method is asynchronous, so the caller will regain control
     * immediately, and the raw classpath will be set at some future time. Right
     * now, there is no way to tell the caller when the operation has completed.
     * If this becomes a concern in the future, a callback parameter can be
     * introduced.
     * </p>
     * 
     * <p>
     * This method does not accept an IProgressMonitor, unlike the equivalent
     * method in IJavaProject, because there is an implicit progress monitor
     * provided when running the setRawClasspath operation as a task. In the
     * future, this method could be modified to accept a user-specified progress
     * monitor.
     * </p>
     * 
     * NOTE: If you are already running in a job, you probably don't want to call
     * this method.
     */
    public static void setRawClasspath(final IJavaProject javaProject,
            final IClasspathEntry[] newClasspathEntries) {

        IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
            public void run(IProgressMonitor monitor) throws CoreException, OperationCanceledException {
                javaProject.setRawClasspath(newClasspathEntries, monitor);
            }
        };

        WorkbenchRunnableAdapter op = new WorkbenchRunnableAdapter(runnable);
        op.runAsUserJob("Updating classpath of Java project '" + javaProject.getProject().getName() + "'", null);
    }

    /**
     * Use this method to set the raw classpath of an IJavaProject. This method
     * should be used in favor of IJavaProject.setRawClasspath(IJavaProject,
     * IClasspathEntry [], IProgressMonitor) due to an odd interaction with
     * certain source control systems, such as Perforce. See the following URL for
     * more information: <a
     * href="http://bugs.eclipse.org/bugs/show_bug.cgi?id=243692"
     * >http://bugs.eclipse.org/bugs/show_bug.cgi?id=243692</a>
     * 
     * <p>
     * Note that this method is asynchronous, so the caller will regain control
     * immediately, and the raw classpath will be set at some future time. Right
     * now, there is no way to tell the caller when the operation has completed.
     * If this becomes a concern in the future, a callback parameter can be
     * introduced.
     * </p>
     * 
     * <p>
     * This method does not accept an IProgressMonitor, unlike the equivalent
     * method in IJavaProject, because there is an implicit progress monitor
     * provided when running the setRawClasspath operation as a task. In the
     * future, this method could be modified to accept a user-specified progress
     * monitor.
     * </p>
     */
    public static void setRawClasspath(final IJavaProject javaProject, List<IClasspathEntry> classpathEntries) {
        setRawClasspath(javaProject, classpathEntries.toArray(NO_ENTRIES));
    }

    /**
     * Waits indefinitely until the given classpath entries are on the given
     * project's raw classpath.
     * 
     * @throws JavaModelException
     * @throws InterruptedException
     */
    public static void waitUntilEntriesAreOnClasspath(final IJavaProject javaProject,
            final List<IClasspathEntry> classpathEntries) throws JavaModelException, InterruptedException {

        // Used to notify when we are finished -- either all entries are on the
        // classpath or the anon class had an exception
        final Object finished = new Object();
        final JavaModelException[] anonClassException = new JavaModelException[1];

        ClasspathChangedListener listener = new ClasspathChangedListener() {
            @Override
            protected void classpathChanged(IJavaProject curJavaProject) {
                synchronized (finished) {
                    try {
                        if (curJavaProject.equals(javaProject)
                                && areEntriesOnClasspath(javaProject, classpathEntries)) {
                            finished.notifyAll();
                        }
                    } catch (JavaModelException e) {
                        anonClassException[0] = e;
                        finished.notifyAll();
                    }
                }
            }
        };

        synchronized (finished) {
            JavaCore.addElementChangedListener(listener);

            try {
                // We're in a state where either the entries already exist on the
                // classpath, or we have a callback queued up (because of the
                // synchronization on finished) that will notify us when they are added.
                while (!areEntriesOnClasspath(javaProject, classpathEntries)) {
                    finished.wait();
                }

                if (anonClassException[0] != null) {
                    throw anonClassException[0];
                }
            } finally {
                JavaCore.removeElementChangedListener(listener);
            }
        }
    }

    private static boolean areEntriesOnClasspath(IJavaProject javaProject, List<IClasspathEntry> classpathEntries)
            throws JavaModelException {
        List<IClasspathEntry> projectClasspath = Arrays.asList(javaProject.getRawClasspath());

        for (IClasspathEntry entry : classpathEntries) {
            if (projectClasspath.indexOf(entry) == -1) {
                return false;
            }
        }

        return true;
    }

    private ClasspathUtilities() {
    }
}