org.eclipse.buckminster.jdt.internal.ClasspathEmitter.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.buckminster.jdt.internal.ClasspathEmitter.java

Source

/*******************************************************************************
 * Copyright (c) 2004, 2006
 * Thomas Hallgren, Kenneth Olwing, Mitch Sonies
 * Pontus Rydin, Nils Unden, Peer Torngren
 * The code, documentation and other materials contained herein have been
 * licensed under the Eclipse Public License - v 1.0 by the individual
 * copyright holders listed above, as Initial Contributors under such license.
 * The text of such license is available at www.eclipse.org.
 *******************************************************************************/
package org.eclipse.buckminster.jdt.internal;

import java.text.Format;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import org.eclipse.buckminster.core.build.PropertiesEmitter;
import org.eclipse.buckminster.core.helpers.ArrayUtils;
import org.eclipse.buckminster.jdt.Messages;
import org.eclipse.buckminster.runtime.Buckminster;
import org.eclipse.buckminster.runtime.BuckminsterException;
import org.eclipse.buckminster.runtime.Logger;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.AssertionFailedException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.ClasspathEntry;

/**
 * A Builder that emits the fully resolved classpath of the current project.
 * 
 * @author Thomas Hallgren
 */
@SuppressWarnings("restriction")
public class ClasspathEmitter extends PropertiesEmitter {
    public static final String ARG_FORMAT_CLASSPATH = "format.classpath"; //$NON-NLS-1$

    public static final String ARG_TARGET = "target"; //$NON-NLS-1$

    /**
     * Path separator. Defaults to setting of system property
     * "path.separator"
     */
    public static final String ARG_PATH_SEPARATOR = "path.separator"; //$NON-NLS-1$

    public static final Format FORMAT_CLASSPATH = new MessageFormat("bm.classpath"); //$NON-NLS-1$

    private static final String pathSeparator = System.getProperty("path.separator", ":"); //$NON-NLS-1$ //$NON-NLS-2$

    /**
     * Obtains the classpath that has been declared for the current project. The
     * classpath entries are resolved down to their absolute location in the
     * local file system. And empty list will be returned if the given project
     * is not represented by a <code>IJavaProject</code> in the java model.
     * 
     * @param project
     *            The project for which the classpath should be resolved.
     * @param target
     *            The target for which the classpath should be resolved or null
     *            if the current project classpath should be used.
     * @return A list of absolute paths in the local file system.
     * @throws CoreException
     */
    public static List<IPath> finalClasspathResolve(IProject project, String target) throws CoreException {
        IJavaModel model = JavaCore.create(ResourcesPlugin.getWorkspace().getRoot());

        HashSet<IPath> seenPaths = new HashSet<IPath>();
        HashSet<String> seenProjects = new HashSet<String>();
        ArrayList<IPath> finalClasspath = new ArrayList<IPath>();

        appendPaths(model, project, target, finalClasspath, seenPaths, seenProjects, true);
        return finalClasspath;
    }

    /**
     * Returns the default output folder relative to the project.
     * 
     * @param project
     * @return The folder or <code>null</code> if not applicable.
     * @throws CoreException
     */
    public static IPath getDefaultOutputFolder(IProject project) throws CoreException {
        String projectName = project.getName();
        IJavaModel model = JavaCore.create(ResourcesPlugin.getWorkspace().getRoot());
        IJavaProject javaProject = model.getJavaProject(projectName);
        if (javaProject == null || !javaProject.exists())
            return null;

        return javaProject.getOutputLocation().removeFirstSegments(1);
    }

