Bug 324246 - Cannot pass the project classpath to external programs defined as builders
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
index 70eb5a5..33c739a 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
@@ -79,6 +79,7 @@
 import org.eclipse.jdt.debug.tests.launching.LaunchTests;
 import org.eclipse.jdt.debug.tests.launching.LaunchesTests;
 import org.eclipse.jdt.debug.tests.launching.MigrationDelegateTests;
+import org.eclipse.jdt.debug.tests.launching.ProjectClasspathVariableTests;
 import org.eclipse.jdt.debug.tests.launching.TabGroupWrapperTests;
 import org.eclipse.jdt.debug.tests.refactoring.MoveCompilationUnitTests;
 import org.eclipse.jdt.debug.tests.refactoring.RenameCompilationUnitUnitTests;
@@ -140,6 +141,7 @@
 		addTest(new TestSuite(ConfigurationResourceMappingTests.class));
 		addTest(new TestSuite(ConfigurationEncodingTests.class));
 		addTest(new TestSuite(LaunchConfigurationManagerTests.class));
+		addTest(new TestSuite(ProjectClasspathVariableTests.class));
 		
 	//Breakpoints tests
 		addTest(new TestSuite(DeferredBreakpointTests.class));
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/ProjectClasspathVariableTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/ProjectClasspathVariableTests.java
new file mode 100644
index 0000000..9b8d310
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/ProjectClasspathVariableTests.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2010 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.jdt.debug.tests.launching;
+
+import java.io.File;
+
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.variables.IStringVariableManager;
+import org.eclipse.core.variables.VariablesPlugin;
+import org.eclipse.jdt.debug.tests.AbstractDebugTest;
+
+/**
+ * Tests for the ${project_classpath} variable 
+ */
+public class ProjectClasspathVariableTests extends AbstractDebugTest {
+
+	public ProjectClasspathVariableTests(String name) {
+		super(name);
+	}
+	
+	/**
+	 * Tests that a project name must be specified.
+	 * 
+	 * @throws Exception
+	 */
+	public void testMissingProjectName() throws Exception {
+		IStringVariableManager manager = VariablesPlugin.getDefault().getStringVariableManager();
+		try {
+			manager.performStringSubstitution("${project_classpath}");
+		} catch (CoreException e) {
+			return; // expected
+		}
+		assertNotNull("Test should have thrown an exception due to missing project name", null);
+	}
+	
+	/**
+	 * Tests that a Java project must exist
+	 * 
+	 * @throws Exception
+	 */
+	public void testProjectDoesNotExist() throws Exception {
+		IStringVariableManager manager = VariablesPlugin.getDefault().getStringVariableManager();
+		try {
+			manager.performStringSubstitution("${project_classpath:a_non_existant_project}");
+		} catch (CoreException e) {
+			return; // expected
+		}
+		assertNotNull("Test should have thrown an exception due to project does not exist", null);
+	}
+	
+	public void testProjectClasspath() throws Exception {
+		IStringVariableManager manager = VariablesPlugin.getDefault().getStringVariableManager();
+		String projectName = getJavaProject().getElementName();
+		String cp = manager.performStringSubstitution("${project_classpath:" + projectName + "}");
+		StringBuffer buffer = new StringBuffer();
+		// expecting default output location and A.jar
+		buffer.append(ResourcesPlugin.getWorkspace().getRoot().getFolder(getJavaProject().getOutputLocation()).getLocation().toOSString());
+		buffer.append(File.pathSeparatorChar);
+		buffer.append(getJavaProject().getProject().getFolder("src").getFile("A.jar").getLocation().toOSString());
+		assertEquals("Wrong classpath", buffer.toString(), cp);
+	}
+
+}
diff --git a/org.eclipse.jdt.debug.ui/plugin.xml b/org.eclipse.jdt.debug.ui/plugin.xml
index df1e01d..f9d2c10 100644
--- a/org.eclipse.jdt.debug.ui/plugin.xml
+++ b/org.eclipse.jdt.debug.ui/plugin.xml
Binary files differ
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java
index 4f992d2..dddcda8 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java
@@ -167,9 +167,11 @@
 	public static String JavaLaunchShortcut_3;
 
 	public static String JreResolutionGenerator_open_ee_prefs;
