Fixes issue 1695

Adds the command line argumetns -addToClassPath and -addModule 
(for applicationCreator) to:

  - applicationCreator
  - projectCreator
  - junitCreator

To allow an external library to be added as a part of the initial specification 
of a project.  It does some checks to see that the library exists and that 
the module exists somewhere within the classpath.

Suggested by: mmendez
Patch by: zundel
Review by: jat (desk check)



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2904 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
index f61e01c..ffe772c 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
@@ -141,6 +141,11 @@
   private final LoadStrategy strategy;
 
   /**
+   * Filename suffix used for GWT Module XML files.
+   */
+  public static final String GWT_MODULE_XML_SUFFIX = ".gwt.xml";
+
+  /**
    * Constructs a {@link ModuleDefLoader} that loads from the class path.
    */
   private ModuleDefLoader() {
@@ -194,7 +199,7 @@
     // Find the specified module using the classpath.
     //
     String slashedModuleName = moduleName.replace('.', '/');
-    String resName = slashedModuleName + ".gwt.xml";
+    String resName = slashedModuleName + ModuleDefLoader.GWT_MODULE_XML_SUFFIX;
     URL moduleURL = classLoader.getResource(resName);
 
     if (moduleURL != null) {
diff --git a/user/src/com/google/gwt/junit/tools/JUnit-hosted.launchsrc b/user/src/com/google/gwt/junit/tools/JUnit-hosted.launchsrc
index e532de1..49a8345 100644
--- a/user/src/com/google/gwt/junit/tools/JUnit-hosted.launchsrc
+++ b/user/src/com/google/gwt/junit/tools/JUnit-hosted.launchsrc
@@ -10,6 +10,7 @@
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/@projectName/test&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento project=&quot;@projectName&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry externalArchive=&quot;@gwtDevPath&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+@eclipseExtraLaunchPaths
 </listAttribute>
 <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="@projectName"/>
 <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgwt.args=&quot;-out www-test&quot; -Xmx256M"/>
diff --git a/user/src/com/google/gwt/junit/tools/JUnit-web.launchsrc b/user/src/com/google/gwt/junit/tools/JUnit-web.launchsrc
index e8504eb..56f35ad 100644
--- a/user/src/com/google/gwt/junit/tools/JUnit-web.launchsrc
+++ b/user/src/com/google/gwt/junit/tools/JUnit-web.launchsrc
@@ -10,6 +10,7 @@
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/@projectName/test&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento project=&quot;@projectName&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry externalArchive=&quot;@gwtDevPath&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+@eclipseExtraLaunchPaths
 </listAttribute>
 <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="@projectName"/>
 <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgwt.args=&quot;-web -out www-test&quot; -Xmx256M"/>
diff --git a/user/src/com/google/gwt/junit/tools/JUnitCreator.java b/user/src/com/google/gwt/junit/tools/JUnitCreator.java
index 2fa2522..00fb599 100644
--- a/user/src/com/google/gwt/junit/tools/JUnitCreator.java
+++ b/user/src/com/google/gwt/junit/tools/JUnitCreator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,9 +15,11 @@
  */
 package com.google.gwt.junit.tools;
 
+import com.google.gwt.user.tools.util.ArgHandlerAddToClassPath;
 import com.google.gwt.user.tools.util.ArgHandlerEclipse;
 import com.google.gwt.user.tools.util.ArgHandlerIgnore;
 import com.google.gwt.user.tools.util.ArgHandlerOverwrite;
+import com.google.gwt.user.tools.util.CreatorUtilities;
 import com.google.gwt.util.tools.ArgHandlerExtra;
 import com.google.gwt.util.tools.ArgHandlerOutDir;
 import com.google.gwt.util.tools.ArgHandlerString;
@@ -27,6 +29,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -116,19 +119,27 @@
    *          Application.
    * @param outDir Where to put the output files
    * @param eclipse The name of a project to attach a .launch config to
+   * @param extraClassPaths extra class path entries to add to the configuration
    * @param overwrite Overwrite an existing files if they exist.
    * @param ignore Ignore existing files if they exist.
    * @throws IOException
    */
   static void createTest(String junitPath, String moduleName,
-      String fullClassName, File outDir, String eclipse, boolean overwrite,
-      boolean ignore) throws IOException {
+      String fullClassName, File outDir, String eclipse,
+      List<String> extraClassPaths, boolean overwrite, boolean ignore)
+      throws IOException {
 
     // Figure out the installation directory
     String installPath = Utility.getInstallPath();
     String gwtUserPath = installPath + '/' + "gwt-user.jar";
     String gwtDevPath = installPath + '/' + Utility.getDevJarName();
 
+    // Check to see that the passed extra path/module arguments are valid.
+    if (!CreatorUtilities.validatePathsAndModules(gwtUserPath, extraClassPaths,
+        null)) {
+      return;
+    }
+
     // Figure out what platform we're on
     // 
     boolean isWindows = gwtDevPath.substring(gwtDevPath.lastIndexOf('/') + 1).indexOf(
@@ -198,6 +209,11 @@
     replacements.put("@gwtUserPath", basePathEnv + gwtUserPath);
     replacements.put("@gwtDevPath", basePathEnv + gwtDevPath);
     replacements.put("@vmargs", isMacOsX ? "-XstartOnFirstThread" : "");
+    replacements.put("@eclipseExtraLaunchPaths",
+        CreatorUtilities.createEclipseExtraLaunchPaths(extraClassPaths));
+    replacements.put("@extraClassPathsColon", CreatorUtilities.appendPaths(":",
+        extraClassPaths));
+    replacements.put("@extraClassPathsSemicolon", CreatorUtilities.appendPaths(";", extraClassPaths));
 
     {
       // Create a skeleton Test class
@@ -213,6 +229,7 @@
     if (eclipse != null) {
       // Create an eclipse launch config
       replacements.put("@projectName", eclipse);
+
       File hostedConfig = Utility.createNormalFile(outDir, className
           + "-hosted.launch", overwrite, ignore);
       if (hostedConfig != null) {
@@ -270,6 +287,7 @@
   private String moduleName = null;
   private File outDir;
   private boolean overwrite = false;
+  private ArgHandlerAddToClassPath classPathHandler = new ArgHandlerAddToClassPath();
 
   protected JUnitCreator() {
 
@@ -394,12 +412,13 @@
     });
 
     registerHandler(new ArgHandlerTestClass());
+    registerHandler(classPathHandler);
   }
 
   protected boolean run() {
     try {
       createTest(junitPath, moduleName, fullClassName, outDir, eclipse,
-          overwrite, ignore);
+          classPathHandler.getExtraClassPathList(), overwrite, ignore);
       return true;
     } catch (IOException e) {
       System.err.println(e.getClass().getName() + ": " + e.getMessage());
diff --git a/user/src/com/google/gwt/junit/tools/junit-hosted.cmdsrc b/user/src/com/google/gwt/junit/tools/junit-hosted.cmdsrc
index 9ed79bb..2e25daf 100644
--- a/user/src/com/google/gwt/junit/tools/junit-hosted.cmdsrc
+++ b/user/src/com/google/gwt/junit/tools/junit-hosted.cmdsrc
@@ -1 +1 @@
-@java -Dgwt.args="-out www-test" -Xmx256M -cp "%~dp0\src;%~dp0\test;%~dp0\bin;@junitPath;@gwtUserPath;@gwtDevPath" junit.textui.TestRunner @clientPackage.@className %*
\ No newline at end of file
+@java -Dgwt.args="-out www-test" -Xmx256M -cp "%~dp0\src;%~dp0\test;%~dp0\bin;@junitPath;@gwtUserPath;@gwtDevPath@extraClassPathsSemicolon" junit.textui.TestRunner @clientPackage.@className %*
\ No newline at end of file
diff --git a/user/src/com/google/gwt/junit/tools/junit-hostedsrc b/user/src/com/google/gwt/junit/tools/junit-hostedsrc
index d050571..44c275b 100644
--- a/user/src/com/google/gwt/junit/tools/junit-hostedsrc
+++ b/user/src/com/google/gwt/junit/tools/junit-hostedsrc
@@ -1,3 +1,3 @@
 #!/bin/sh
 APPDIR=`dirname $0`;
-java @vmargs -Dgwt.args="-out www-test" -Xmx256M -cp "$APPDIR/src:$APPDIR/test:$APPDIR/bin:@junitPath:@gwtUserPath:@gwtDevPath" junit.textui.TestRunner @clientPackage.@className "$@";
+java @vmargs -Dgwt.args="-out www-test" -Xmx256M -cp "$APPDIR/src:$APPDIR/test:$APPDIR/bin:@junitPath:@gwtUserPath:@gwtDevPath@extraClassPathsColon" junit.textui.TestRunner @clientPackage.@className "$@";
diff --git a/user/src/com/google/gwt/junit/tools/junit-web.cmdsrc b/user/src/com/google/gwt/junit/tools/junit-web.cmdsrc
index e6940de..ac61775 100644
--- a/user/src/com/google/gwt/junit/tools/junit-web.cmdsrc
+++ b/user/src/com/google/gwt/junit/tools/junit-web.cmdsrc
@@ -1 +1 @@
-@java -Dgwt.args="-web -out www-test" -Xmx256M -cp "%~dp0\src;%~dp0\test;%~dp0\bin;@junitPath;@gwtUserPath;@gwtDevPath" junit.textui.TestRunner @clientPackage.@className %*
\ No newline at end of file
+@java -Dgwt.args="-web -out www-test" -Xmx256M -cp "%~dp0\src;%~dp0\test;%~dp0\bin;@junitPath;@gwtUserPath;@gwtDevPath@extraClassPathsSemicolon" junit.textui.TestRunner @clientPackage.@className %*
\ No newline at end of file
diff --git a/user/src/com/google/gwt/junit/tools/junit-websrc b/user/src/com/google/gwt/junit/tools/junit-websrc
index 5b92d82..918b68a 100644
--- a/user/src/com/google/gwt/junit/tools/junit-websrc
+++ b/user/src/com/google/gwt/junit/tools/junit-websrc
@@ -1,3 +1,3 @@
 #!/bin/sh
 APPDIR=`dirname $0`;
-java @vmargs -Dgwt.args="-web -out www-test" -Xmx256M -cp "$APPDIR/src:$APPDIR/test:$APPDIR/bin:@junitPath:@gwtUserPath:@gwtDevPath" junit.textui.TestRunner @clientPackage.@className "$@";
+java @vmargs -Dgwt.args="-web -out www-test" -Xmx256M -cp "$APPDIR/src:$APPDIR/test:$APPDIR/bin:@junitPath:@gwtUserPath:@gwtDevPath@extraClassPathsColon" junit.textui.TestRunner @clientPackage.@className "$@";
diff --git a/user/src/com/google/gwt/user/tools/.classpathsrc b/user/src/com/google/gwt/user/tools/.classpathsrc
index af78a3f..5a43ea4 100644
--- a/user/src/com/google/gwt/user/tools/.classpathsrc
+++ b/user/src/com/google/gwt/user/tools/.classpathsrc
@@ -6,4 +6,5 @@
    <classpathentry kind="lib" path="@gwtUserPath"/>
    <classpathentry kind="var" path="JUNIT_HOME/junit.jar"/>
    <classpathentry kind="output" path="bin"/>
+@eclipseClassPathEntries
 </classpath>
diff --git a/user/src/com/google/gwt/user/tools/App.launchsrc b/user/src/com/google/gwt/user/tools/App.launchsrc
index 6ea991a..b35bd08 100644
--- a/user/src/com/google/gwt/user/tools/App.launchsrc
+++ b/user/src/com/google/gwt/user/tools/App.launchsrc
@@ -7,6 +7,7 @@
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/@projectName/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento project=&quot;@projectName&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry externalArchive=&quot;@gwtDevPath&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+@eclipseExtraLaunchPaths
 </listAttribute>
 <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="@vmargs -Xmx256M"/>
 <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-out www @startupUrl"/>
diff --git a/user/src/com/google/gwt/user/tools/ApplicationCreator.java b/user/src/com/google/gwt/user/tools/ApplicationCreator.java
index efeaf0a..99948f9 100644
--- a/user/src/com/google/gwt/user/tools/ApplicationCreator.java
+++ b/user/src/com/google/gwt/user/tools/ApplicationCreator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,28 +15,82 @@
  */
 package com.google.gwt.user.tools;
 
+import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.user.tools.util.ArgHandlerAddToClassPath;
 import com.google.gwt.user.tools.util.ArgHandlerEclipse;
 import com.google.gwt.user.tools.util.ArgHandlerIgnore;
 import com.google.gwt.user.tools.util.ArgHandlerOverwrite;
+import com.google.gwt.user.tools.util.CreatorUtilities;
 import com.google.gwt.util.tools.ArgHandlerExtra;
 import com.google.gwt.util.tools.ArgHandlerOutDir;
+import com.google.gwt.util.tools.ArgHandlerString;
 import com.google.gwt.util.tools.ToolBase;
 import com.google.gwt.util.tools.Utility;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.StringTokenizer;
 
 /**
- * Creates a GWT application.
- * 
+ * Creates a GWT application skeleton.
  */
 public final class ApplicationCreator extends ToolBase {
 
-  /**
+  /*
    * Arguments for the application creator.
-   * 
+   */
+
+  /**
+   * Add an extra module injection into the top level module file.
+   */
+  protected class ArgHandlerAddModule extends ArgHandlerString {
+    private List<String> extraModuleList = new ArrayList<String>();
+
+    public List<String> getExtraModuleList() {
+      return extraModuleList;
+    }
+
+    @Override
+    public String getPurpose() {
+      return "Adds extra GWT modules to be inherited.";
+    }
+
+    @Override
+    public String getTag() {
+      return "-addModule";
+    }
+
+    @Override
+    public String[] getTagArgs() {
+      return new String[] {"module"};
+    }
+
+    @Override
+    public boolean setString(String str) {
+      // Parse out a comma separated list
+      StringTokenizer st = new StringTokenizer(str, ",");
+      while (st.hasMoreTokens()) {
+        String module = st.nextToken();
+
+        // Check className to see that it is a period separated string of words.
+        if (!module.matches("[\\w\\$]+(\\.[\\w\\$]+)+")) {
+          System.err.println("'" + module
+              + "' does not appear to be a valid fully-qualified module name");
+          return false;
+        }
+        extraModuleList.add(module);
+      }
+
+      return true;
+    }
+  }
+
+  /**
+   * Specify the top level class name of the application to create.
    */
   protected class ArgHandlerAppClass extends ArgHandlerExtra {
 
@@ -55,7 +109,6 @@
       }
 
       // Check out the class name.
-      //
       if (arg.indexOf('$') != -1) {
         System.err.println("'" + arg
             + "': This version of the tool does not support nested classes");
@@ -119,13 +172,38 @@
    * @throws IOException
    */
   static void createApplication(String fullClassName, File outDir,
-      String eclipse, boolean overwrite, boolean ignore) throws IOException {
+      String eclipse, boolean overwrite, boolean ignore)
+      throws IOException {
+    createApplication(fullClassName, outDir, eclipse, overwrite, ignore, null, null);
+  }
+  
+  /**
+   * @param fullClassName Name of the fully-qualified Java class to create as an
+   *          Application.
+   * @param outDir Where to put the output files
+   * @param eclipse The name of a project to attach a .launch config to
+   * @param overwrite Overwrite an existing files if they exist.
+   * @param ignore Ignore existing files if they exist.
+   * @param extraClassPaths A list of paths to append to the class path for
+   *          launch configs.
+   * @param extraModules A list of GWT modules to add 'inherits' tags for.
+   * @throws IOException
+   */
+  static void createApplication(String fullClassName, File outDir,
+      String eclipse, boolean overwrite, boolean ignore,
+      List<String> extraClassPaths, List<String> extraModules)
+      throws IOException {
 
     // Figure out the installation directory
     String installPath = Utility.getInstallPath();
     String gwtUserPath = installPath + '/' + "gwt-user.jar";
     String gwtDevPath = installPath + '/' + Utility.getDevJarName();
 
+    // Validate the arguments for extra class path entries and modules.
+    if (!CreatorUtilities.validatePathsAndModules(gwtUserPath, extraClassPaths,
+        extraModules)) {
+      return;
+    }
     // Figure out what platform we're on
     // 
     boolean isWindows = gwtDevPath.substring(gwtDevPath.lastIndexOf('/') + 1).indexOf(
@@ -184,11 +262,18 @@
     replacements.put("@compileClass", "com.google.gwt.dev.GWTCompiler");
     replacements.put("@startupUrl", startupUrl);
     replacements.put("@vmargs", isMacOsX ? "-XstartOnFirstThread" : "");
+    replacements.put("@eclipseExtraLaunchPaths",
+        CreatorUtilities.createEclipseExtraLaunchPaths(extraClassPaths));
+    replacements.put("@extraModuleInherits",
+        createExtraModuleInherits(extraModules));
+    replacements.put("@extraClassPathsColon", CreatorUtilities.appendPaths(":",
+        extraClassPaths));
+    replacements.put("@extraClassPathsSemicolon", CreatorUtilities.appendPaths(";", extraClassPaths));
 
     {
       // Create the module
       File moduleXML = Utility.createNormalFile(basePackageDir, className
-          + ".gwt.xml", overwrite, ignore);
+          + ModuleDefLoader.GWT_MODULE_XML_SUFFIX, overwrite, ignore);
       if (moduleXML != null) {
         String out = Utility.getFileFromClassPath(PACKAGE_PATH
             + "Module.gwt.xmlsrc");
@@ -287,9 +372,25 @@
     }
   }
 
+  private static String createExtraModuleInherits(List<String> modules) {
+    if (modules == null) {
+      return "";
+    }
+    // Create an <inherits> tag in the gwt.xml file for each extra module
+    StringBuilder buf = new StringBuilder();
+    for (String module : modules) {
+      buf.append("      <inherits name=\"");
+      buf.append(module);
+      buf.append("\" />\n");
+    }
+    return buf.toString();
+  }
+
+  private ArgHandlerAddToClassPath classPathHandler = new ArgHandlerAddToClassPath();
   private String eclipse = null;
   private String fullClassName = null;
   private boolean ignore = false;
+  private ArgHandlerAddModule moduleHandler = new ArgHandlerAddModule();
   private File outDir;
   private boolean overwrite = false;
 
@@ -340,11 +441,15 @@
     });
 
     registerHandler(new ArgHandlerAppClass());
+    registerHandler(classPathHandler);
+    registerHandler(moduleHandler);
   }
 
   protected boolean run() {
     try {
-      createApplication(fullClassName, outDir, eclipse, overwrite, ignore);
+      createApplication(fullClassName, outDir, eclipse, overwrite, ignore,
+          classPathHandler.getExtraClassPathList(),
+          moduleHandler.getExtraModuleList());
       return true;
     } catch (IOException e) {
       System.err.println(e.getClass().getName() + ": " + e.getMessage());
diff --git a/user/src/com/google/gwt/user/tools/Module.gwt.xmlsrc b/user/src/com/google/gwt/user/tools/Module.gwt.xmlsrc
index a930314..8f0ed45 100644
--- a/user/src/com/google/gwt/user/tools/Module.gwt.xmlsrc
+++ b/user/src/com/google/gwt/user/tools/Module.gwt.xmlsrc
@@ -10,6 +10,9 @@
       <!-- <inherits name="com.google.gwt.user.theme.chrome.Chrome"/> -->
       <!-- <inherits name="com.google.gwt.user.theme.dark.Dark"/>     -->
 
+      <!-- Other module inherits                                      -->
+@extraModuleInherits
+
       <!-- Specify the app entry point class.                         -->
       <entry-point class='@clientPackage.@className'/>
     
diff --git a/user/src/com/google/gwt/user/tools/ProjectCreator.java b/user/src/com/google/gwt/user/tools/ProjectCreator.java
index 6b6a558..0ea5d2b 100644
--- a/user/src/com/google/gwt/user/tools/ProjectCreator.java
+++ b/user/src/com/google/gwt/user/tools/ProjectCreator.java
@@ -15,17 +15,21 @@
  */
 package com.google.gwt.user.tools;
 
+import com.google.gwt.user.tools.util.ArgHandlerAddToClassPath;
 import com.google.gwt.user.tools.util.ArgHandlerEclipse;
 import com.google.gwt.user.tools.util.ArgHandlerIgnore;
 import com.google.gwt.user.tools.util.ArgHandlerOverwrite;
+import com.google.gwt.user.tools.util.CreatorUtilities;
 import com.google.gwt.util.tools.ArgHandlerOutDir;
 import com.google.gwt.util.tools.ArgHandlerString;
 import com.google.gwt.util.tools.ToolBase;
 import com.google.gwt.util.tools.Utility;
 
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -53,6 +57,8 @@
   }
 
   /**
+   * Create a set of project files.
+   * 
    * @param eclipse The name of project to create.
    * @param ant The name of an ant file to create.
    * @param outDir The directory to write into.
@@ -62,14 +68,37 @@
    */
   static void createProject(String eclipse, String ant, File outDir,
       boolean overwrite, boolean ignore) throws IOException {
+    createProject(eclipse, ant, outDir, overwrite, ignore, null);
+  }
+
+  /**
+   * Create a set of project files.
+   * 
+   * @param eclipse The name of project to create.
+   * @param ant The name of an ant file to create.
+   * @param outDir The directory to write into.
+   * @param overwrite Overwrite an existing files if they exist.
+   * @param ignore Ignore existing files if they exist.
+   * @param extraClassPaths class path entries passed on the command line
+   * @throws IOException
+   */
+  static void createProject(String eclipse, String ant, File outDir,
+      boolean overwrite, boolean ignore, List<String> extraClassPaths)
+      throws IOException {
 
     // Figure out the installation directory
     String installPath = Utility.getInstallPath();
 
     // Create a map of replacements.
-    //
     Map<String, String> replacements = new HashMap<String, String>();
-    replacements.put("@gwtUserPath", installPath + '/' + "gwt-user.jar");
+    String userJarPath = installPath + '/' + "gwt-user.jar";
+    replacements.put("@gwtUserPath", userJarPath);
+
+    // Check to see that the passed extra path/module arguments are valid.
+    if (!CreatorUtilities.validatePathsAndModules(userJarPath, extraClassPaths,
+        null)) {
+      return;
+    }
 
     Utility.getDirectory(outDir, "src", true);
     Utility.getDirectory(outDir, "test", true);
@@ -77,6 +106,16 @@
     if (ant != null) {
       // Create an ant build file
       replacements.put("@projectName", ant);
+
+      // Build the list of extra paths
+      StringBuilder buf = new StringBuilder();
+      if (extraClassPaths != null) {
+        for (String path : extraClassPaths) {
+          buf.append("    <pathelement path=\"" + path + "\"/>");
+        }
+      }
+      replacements.put("@extraAntPathElements", buf.toString());
+
       File antXML = Utility.createNormalFile(outDir, ant + ".ant.xml",
           overwrite, ignore);
       if (antXML != null) {
@@ -96,6 +135,35 @@
         Utility.writeTemplateFile(dotProject, out, replacements);
       }
 
+      StringBuilder classpathEntries = new StringBuilder();
+      if (extraClassPaths != null) {
+        for (String path : extraClassPaths) {
+          File f = new File(path);
+
+          if (!f.exists()) {
+            throw new FileNotFoundException("extraClassPath: " + path
+                + " must be present before .launch file can be created.");
+          }
+          // Handle both .jar files and paths
+        String kindString;
+          if (f.isDirectory()) {
+            kindString = "output";
+          } else if (path.endsWith(".jar")) {
+            kindString = "lib";
+          } else {
+            throw new RuntimeException("Don't know how to handle path: " + path
+                + ". It doesn't appear to be a directory or a .jar file");
+          }
+          classpathEntries.append("   <classpathentry kind=\"");
+          classpathEntries.append(kindString);
+          classpathEntries.append("\" path=\"");
+          classpathEntries.append(path);
+          classpathEntries.append("\"/>\n");
+        }
+      }
+
+      replacements.put("@eclipseClassPathEntries", classpathEntries.toString());
+
       // Create an eclipse classpath file
       File dotClasspath = Utility.createNormalFile(outDir, ".classpath",
           overwrite, ignore);
@@ -108,12 +176,11 @@
   }
 
   private String ant = null;
-
   private String eclipse = null;
-
   private boolean ignore = false;
   private File outDir = null;
   private boolean overwrite = false;
+  private ArgHandlerAddToClassPath classPathHandler = new ArgHandlerAddToClassPath();
 
   protected ProjectCreator() {
 
@@ -185,6 +252,8 @@
         return true;
       }
     });
+
+    registerHandler(classPathHandler);
   }
 
   protected boolean run() {
@@ -194,7 +263,8 @@
         printHelp();
         return false;
       }
-      createProject(eclipse, ant, outDir, overwrite, ignore);
+      createProject(eclipse, ant, outDir, overwrite, ignore,
+          classPathHandler.getExtraClassPathList());
       return true;
     } catch (IOException e) {
       System.err.println(e.getClass().getName() + ": " + e.getMessage());
diff --git a/user/src/com/google/gwt/user/tools/gwtcompile.cmdsrc b/user/src/com/google/gwt/user/tools/gwtcompile.cmdsrc
index 9abaded..80f6e97 100644
--- a/user/src/com/google/gwt/user/tools/gwtcompile.cmdsrc
+++ b/user/src/com/google/gwt/user/tools/gwtcompile.cmdsrc
@@ -1 +1 @@
-@java -Xmx256M -cp "%~dp0\src;%~dp0\bin;@gwtUserPath;@gwtDevPath" @compileClass -out "%~dp0\www" %* @moduleName
\ No newline at end of file
+@java -Xmx256M -cp "%~dp0\src;%~dp0\bin;@gwtUserPath;@gwtDevPath@extraClassPathsSemicolon" @compileClass -out "%~dp0\www" %* @moduleName
\ No newline at end of file
diff --git a/user/src/com/google/gwt/user/tools/gwtcompilesrc b/user/src/com/google/gwt/user/tools/gwtcompilesrc
index 4318531..75064a8 100644
--- a/user/src/com/google/gwt/user/tools/gwtcompilesrc
+++ b/user/src/com/google/gwt/user/tools/gwtcompilesrc
@@ -1,3 +1,3 @@
 #!/bin/sh
 APPDIR=`dirname $0`;
-java @vmargs -Xmx256M -cp "$APPDIR/src:$APPDIR/bin:@gwtUserPath:@gwtDevPath" @compileClass -out "$APPDIR/www" "$@" @moduleName;
+java @vmargs -Xmx256M -cp "$APPDIR/src:$APPDIR/bin:@gwtUserPath:@gwtDevPath@extraClassPathsColon" @compileClass -out "$APPDIR/www" "$@" @moduleName;
diff --git a/user/src/com/google/gwt/user/tools/gwtshell.cmdsrc b/user/src/com/google/gwt/user/tools/gwtshell.cmdsrc
index 15a496a..dd14324 100644
--- a/user/src/com/google/gwt/user/tools/gwtshell.cmdsrc
+++ b/user/src/com/google/gwt/user/tools/gwtshell.cmdsrc
@@ -1 +1 @@
-@java -Xmx256M -cp "%~dp0\src;%~dp0\bin;@gwtUserPath;@gwtDevPath" @shellClass -out "%~dp0\www" %* @startupUrl
\ No newline at end of file
+@java -Xmx256M -cp "%~dp0\src;%~dp0\bin;@gwtUserPath;@gwtDevPath@extraClassPathsSemicolon" @shellClass -out "%~dp0\www" %* @startupUrl
\ No newline at end of file
diff --git a/user/src/com/google/gwt/user/tools/gwtshellsrc b/user/src/com/google/gwt/user/tools/gwtshellsrc
index 31a52df..195b443 100644
--- a/user/src/com/google/gwt/user/tools/gwtshellsrc
+++ b/user/src/com/google/gwt/user/tools/gwtshellsrc
@@ -1,3 +1,3 @@
 #!/bin/sh
 APPDIR=`dirname $0`;
-java @vmargs -Xmx256M -cp "$APPDIR/src:$APPDIR/bin:@gwtUserPath:@gwtDevPath" @shellClass -out "$APPDIR/www" "$@" @startupUrl;
+java @vmargs -Xmx256M -cp "$APPDIR/src:$APPDIR/bin:@gwtUserPath:@gwtDevPath@extraClassPathsColon" @shellClass -out "$APPDIR/www" "$@" @startupUrl;
diff --git a/user/src/com/google/gwt/user/tools/project.ant.xmlsrc b/user/src/com/google/gwt/user/tools/project.ant.xmlsrc
index 01535e8..942140d 100644
--- a/user/src/com/google/gwt/user/tools/project.ant.xmlsrc
+++ b/user/src/com/google/gwt/user/tools/project.ant.xmlsrc
@@ -9,6 +9,7 @@
   <path id="project.class.path">
     <pathelement path="${java.class.path}/"/>
     <pathelement path="@gwtUserPath"/>
+@extraAntPathElements
     <!-- Additional dependencies (such as junit) go here -->
   </path>
 
diff --git a/user/src/com/google/gwt/user/tools/util/ArgHandlerAddToClassPath.java b/user/src/com/google/gwt/user/tools/util/ArgHandlerAddToClassPath.java
new file mode 100644
index 0000000..3833115
--- /dev/null
+++ b/user/src/com/google/gwt/user/tools/util/ArgHandlerAddToClassPath.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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.gwt.user.tools.util;
+
+import com.google.gwt.util.tools.ArgHandlerString;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * Parse the -addToClassPath argument. Appends a .jar or classpath to the
+ * generated launch scripts.
+ */
+public class ArgHandlerAddToClassPath extends ArgHandlerString {
+  private List<String> extraClassPathList = new ArrayList<String>();
+
+  public List<String> getExtraClassPathList() {
+    return extraClassPathList;
+  }
+
+  @Override
+  public String getPurpose() {
+    return "Adds extra elements to the class path.";
+  }
+
+  @Override
+  public String getTag() {
+    return "-addToClassPath";
+  }
+
+  @Override
+  public String[] getTagArgs() {
+    return new String[] {"classPathEntry"};
+  }
+
+  @Override
+  public boolean setString(String str) {
+    
+    StringTokenizer st = new StringTokenizer(str, ",");
+    while (st.hasMoreTokens()) {
+      extraClassPathList.add(st.nextToken().trim());
+    }
+    return true;
+  }
+}
diff --git a/user/src/com/google/gwt/user/tools/util/CreatorUtilities.java b/user/src/com/google/gwt/user/tools/util/CreatorUtilities.java
new file mode 100644
index 0000000..f30b587
--- /dev/null
+++ b/user/src/com/google/gwt/user/tools/util/CreatorUtilities.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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.gwt.user.tools.util;
+
+import com.google.gwt.dev.cfg.ModuleDefLoader;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility methods shared by ApplicationCreator and ProjectCreator.
+ * 
+ */
+public class CreatorUtilities {
+
+  /**
+   * Create a PATH style string separated by the specified delimiter (';' for
+   * windows, ':' for UNIX) Note that this method prepends the delimiter to the
+   * front of the string. There is an existing path we want to append to.
+   * 
+   * @param delimiter The delimiter string to place between variables.
+   * @param paths The list of paths to concatenate together.
+   * @return the concatenated list of paths as a single string.
+   */
+  public static String appendPaths(String delimiter, List<String> paths) {
+    if (paths == null) {
+      return "";
+    }
+    StringBuilder buf = new StringBuilder();
+    for (String value : paths) {
+      buf.append(delimiter);
+      buf.append(value);
+    }
+    return buf.toString();
+  }
+
+  /**
+   * Create the extra path entries for an Eclipse '.launch' file in XML format.
+   * 
+   * @param extraClassPaths a list of paths/.jar files to add to the class path
+   * @return A string formatted to include in the .launch file
+   */
+  public static String createEclipseExtraLaunchPaths(
+      List<String> extraClassPaths) throws FileNotFoundException {
+
+    if (extraClassPaths == null) {
+      return "";
+    }
+
+    // Create an entry for an Eclipse launch file additional classpath entry.
+    StringBuilder buf = new StringBuilder();
+    for (String path : extraClassPaths) {
+      File f = new File(path);
+
+      if (!f.exists()) {
+        throw new FileNotFoundException("extraClassPath: " + path
+            + "Must be present before .launch file can be created");
+      }
+
+      String lcPath = path.toLowerCase();
+      
+      if (f.isDirectory()) {
+        // For a directory, we assume it contains compiled class files
+        buf.append("<listEntry value=\"&lt;?xml version=&quot;1.0&quot; ");
+        buf.append("encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;");
+        buf.append("?&gt;&#13;&#10;&lt;runtimeClasspathEntry ");
+        buf.append("internalArchive=&quot;");
+        buf.append(path);
+        buf.append("&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;\"/>");
+        buf.append("\n");
+      } else if (lcPath.endsWith(".jar") || lcPath.endsWith(".zip")) {
+        // Any plain file we assume is an external library (e.g. a .jar file)
+        buf.append("<listEntry value=\"&lt;?xml version=&quot;1.0&quot; ");
+        buf.append("encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry ");
+        buf.append("externalArchive=&quot;");
+        buf.append(path);
+        buf.append("&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;\"/>");
+        buf.append("\n");
+      } else {
+        throw new RuntimeException("Don't know how to handle path: " + path
+            + ". It doesn't appear to be a directory or a .jar/.zip file");
+      }
+    }
+    return buf.toString();
+  }
+
+  /**
+   * Check to see that the userJar and pathList files all exist, and that the
+   * moduleList entries can be found within the jars.
+   * 
+   * @param userJar The full path to gwt-user.jar
+   * @param pathList A list of jar files to add to the class path.
+   * @param moduleList A list of GWT module names to add as 'inherits' tags
+   * @return <code>true</code> if all validations pass.
+   */
+  public static boolean validatePathsAndModules(String userJar,
+      List<String> pathList, List<String> moduleList) {
+    List<URL> urlList = new ArrayList<URL>();
+
+    if (!addURL(urlList, userJar)) {
+      return false;
+    }
+    if (pathList != null) {
+      for (String path : pathList) {
+        if (!addURL(urlList, path)) {
+          return false;
+        }
+      }
+    }
+
+    /*
+     * Create a class loader from the extra class paths and the current class
+     * loader. The assumption is that if the userJar isn't available right now,
+     * that the current class loader will contain the same gwt.xml module def
+     * files.
+     */
+    URL urlArray[] = urlList.toArray(new URL[urlList.size()]);
+    URLClassLoader classLoader = new URLClassLoader(urlArray,
+        CreatorUtilities.class.getClassLoader());
+    if (moduleList != null) {
+      for (String module : moduleList) {
+        String modulePath = module.replace(".", "/")
+            + ModuleDefLoader.GWT_MODULE_XML_SUFFIX;
+        URL found = classLoader.getResource(modulePath);
+        if (found == null) {
+          System.err.println("Couldn't find module definition file "
+              + modulePath + " in class path.");
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Append a path to a list of URLs.
+   * 
+   * @param urls list to append to
+   * @param pathToAdd string to append as the last entry in the URL list.
+   * @return <code>true</code> on success. <code>false</code> if an error
+   *         occurs (malformed URL or missing file.)
+   */
+  private static boolean addURL(List<URL> urls, String pathToAdd) {
+    File f = new File(pathToAdd);
+
+    // Ignore gwt-user.jar in the validation. This helps the build process
+    // get by when overriding the location of the .jar with -Dgwt.devjar
+    if (!pathToAdd.matches(".*gwt-user.jar") && !f.exists()) {
+      System.err.println("Couldn't find library file or path " + pathToAdd);
+      return false;
+    }
+    try {
+      urls.add(f.toURI().toURL());
+    } catch (MalformedURLException urlEx) {
+      urlEx.printStackTrace();
+      return false;
+    }
+    return true;
+  }
+}