    private static void appendPaths(IJavaModel model, IProject project, String target, List<IPath> path,
            HashSet<IPath> seenPaths, HashSet<String> seenProjects, boolean atTop) throws CoreException {
        Logger log = Buckminster.getLogger();
        String projectName = project.getName();
        if (seenProjects.contains(projectName))
            return;
        seenProjects.add(projectName);
        log.debug("Emitting classpath for project %s...", projectName); //$NON-NLS-1$

        IJavaProject javaProject = model.getJavaProject(projectName);
        IClasspathEntry[] entries;
        if (javaProject == null || !javaProject.exists()) {
            // The project may still be a component that exports jar files.
            //
            BMClasspathContainer container = new BMClasspathContainer(project, target);
            entries = container.getClasspathEntries();
            log.debug(" not a java project, contains %d entries", Integer.valueOf(entries.length)); //$NON-NLS-1$
        } else {
            entries = (atTop && target != null) ? changeClasspathForTarget(javaProject, target)
                    : javaProject.getResolvedClasspath(false);
            log.debug(" java project, contains %d entries", Integer.valueOf(entries.length)); //$NON-NLS-1$
        }

        for (IClasspathEntry entry : entries) {
            IPath entryPath;
            switch (entry.getEntryKind()) {
            case IClasspathEntry.CPE_LIBRARY:
                log.debug(" found library with path: %s", entry.getPath()); //$NON-NLS-1$
                if (!(atTop || entry.isExported())) {
                    log.debug(" skipping path %s. It's neither at top nor exported", entry.getPath()); //$NON-NLS-1$
                    continue;
                }

                entryPath = entry.getPath();
                break;
            case IClasspathEntry.CPE_SOURCE:
                entryPath = entry.getOutputLocation();
                if (entryPath == null) {
                    // Uses default output location
                    //
                    IJavaProject proj = model.getJavaProject(entry.getPath().segment(0));
                    if (proj == null)
                        continue;
                    entryPath = proj.getOutputLocation();
                }
                log.debug(" found source with path: %s", entryPath); //$NON-NLS-1$
                break;
            case IClasspathEntry.CPE_PROJECT:
                projectName = entry.getPath().segment(0);
                log.debug(" found project: %s", projectName); //$NON-NLS-1$
                if (!(atTop || entry.isExported())) {
                    log.debug(" skipping project %s. It's neither at top nor exported", projectName); //$NON-NLS-1$
                    continue;
                }

                IProject conProject = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
                appendPaths(model, conProject, null, path, seenPaths, seenProjects, false);
                continue;
            default:
                throw BuckminsterException.fromMessage(Messages.unexpected_classpath_entry_kind);
            }

            IResource folder = ResourcesPlugin.getWorkspace().getRoot().findMember(entryPath);
            if (folder != null) {
                log.debug(" path %s is inside workspace, switching to %s", entryPath, folder.getLocation()); //$NON-NLS-1$
                entryPath = folder.getLocation();
            }

            if (!seenPaths.contains(entryPath)) {
                seenPaths.add(entryPath);
                path.add(entryPath);
                log.debug(" path %s added", entryPath); //$NON-NLS-1$
            }
        }
    }

    /**
     * This method obtains the raw classpath from the javaProject and scans it
     * for BMClasspathContainer. The first one found is either kept if it
     * corresponds to the target or replaced if not. All other
     * BMClasspathContainers are removed. If no BMClasspathContainer was found,
     * a new one that represents the target is added first in the list. All
     * IClasspathEntry instances are then resolved.
     * 
     * @param javaProject
     * @param target
     * @return
     * @throws JavaModelException
     */
    private static IClasspathEntry[] changeClasspathForTarget(IJavaProject javaProject, String target)
            throws CoreException {
        boolean entriesChanged = false;
        boolean haveOtherBMCPs = false;
        boolean targetContainerInstalled = false;

        Logger log = Buckminster.getLogger();
        log.debug("Changing classpath for project %s into %s", javaProject.getProject().getName(), target); //$NON-NLS-1$
        IPath desiredContainer = BMClasspathContainer.PATH.append(target);
        IClasspathEntry[] rawEntries = javaProject.readRawClasspath();
        int top = rawEntries.length;
        for (int idx = 0; idx < top; ++idx) {
            IClasspathEntry rawEntry = rawEntries[idx];
            if (rawEntry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
                IPath entryPath = rawEntry.getPath();
                if (BMClasspathContainer.PATH.isPrefixOf(entryPath)) {
                    if (!targetContainerInstalled) {
                        if (!desiredContainer.equals(entryPath)) {
                            // This is not the desired container. Replace it.
                            //
                            rawEntries[idx] = JavaCore.newContainerEntry(desiredContainer);
                            entriesChanged = true;
                        }
                        targetContainerInstalled = true;
                    } else
                        haveOtherBMCPs = true;
                }
            }
        }

        if (targetContainerInstalled) {
            if (haveOtherBMCPs) {
                // Remove other Buckminster containers
                //
                ArrayList<IClasspathEntry> newEntries = new ArrayList<IClasspathEntry>(top);
                for (int idx = 0; idx < top; ++idx) {
                    IClasspathEntry rawEntry = rawEntries[idx];
                    if (rawEntry.getEntryKind() != IClasspathEntry.CPE_CONTAINER
                            || rawEntry.getPath().equals(desiredContainer))
                        newEntries.add(rawEntry);
                }
                rawEntries = newEntries.toArray(new IClasspathEntry[newEntries.size()]);
                entriesChanged = true;
            }
        } else {
            rawEntries = ArrayUtils.appendFirst(rawEntries,
                    new IClasspathEntry[] { JavaCore.newContainerEntry(desiredContainer) });
            entriesChanged = true;
        }

        log.debug(entriesChanged ? " changes detected" : " no changes detected"); //$NON-NLS-1$ //$NON-NLS-2$

        return entriesChanged ? getResolvedClasspath(javaProject, rawEntries)
                : javaProject.getResolvedClasspath(false);
    }