-
 	public static String JreResolutionGenerator_opens_ee_prefs;
 
+	public static String ProjectClasspathArugumentSelector_0;
+	public static String ProjectClasspathArugumentSelector_1;
+
 	static {
 		// load message values from bundle file
 		NLS.initializeMessages(BUNDLE_NAME, LauncherMessages.class);
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties
index fa1dc6d..b91ad9c 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties
@@ -150,3 +150,5 @@
 AbstractJavaMainTab_3=Select a project to constrain your search.
 ExecutionEnvironmentSelector_0=Select Execution Environment
 ExecutionEnvironmentSelector_1=Choose an environment (? = any character, * = any string):
+ProjectClasspathArugumentSelector_0=Select Java Project
+ProjectClasspathArugumentSelector_1=Choose a project (? = any character, * = any string):
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/ProjectClasspathArugumentSelector.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/ProjectClasspathArugumentSelector.java
new file mode 100644
index 0000000..11d04ce
--- /dev/null
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/ProjectClasspathArugumentSelector.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2010 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.jdt.internal.debug.ui.launcher;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.variables.IStringVariable;
+import org.eclipse.debug.internal.ui.stringsubstitution.IArgumentSelector;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.dialogs.ElementListSelectionDialog;
+import org.eclipse.ui.model.WorkbenchLabelProvider;
+
+/**
+ * Allows a Java project to be selected for the ${project_classpath} variable.
+ */
+public class ProjectClasspathArugumentSelector implements IArgumentSelector {
+
+	public ProjectClasspathArugumentSelector() {
+	}
+
+	public String selectArgument(IStringVariable variable, Shell shell) {
+		ElementListSelectionDialog dialog = new ElementListSelectionDialog(shell, new WorkbenchLabelProvider());
+		dialog.setTitle(LauncherMessages.ProjectClasspathArugumentSelector_0);
+		dialog.setMultipleSelection(false);
+		dialog.setMessage(LauncherMessages.ProjectClasspathArugumentSelector_1);
+		List javaProjects = new ArrayList();
+		IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+		for (int i = 0; i < projects.length; i++) {
+			IJavaProject jp = JavaCore.create(projects[i]);
+			if (jp.exists()) {
+				javaProjects.add(jp);
+			}
+		}
+		dialog.setElements(javaProjects.toArray()); 
+		if (dialog.open() == Window.OK) {
+			return (((IJavaProject)dialog.getResult()[0]).getElementName());
+		}
+		return null;
+	}
+
+}
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingMessages.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingMessages.java
index 8933b32..b9fdea5 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingMessages.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingMessages.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * Copyright (c) 2000, 2010 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
@@ -211,6 +211,9 @@
 	public static String PackageFragmentRootSourceContainerTypeDelegate_7;
 	public static String PackageFragmentRootSourceContainerTypeDelegate_8;
 
+	public static String ProjectClasspathVariableResolver_0;
+	public static String ProjectClasspathVariableResolver_1;
+
 	static {
 		// load message values from bundle file
 		NLS.initializeMessages(BUNDLE_NAME, LaunchingMessages.class);
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingMessages.properties b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingMessages.properties
index 613d016..564a7c4 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingMessages.properties
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingMessages.properties
@@ -1,5 +1,5 @@
 ###############################################################################
-# Copyright (c) 2000, 2008 IBM Corporation and others.
+# Copyright (c) 2000, 2010 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
@@ -174,6 +174,8 @@
 PackageFragmentRootSourceContainerTypeDelegate_6=Missing required <handle> attribute
 PackageFragmentRootSourceContainerTypeDelegate_7=Unable to restore package fragment root source container
 PackageFragmentRootSourceContainerTypeDelegate_8=Missing required <packageFragmentRoot> attribute
+ProjectClasspathVariableResolver_0=Project name must be specified with ${project_classpath} variable.
+ProjectClasspathVariableResolver_1=Specified Java project ''{0}'' does not exist.
 EEVMType_1=Description is missing required property ''{0}''
 EEVMType_2=Execution Environment Description
 EEVMType_3=Install location (-Djava.home) does not exist: {0}
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/ProjectClasspathVariableResolver.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/ProjectClasspathVariableResolver.java
new file mode 100644
index 0000000..fced6a9
--- /dev/null
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/ProjectClasspathVariableResolver.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2010 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.jdt.internal.launching;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.variables.IDynamicVariable;
+import org.eclipse.core.variables.IDynamicVariableResolver;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.launching.IRuntimeClasspathEntry;
+import org.eclipse.jdt.launching.IRuntimeClasspathEntry2;
+import org.eclipse.jdt.launching.JavaRuntime;
+
+import com.ibm.icu.text.MessageFormat;
+
+/**
+ * Resolver for ${project_classpath:<project_name>}. Returns a string corresponding to the
+ * class path of the corresponding Java project.
+ */
+public class ProjectClasspathVariableResolver implements IDynamicVariableResolver {
+
+	public String resolveValue(IDynamicVariable variable, String argument) throws CoreException {
+		if (argument == null) {
+			throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.ID_PLUGIN, LaunchingMessages.ProjectClasspathVariableResolver_0));
+		}
+		IProject proj = ResourcesPlugin.getWorkspace().getRoot().getProject(argument);
+		IJavaProject javaProject = JavaCore.create(proj);
+		if (javaProject.exists()) {
+			IRuntimeClasspathEntry2 defClassPath = (IRuntimeClasspathEntry2) JavaRuntime.newDefaultProjectClasspathEntry(javaProject);
+			IRuntimeClasspathEntry[] entries = defClassPath.getRuntimeClasspathEntries(null);
+			List collect = new ArrayList();
+			for (int i = 0; i < entries.length; i++) {
+				IRuntimeClasspathEntry[] children = JavaRuntime.resolveRuntimeClasspathEntry(entries[i], javaProject);
+				for (int j = 0; j < children.length; j++) {
+					collect.add(children[j]);
+				}
+			}
+			entries = (IRuntimeClasspathEntry[]) collect.toArray(new IRuntimeClasspathEntry[collect.size()]);
+			StringBuffer buffer = new StringBuffer();
+			for (int i = 0; i < entries.length; i++) {
+				if (i > 0) {
+					buffer.append(File.pathSeparatorChar);
+				}
+				buffer.append(entries[i].getLocation());
+			}
+			return buffer.toString();
+		} else {
+			throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.ID_PLUGIN, MessageFormat.format(LaunchingMessages.ProjectClasspathVariableResolver_1, new String[]{argument})));
+		}
+	}
+
+}
diff --git a/org.eclipse.jdt.launching/plugin.properties b/org.eclipse.jdt.launching/plugin.properties
index b66efc9..fde3ef1 100644
--- a/org.eclipse.jdt.launching/plugin.properties
+++ b/org.eclipse.jdt.launching/plugin.properties
@@ -58,3 +58,4 @@
 classpathVariableInitializer.deprecated = Use the JRE System Library instead
 
 ee.home.description = Returns the absolute file system path of the JRE home directory corresponding to the specified execution environment. An execution environment identifier must be provided as an argument.
+project.classpath.description = Returns the resolved class path of the specified project. A project name must be provided as an argument.
diff --git a/org.eclipse.jdt.launching/plugin.xml b/org.eclipse.jdt.launching/plugin.xml
index 57def57..a9afc7e 100644
--- a/org.eclipse.jdt.launching/plugin.xml
+++ b/org.eclipse.jdt.launching/plugin.xml
@@ -301,6 +301,11 @@
                name="ee_home"
                resolver="org.eclipse.jdt.internal.launching.environments.ExecutionEnvironmentVariableResolver">
          </variable>
+         <variable
+               description="%project.classpath.description"
+               name="project_classpath"
+               resolver="org.eclipse.jdt.internal.launching.ProjectClasspathVariableResolver">
+         </variable>
       </extension>
      
 </plugin>