/*******************************************************************************
* Copyright (c) 2000, 2006 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.rubypeople.rdt.internal.launching;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.rubypeople.rdt.core.ILoadpathContainer;
import org.rubypeople.rdt.core.ILoadpathEntry;
import org.rubypeople.rdt.core.IRubyProject;
import org.rubypeople.rdt.core.LoadpathContainerInitializer;
import org.rubypeople.rdt.core.RubyCore;
import org.rubypeople.rdt.launching.IRuntimeContainerComparator;
import org.rubypeople.rdt.launching.IRuntimeLoadpathEntry;
import org.rubypeople.rdt.launching.RubyRuntime;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Default user classpath entries for a Ruby project
*/
public class DefaultProjectLoadpathEntry extends AbstractRuntimeLoadpathEntry {
public static final String TYPE_ID = "org.eclipse.jdt.launching.classpathentry.defaultLoadpath"; //$NON-NLS-1$
/**
* Whether only exported entries should be on the runtime classpath.
* By default all entries are on the runtime classpath.
*/
private boolean fExportedEntriesOnly = false;
/**
* Default constructor need to instantiate extensions
*/
public DefaultProjectLoadpathEntry() {
}
/**
* Constructs a new classpath entry for the given project.
*
* @param project Ruby project
*/
public DefaultProjectLoadpathEntry(IRubyProject project) {
setRubyProject(project);
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.launching.AbstractRuntimeLoadpathEntry#buildMemento(org.w3c.dom.Document, org.w3c.dom.Element)
*/
protected void buildMemento(Document document, Element memento) throws CoreException {
memento.setAttribute("project", getRubyProject().getElementName()); //$NON-NLS-1$
memento.setAttribute("exportedEntriesOnly", Boolean.toString(fExportedEntriesOnly)); //$NON-NLS-1$
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.IRuntimeLoadpathEntry2#initializeFrom(org.w3c.dom.Element)
*/
public void initializeFrom(Element memento) throws CoreException {
String name = memento.getAttribute("project"); //$NON-NLS-1$
if (name == null) {
abort(LaunchingMessages.DefaultProjectLoadpathEntry_3, null);
}
IRubyProject project = RubyCore.create(ResourcesPlugin.getWorkspace().getRoot().getProject(name));
setRubyProject(project);
name = memento.getAttribute("exportedEntriesOnly"); //$NON-NLS-1$
if (name == null) {
fExportedEntriesOnly = false;
} else {
fExportedEntriesOnly = Boolean.valueOf(name).booleanValue();
}
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.IRuntimeLoadpathEntry2#getTypeId()
*/
public String getTypeId() {
return TYPE_ID;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.IRuntimeLoadpathEntry#getType()
*/
public int getType() {
return OTHER;
}
protected IProject getProject() {
return getRubyProject().getProject();
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.IRuntimeLoadpathEntry#getLocation()
*/
public String getLocation() {
return getProject().getLocation().toOSString();
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.IRuntimeLoadpathEntry#getPath()
*/
public IPath getPath() {
return getProject().getFullPath();
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.IRuntimeLoadpathEntry#getResource()
*/
public IResource getResource() {
return getProject();
}
/* (non-Javadoc)
* @see org.rubypeople.rdt.launching.IRuntimeLoadpathEntry2#getRuntimeLoadpathEntries(org.eclipse.debug.core.ILaunchConfiguration)
*/
public IRuntimeLoadpathEntry[] getRuntimeLoadpathEntries(ILaunchConfiguration configuration) throws CoreException {
ILoadpathEntry entry = RubyCore.newProjectEntry(getRubyProject().getProject().getFullPath());
List classpathEntries = new ArrayList(5);
List<ILoadpathEntry> expanding = new ArrayList<ILoadpathEntry>(5);
expandProject(entry, classpathEntries, expanding);
IRuntimeLoadpathEntry[] runtimeEntries = new IRuntimeLoadpathEntry[classpathEntries.size()];
for (int i = 0; i < runtimeEntries.length; i++) {
Object e = classpathEntries.get(i);
if (e instanceof ILoadpathEntry) {
ILoadpathEntry cpe = (ILoadpathEntry)e;
runtimeEntries[i] = new RuntimeLoadpathEntry(cpe);
} else {
runtimeEntries[i] = (IRuntimeLoadpathEntry)e;
}
}
// remove bootpath entries - this is a default user loadpath
List<IRuntimeLoadpathEntry> ordered = new ArrayList<IRuntimeLoadpathEntry>(runtimeEntries.length);
for (int i = 0; i < runtimeEntries.length; i++) {
if (runtimeEntries[i].getLoadpathProperty() == IRuntimeLoadpathEntry.USER_CLASSES) {
ordered.add(runtimeEntries[i]);
}
}
return ordered.toArray(new IRuntimeLoadpathEntry[ordered.size()]);
}
/**
* Returns the transitive closure of classpath entries for the
* given project entry.
*
* @param projectEntry project classpath entry
* @param expandedPath a list of entries already expanded, should be empty
* to begin, and contains the result
* @param expanding a list of projects that have been or are currently being
* expanded (to detect cycles)
* @exception CoreException if unable to expand the classpath
*/
private void expandProject(ILoadpathEntry projectEntry, List expandedPath, List<ILoadpathEntry> expanding) throws CoreException {
expanding.add(projectEntry);
// 1. Get the raw classpath
// 2. Replace source folder entries with a project entry
IPath projectPath = projectEntry.getPath();
IResource res = ResourcesPlugin.getWorkspace().getRoot().findMember(projectPath.lastSegment());
if (res == null) {
// add project entry and return
expandedPath.add(projectEntry);
return;
}
IRubyProject project = (IRubyProject)RubyCore.create(res);
if (project == null || !project.getProject().isOpen() || !project.exists()) {
// add project entry and return
expandedPath.add(projectEntry);
return;
}
ILoadpathEntry[] buildPath = project.getRawLoadpath();
List unexpandedPath = new ArrayList(buildPath.length);
boolean projectAdded = false;
for (int i = 0; i < buildPath.length; i++) {
ILoadpathEntry classpathEntry = buildPath[i];
if (classpathEntry.getEntryKind() == ILoadpathEntry.CPE_SOURCE)
{
if (!projectAdded && classpathEntry.getPath().equals(projectEntry.getPath()))
{
projectAdded = true;
unexpandedPath.add(projectEntry);
continue;
}
}
// add exported entires, as configured
if (classpathEntry.isExported()) {
unexpandedPath.add(classpathEntry);
} else if (!isExportedEntriesOnly() || project.equals(getRubyProject())) {
// add non exported entries from root project or if we are including all entries
unexpandedPath.add(classpathEntry);
}
}
// 3. expand each project entry (except for the root project)
// 4. replace each container entry with a runtime entry associated with the project
Iterator iter = unexpandedPath.iterator();
while (iter.hasNext()) {
ILoadpathEntry entry = (ILoadpathEntry)iter.next();
if (entry == projectEntry) {
expandedPath.add(entry);
} else {
switch (entry.getEntryKind()) {
case ILoadpathEntry.CPE_PROJECT:
if (!expanding.contains(entry)) {
expandProject(entry, expandedPath, expanding);
}
break;
case ILoadpathEntry.CPE_CONTAINER:
ILoadpathContainer container = RubyCore.getLoadpathContainer(entry.getPath(), project);
int property = -1;
if (container != null) {
switch (container.getKind()) {
case ILoadpathContainer.K_APPLICATION:
property = IRuntimeLoadpathEntry.USER_CLASSES;
break;
case ILoadpathContainer.K_DEFAULT_SYSTEM:
property = IRuntimeLoadpathEntry.STANDARD_CLASSES;
break;
case ILoadpathContainer.K_SYSTEM:
property = IRuntimeLoadpathEntry.BOOTSTRAP_CLASSES;
break;
}
IRuntimeLoadpathEntry r = RubyRuntime.newRuntimeContainerLoadpathEntry(entry.getPath(), property, project);
// check for duplicate/redundant entries
boolean duplicate = false;
LoadpathContainerInitializer initializer = RubyCore.getLoadpathContainerInitializer(r.getPath().segment(0));
for (int i = 0; i < expandedPath.size(); i++) {
Object o = expandedPath.get(i);
if (o instanceof IRuntimeLoadpathEntry) {
IRuntimeLoadpathEntry re = (IRuntimeLoadpathEntry)o;
if (re.getType() == IRuntimeLoadpathEntry.CONTAINER) {
if (container instanceof IRuntimeContainerComparator) {
duplicate = ((IRuntimeContainerComparator)container).isDuplicate(re.getPath());
} else {
LoadpathContainerInitializer initializer2 = RubyCore.getLoadpathContainerInitializer(re.getPath().segment(0));
Object id1 = null;
Object id2 = null;
if (initializer == null) {
id1 = r.getPath().segment(0);
} else {
id1 = initializer.getComparisonID(r.getPath(), project);
}
if (initializer2 == null) {
id2 = re.getPath().segment(0);
} else {
IRubyProject context = re.getRubyProject();
if (context == null) {
context = project;
}
id2 = initializer2.getComparisonID(re.getPath(), context);
}
if (id1 == null) {
duplicate = id2 == null;
} else {
duplicate = id1.equals(id2);
}
}
if (duplicate) {
break;
}
}
}
}
if (!duplicate) {
expandedPath.add(r);
}
}
break;
case ILoadpathEntry.CPE_VARIABLE:
if (entry.getPath().segment(0).equals(RubyRuntime.RUBYLIB_VARIABLE)) {
IRuntimeLoadpathEntry r = RubyRuntime.newVariableRuntimeLoadpathEntry(entry.getPath());
r.setLoadpathProperty(IRuntimeLoadpathEntry.STANDARD_CLASSES);
if (!expandedPath.contains(r)) {
expandedPath.add(r);
}
break;
}
// fall through if not the special RUBYLIB variable
default:
if (!expandedPath.contains(entry)) {
expandedPath.add(entry);
}
break;
}
}
}
return;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.IRuntimeLoadpathEntry2#isComposite()
*/
public boolean isComposite() {
return true;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.IRuntimeLoadpathEntry2#getName()
*/
public String getName() {
if (isExportedEntriesOnly()) {
return MessageFormat.format(LaunchingMessages.DefaultProjectLoadpathEntry_2, getRubyProject().getElementName());
}
return MessageFormat.format(LaunchingMessages.DefaultProjectLoadpathEntry_4, getRubyProject().getElementName());
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (obj instanceof DefaultProjectLoadpathEntry) {
DefaultProjectLoadpathEntry entry = (DefaultProjectLoadpathEntry) obj;
return entry.getRubyProject().equals(getRubyProject()) &&
entry.isExportedEntriesOnly() == isExportedEntriesOnly();
}
return false;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return getRubyProject().hashCode();
}
/**
* Sets whether the runtime classpath computaion should only
* include exported entries in referenced projects.
*
* @param exportedOnly
* @since 3.2
*/
public void setExportedEntriesOnly(boolean exportedOnly) {
fExportedEntriesOnly = exportedOnly;
}
/**
* Returns whether the classpath computation only includes exported
* entries in referenced projects.
*
* @return
* @since 3.2
*/
public boolean isExportedEntriesOnly() {
return fExportedEntriesOnly;
}
}