    private static IClasspathEntry[] getResolvedClasspath(IJavaProject project, IClasspathEntry[] entries)
            throws CoreException {
        ArrayList<IClasspathEntry> resolvedEntries = new ArrayList<IClasspathEntry>();
        for (IClasspathEntry rawEntry : entries) {
            switch (rawEntry.getEntryKind()) {
            case IClasspathEntry.CPE_VARIABLE:

                IClasspathEntry resolvedEntry = null;
                try {
                    resolvedEntry = JavaCore.getResolvedClasspathEntry(rawEntry);
                } catch (AssertionFailedException e) {
                }
                if (resolvedEntry == null)
                    throw new JavaModelException(
                            ClasspathEntry.validateClasspathEntry(project, rawEntry, false, false));
                break;

            case IClasspathEntry.CPE_CONTAINER:
                IPath entryPath = rawEntry.getPath();
                IClasspathContainer container;
                if (BMClasspathContainer.PATH.isPrefixOf(entryPath))
                    //
                    // This one is probably not in the project classpath
                    //
                    container = new BMClasspathContainer(project.getProject(),
                            entryPath.segmentCount() > 1 ? entryPath.lastSegment() : null);
                else
                    container = JavaCore.getClasspathContainer(entryPath, project);

                if (container == null)
                    throw new JavaModelException(
                            ClasspathEntry.validateClasspathEntry(project, rawEntry, false, false));

                IClasspathEntry[] containerEntries = container.getClasspathEntries();
                if (containerEntries == null)
                    continue;

                for (IClasspathEntry cEntry : containerEntries) {
                    // if container is exported or restricted, then its
                    // nested
                    // entries must in turn be exported
                    //
                    cEntry = ((ClasspathEntry) cEntry).combineWith((ClasspathEntry) rawEntry);
                    resolvedEntries.add(cEntry);
                }
                continue;
            }
            resolvedEntries.add(rawEntry);
        }
        return resolvedEntries.toArray(new IClasspathEntry[resolvedEntries.size()]);
    }

    @Override
    protected void addFormatters() {
        this.addFormat(ARG_FORMAT_CLASSPATH, FORMAT_CLASSPATH);
    }

    @Override
    protected void appendProperties() throws CoreException {
        IProject project = this.getProject();
        StringBuilder bld = new StringBuilder();
        String target = this.getArgument(ARG_TARGET);
        String pathSep = this.getArgument(ARG_PATH_SEPARATOR);
        if (pathSep == null)
            pathSep = pathSeparator;

        for (IPath location : finalClasspathResolve(project, target)) {
            if (bld.length() > 0)
                bld.append(pathSeparator);
            bld.append(this.formatPath(location));
        }
        this.addProperty(ARG_FORMAT_CLASSPATH, new String[] { project.getName(), target }, bld.toString());
    }
}