/******************************************************************************* * 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()); } }