1) Adds command-line flags to allow the user to specify a reference jar and gwt dev
and user jars. These changes were necessary so that gwt-incubator and other
projects using gwt-trunk could use api-checker. 

2) Cleans up the code for processing the command-line flags (it uses ToolBase
now) and the config file.

3) Adds the capability to get a list of compilation units from a jar file. 

4) Updates the build files as per the above changes. 

Patch by: amitmanjhi
Review by: spoon (desk review)




git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.6@4343 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/build.xml b/build.xml
index 48c1335..d6761b0 100755
--- a/build.xml
+++ b/build.xml
@@ -83,6 +83,8 @@
     </copy>
     <java failonerror="true" fork="true"
           classname="com.google.gwt.tools.apichecker.ApiCompatibilityChecker">
+      <jvmarg line="-Xmx512m" />
+      <sysproperty key="gwt.devjar" value="${gwt.dev.staging.jar}" />
       <classpath>
         <pathelement location="${gwt.build.out}/tools/api-checker/bin"/>
         <pathelement location="${gwt.build.out}/dev/core/bin"/>
@@ -90,6 +92,7 @@
         <pathelement location="${gwt.tools.lib}/eclipse/jdt-3.3.1.jar"/>
         <pathelement path="${java.class.path}"/>
       </classpath>
+      <arg value="-configFile"/>
       <arg file="${gwt.build.out}/userApi.conf"/>
     </java>
   </target>
diff --git a/tools/api-checker/build.xml b/tools/api-checker/build.xml
index 3259342..8afcaea 100755
--- a/tools/api-checker/build.xml
+++ b/tools/api-checker/build.xml
@@ -54,6 +54,7 @@
     <mkdir dir="${junit.out}/reports" />
   
     <junit dir="${junit.out}" fork="yes" printsummary="yes" haltonfailure="true">
+      <sysproperty key="gwt.devjar" value="${gwt.dev.staging.jar}" />
       <classpath>
         <pathelement location="test" />
         <pathelement location="${gwt.build.out}/dev/core/bin"/>
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiCompatibilityChecker.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiCompatibilityChecker.java
index 71bd2e2..8e2c6e5 100644
--- a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiCompatibilityChecker.java
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiCompatibilityChecker.java
@@ -16,19 +16,37 @@
 package com.google.gwt.tools.apichecker;
 
 import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.javac.impl.FileCompilationUnit;
 import com.google.gwt.dev.util.log.AbstractTreeLogger;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+import com.google.gwt.util.tools.ArgHandlerFlag;
+import com.google.gwt.util.tools.ArgHandlerString;
+import com.google.gwt.util.tools.ToolBase;
 
+import java.io.BufferedInputStream;
 import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Properties;
 import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
 
 /**
  * {@link ApiCompatibilityChecker} Main class to check if the new API is
@@ -67,46 +85,349 @@
  * adding the 'final' keyword to the API member.
  * 
  */
-public class ApiCompatibilityChecker {
-
-  // TODO(amitmanjhi): use ToolBase for command-line processing
+public class ApiCompatibilityChecker extends ToolBase {
 
   // TODO(amitmanjhi): check gwt's dev/core/src files. Would need the ability to
   // build TypeOracle from class files
 
-  // TODO(amitmanjhi): ignore API breakages due to impl package. More generally,
-  // white-list of packages that should not be checked.
-
   // TODO(amitmanjhi): better handling of exceptions and exception-chaining.
 
+  static class StaticCompilationUnit extends CompilationUnit {
+
+    private final String source;
+    private final String typeName;
+
+    public StaticCompilationUnit(String typeName, String source) {
+      this.typeName = typeName;
+      this.source = source;
+    }
+
+    @Override
+    public String getDisplayLocation() {
+      return "/mock/" + typeName;
+    }
+
+    @Override
+    public long getLastModified() {
+      return 0;
+    }
+
+    @Override
+    public String getSource() {
+      return String.valueOf(source);
+    }
+
+    @Override
+    public String getTypeName() {
+      return typeName;
+    }
+
+    @Override
+    public boolean isGenerated() {
+      return false;
+    }
+
+    @Override
+    public boolean isSuperSource() {
+      return false;
+    }
+  }
+
+  /**
+   * Abstract internal class that specifies a set of {@link CompilationUnit}.
+   */
+  private abstract static class CompilationUnits {
+    protected final TreeLogger logger;
+
+    CompilationUnits(TreeLogger logger) {
+      this.logger = logger;
+    }
+
+    public abstract Set<CompilationUnit> getCompilationUnits()
+        throws NotFoundException, IOException, UnableToCompleteException;
+
+    // TODO (amitmanjhi): remove this code. use TypeOracle functionality
+    // instead.
+    protected String extractPackageName(Reader reader) throws IOException {
+      BufferedReader br = new BufferedReader(reader);
+      String str = null;
+      while ((str = br.readLine()) != null) {
+        if (str.indexOf("package") != 0) {
+          continue;
+        }
+        String parts[] = str.split("[\b\t\n\r ]+");
+        if ((parts.length == 2) && parts[0].equals("package")) {
+          return parts[1].substring(0, parts[1].length() - 1); // the ; char
+        }
+      }
+      return null;
+    }
+
+    protected File getFileFromName(String tag, String pathName)
+        throws FileNotFoundException {
+      File file = new File(pathName);
+      if (!file.exists()) {
+        throw new FileNotFoundException(tag + "file " + pathName + " not found");
+      }
+      return file;
+    }
+
+    // TODO (amitmanjhi): remove this code. use TypeOracle functionality
+    // instead.
+    protected boolean isValidPackage(String packageName, String filePath) {
+      logger.log(TreeLogger.SPAM, "packageName = " + packageName
+          + ", filePath = " + filePath, null);
+      if (packageName == null) {
+        return false;
+      }
+      int lastSlashPosition = filePath.lastIndexOf("/");
+      if (lastSlashPosition == -1) {
+        return false;
+      }
+      String dirPath = filePath.substring(0, lastSlashPosition);
+      String packageNameAsPath = packageName.replace('.', '/');
+      logger.log(TreeLogger.SPAM, "packageNameAsPath " + packageNameAsPath
+          + ", dirPath = " + dirPath, null);
+      return dirPath.endsWith(packageNameAsPath);
+    }
+  }
+
+  /**
+   * Class that specifies a set of {@link CompilationUnit} read from jar files.
+   */
+  private static class JarFileCompilationUnits extends CompilationUnits {
+    private final Set<String> excludedPaths;
+    private final Set<String> includedPaths;
+    private final JarFile jarFiles[];
+    private Set<CompilationUnit> units = null;
+
+    JarFileCompilationUnits(JarFile[] jarFiles, Set<String> includedPaths,
+        Set<String> excludedPaths, TreeLogger logger) {
+      super(logger);
+      this.jarFiles = jarFiles;
+      this.includedPaths = includedPaths;
+      this.excludedPaths = excludedPaths;
+    }
+
+    @Override
+    public Set<CompilationUnit> getCompilationUnits() throws IOException {
+      if (units != null) {
+        return units;
+      }
+
+      units = new HashSet<CompilationUnit>();
+      for (JarFile jarFile : jarFiles) {
+        Enumeration<JarEntry> entries = jarFile.entries();
+        while (entries.hasMoreElements()) {
+          JarEntry jarEntry = entries.nextElement();
+          String fileName = jarEntry.toString();
+          if (fileName.endsWith(".java") && isIncluded(fileName)) {
+            // add this compilation unit
+            String fileContent = getFileContentsFromJar(jarFile, jarEntry);
+            String packageName = extractPackageName(new StringReader(
+                fileContent));
+            if (packageName == null) {
+              logger.log(TreeLogger.WARN, "Not adding file = " + fileName
+                  + ", because packageName = null", null);
+            } else {
+              if (isValidPackage(packageName, fileName)) {
+                // Add if package and fileNames are okay
+                units.add(new StaticCompilationUnit(packageName + "."
+                    + getClassName(fileName), fileContent));
+                logger.log(TreeLogger.DEBUG, "adding pkgName = " + packageName
+                    + ", file = " + fileName, null);
+              } else {
+                logger.log(TreeLogger.SPAM, " not adding file " + fileName,
+                    null);
+              }
+            }
+          }
+        }
+      }
+      return units;
+    }
+
+    String getFileContentsFromJar(JarFile jarFile, JarEntry jarEntry)
+        throws IOException {
+      StringBuffer fileContent = new StringBuffer();
+      InputStream is = jarFile.getInputStream(jarEntry);
+      BufferedInputStream bis = new BufferedInputStream(is);
+      int length = 500;
+      byte buf[] = new byte[length];
+      int count = 0;
+      while ((count = bis.read(buf, 0, length)) != -1) {
+        fileContent.append(new String(buf, 0, count));
+        buf = new byte[length];
+      }
+      bis.close();
+      is.close();
+      return fileContent.toString();
+    }
+
+    private String getClassName(String fileName) {
+      int index = fileName.lastIndexOf("/");
+      int endOffset = 0;
+      if (fileName.endsWith(".java")) {
+        endOffset = 5;
+      }
+      return fileName.substring(index + 1, fileName.length() - endOffset);
+    }
+
+    private boolean isIncluded(String fileName) {
+      int index = fileName.length();
+      do {
+        fileName = fileName.substring(0, index);
+        if (includedPaths.contains(fileName)) {
+          return true;
+        }
+        if (excludedPaths.contains(fileName)) {
+          return false;
+        }
+        index = fileName.lastIndexOf("/");
+      } while (index != -1);
+      return false;
+    }
+  }
+
+  /**
+   * Class that specifies a set of {@link CompilationUnit} read from the
+   * file-system.
+   */
+  private static class SourceFileCompilationUnits extends CompilationUnits {
+    private final Set<String> excludedPaths;
+    private final Set<File> includedPaths;
+    private Set<CompilationUnit> units = null;
+
+    SourceFileCompilationUnits(String dirRoot,
+        Set<String> includedPathsAsString, Set<String> excludedPathsAsString,
+        TreeLogger logger) throws FileNotFoundException, IOException {
+      super(logger);
+      if (dirRoot == null) {
+        dirRoot = "";
+      }
+      includedPaths = new HashSet<File>();
+      for (String includedPath : includedPathsAsString) {
+        includedPaths.add(getFileFromName("source file: ", dirRoot
+            + includedPath));
+      }
+
+      excludedPaths = new HashSet<String>();
+      for (String excludedPath : excludedPathsAsString) {
+        excludedPaths.add(getFileFromName("excluded file: ",
+            dirRoot + excludedPath).getCanonicalPath());
+      }
+    }
+
+    @Override
+    public Set<CompilationUnit> getCompilationUnits() throws NotFoundException,
+        IOException, UnableToCompleteException {
+      if (units != null) {
+        return units;
+      }
+
+      units = new HashSet<CompilationUnit>();
+      for (File sourceFile : includedPaths) {
+        updateCompilationUnitsInPath(sourceFile);
+      }
+      return units;
+    }
+
+    private boolean isExcludedFile(String fileName) {
+      String pattern = "file:";
+      if (fileName.indexOf(pattern) == 0) {
+        fileName = fileName.substring(pattern.length());
+      }
+      return excludedPaths.contains(fileName);
+    }
+
+    private void updateCompilationUnitsInPath(File sourcePathEntry)
+        throws NotFoundException, IOException, UnableToCompleteException {
+      logger.log(TreeLogger.SPAM, "entering addCompilationUnitsInPath, file = "
+          + sourcePathEntry, null);
+      File[] files = sourcePathEntry.listFiles();
+      if (files == null) { // No files found.
+        return;
+      }
+
+      for (int i = 0; i < files.length; i++) {
+        final File file = files[i];
+        logger.log(TreeLogger.SPAM, "deciding the fate of file " + file, null);
+        // Ignore files like .svn and .cvs
+        if (file.getName().startsWith(".") || file.getName().equals("CVS")) {
+          continue;
+        }
+        if (isExcludedFile(file.getCanonicalPath())) {
+          // do not process the subtree
+          logger.log(TreeLogger.DEBUG,
+              "not traversing " + file.toURI().toURL(), null);
+          continue;
+        }
+        if (file.isFile()) {
+          String pkgName = null;
+          if (file.getName().endsWith("java")) {
+            pkgName = extractPackageName(new FileReader(file));
+            if (pkgName == null) {
+              logger.log(TreeLogger.WARN, "Not adding file = "
+                  + file.toString() + ", because packageName = null", null);
+            } else {
+              if (isValidPackage(pkgName,
+                  sourcePathEntry.toURI().toURL().toString())) {
+                // Add if the package and fileNames are okay
+                units.add(new FileCompilationUnit(file, pkgName));
+                logger.log(TreeLogger.DEBUG, "adding pkgName = " + pkgName
+                    + ", file = " + file.toString(), null);
+              } else {
+                logger.log(TreeLogger.SPAM, " not adding file "
+                    + file.toURI().toURL(), null);
+              }
+            }
+          }
+        } else {
+          // Recurse into subDirs
+          updateCompilationUnitsInPath(file);
+        }
+      }
+    }
+  }
+
   // currently doing only source_compatibility. true by default.
   public static final boolean API_SOURCE_COMPATIBILITY = true;
 
-  // prints which class the member was declared in plus the string message in
-  // ApiChange, false by default
-  public static final boolean DEBUG = false;
-
-  // prints the API of the two containers, false by default.
-  public static final boolean DEBUG_PRINT_ALL_API = false;
-
-  // these two parameters print APIs common in the two repositories. Should be
-  // false by default.
-  public static final boolean PRINT_COMPATIBLE = false;
-
-  public static final boolean PRINT_COMPATIBLE_WITH = false;
-  // for debugging. To see if TypeOracle builds
-  public static final boolean PROCESS_EXISTING_API = true;
-
-  public static final boolean PROCESS_NEW_API = true;
-  // true by default
-  public static final boolean REMOVE_NON_SUBCLASSABLE_ABSTRACT_CLASS_FROM_API = true;
-
-  public static final boolean FILTER_DUPLICATES = true;
+  /**
+   * prints which class the member was declared in plus the string message in
+   * ApiChange, false by default
+   */
+  public static boolean DEBUG = false;
 
   public static final boolean DEBUG_DUPLICATE_REMOVAL = false;
 
-  // Tweak for log output.
-  public static final TreeLogger.Type type = TreeLogger.WARN;
+  /**
+   * Flag for filtering duplicates, true by default. Just for debugging in rare
+   * cases.
+   */
+  public static final boolean FILTER_DUPLICATES = true;
+
+  /**
+   * Print APIs common in the two repositories. Should be false by default.
+   */
+  public static final boolean PRINT_COMPATIBLE = false;
+  /**
+   * Print APIs common in the two repositories. Should be false by default.
+   */
+  public static final boolean PRINT_COMPATIBLE_WITH = false;
+
+  /**
+   * Flag for debugging whether typeOracle builds.
+   */
+  public static final boolean PROCESS_EXISTING_API = true;
+
+  /**
+   * Flag for debugging whether typeOracle builds.
+   */
+  public static final boolean PROCESS_NEW_API = true;
+
+  // true by default
+  public static final boolean REMOVE_NON_SUBCLASSABLE_ABSTRACT_CLASS_FROM_API = true;
 
   // remove duplicates by default
   public static Collection<ApiChange> getApiDiff(ApiContainer newApi,
@@ -115,45 +436,70 @@
     return getApiDiff(apiDiff, whiteList, FILTER_DUPLICATES);
   }
 
-  // Call APIBuilders for each of the 2 source trees
   public static void main(String args[]) {
-
     try {
-      ApiContainer newApi = null, existingApi = null;
+      ApiCompatibilityChecker checker = new ApiCompatibilityChecker();
+      if (checker.processArgs(args)) {
+        ApiContainer newApi = null, existingApi = null;
 
-      if (args.length < 1) {
-        printHelp();
-        System.exit(-1);
-      }
+        AbstractTreeLogger logger = new PrintWriterTreeLogger();
+        logger.setMaxDetail(checker.type);
+        logger.log(TreeLogger.INFO, "gwtDevJar = " + checker.gwtDevJar
+            + ", userJar = " + checker.gwtUserJar + ", refjars = "
+            + checker.refJars + ", logLevel = " + checker.type
+            + ", printAllApi = " + checker.printAllApi, null);
 
-      AbstractTreeLogger logger = new PrintWriterTreeLogger();
-      logger.setMaxDetail(type);
-      if (PROCESS_NEW_API) {
-        newApi = new ApiContainer(args[0], "_new", logger);
-        if (ApiCompatibilityChecker.DEBUG_PRINT_ALL_API) {
-          logger.log(TreeLogger.INFO, newApi.getApiAsString()); // print the API
+        Set<String> excludedPackages = checker.getSetOfExcludedPackages(checker.configProperties);
+        if (PROCESS_NEW_API) {
+          Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+          units.addAll(new SourceFileCompilationUnits(
+              checker.configProperties.getProperty("dirRoot_new"),
+              checker.getConfigPropertyAsSet("sourceFiles_new"),
+              checker.getConfigPropertyAsSet("excludedFiles_new"), logger).getCompilationUnits());
+          units.addAll(checker.getGwtCompilationUnits(logger));
+          newApi = new ApiContainer(
+              checker.configProperties.getProperty("name_new"), units,
+              excludedPackages, logger);
+          if (checker.printAllApi) {
+            logger.log(TreeLogger.INFO, newApi.getApiAsString());
+          }
         }
-      }
-      if (PROCESS_EXISTING_API) {
-        existingApi = new ApiContainer(args[0], "_old", logger);
-        if (ApiCompatibilityChecker.DEBUG_PRINT_ALL_API) {
-          logger.log(TreeLogger.INFO, existingApi.getApiAsString());
+        if (PROCESS_EXISTING_API) {
+          Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+          if (checker.refJars == null) {
+            units.addAll(new SourceFileCompilationUnits(
+                checker.configProperties.getProperty("dirRoot_old"),
+                checker.getConfigPropertyAsSet("sourceFiles_old"),
+                checker.getConfigPropertyAsSet("excludedFiles_old"), logger).getCompilationUnits());
+          } else {
+            units.addAll(new JarFileCompilationUnits(checker.refJars,
+                checker.getConfigPropertyAsSet("sourceFiles_old"),
+                checker.getConfigPropertyAsSet("excludedFiles_old"), logger).getCompilationUnits());
+          }
+          units.addAll(checker.getGwtCompilationUnits(logger));
+          existingApi = new ApiContainer(
+              checker.configProperties.getProperty("name_old"), units,
+              excludedPackages, logger);
+          if (checker.printAllApi) {
+            logger.log(TreeLogger.INFO, existingApi.getApiAsString());
+          }
+        }
+
+        if (PROCESS_NEW_API && PROCESS_EXISTING_API) {
+          Collection<ApiChange> apiDifferences = getApiDiff(newApi,
+              existingApi, checker.whiteList);
+          for (ApiChange apiChange : apiDifferences) {
+            System.out.println(apiChange);
+          }
+          if (apiDifferences.size() == 0) {
+            System.out.println("API compatibility check SUCCESSFUL");
+          } else {
+            System.out.println("API compatibility check FAILED");
+          }
+          System.exit(apiDifferences.size() == 0 ? 0 : 1);
         }
       }
 
-      if (PROCESS_NEW_API && PROCESS_EXISTING_API) {
-        Collection<ApiChange> apiDifferences = getApiDiff(newApi, existingApi,
-            readWhiteListFromFile(args[0]));
-        for (ApiChange apiChange : apiDifferences) {
-          System.out.println(apiChange);
-        }
-        if (apiDifferences.size() == 0) {
-          System.out.println("API compatibility check SUCCESSFUL");
-        } else {
-          System.out.println("API compatibility check FAILED");
-        }
-        System.exit(apiDifferences.size() == 0 ? 0 : 1);
-      }
     } catch (Exception e) {
       // intercepting all exceptions in main, because I have to exit with -1 so
       // that the build breaks.
@@ -163,44 +509,6 @@
     }
   }
 
-  public static void printHelp() {
-    StringBuffer sb = new StringBuffer();
-    sb.append("java ApiCompatibilityChecker configFile\n");
-    sb.append("The ApiCompatibilityChecker tool requires a config file as an argument. ");
-    sb.append("The config file must specify two repositories of java source files: ");
-    sb.append("'_old' and '_new', which are to be compared for API source compatibility.\n");
-    sb.append("An optional whitelist is present at the end of ");
-    sb.append("the config file. The format of the whitelist is same as the output of ");
-    sb.append("the tool without the whitelist.\n");
-    sb.append("Each repository is specified by the following four properties:\n");
-    sb.append("name           specifies how the api should be refered to in the output\n");
-    sb.append("dirRoot        optional argument that specifies the base directory of all other file/directory names\n");
-    sb.append("sourceFiles    a colon-separated list of files/directories that specify the roots of the the filesystem trees to be included.\n");
-    sb.append("excludeFiles   a colon-separated lists of files/directories that specify the roots of the filesystem trees to be excluded");
-
-    sb.append("\n\n");
-    sb.append("Example api.conf file:\n");
-    sb.append("name_old         gwtEmulator");
-    sb.append("\n");
-    sb.append("dirRoot_old      ./");
-    sb.append("\n");
-    sb.append("sourceFiles_old  dev/core/super:user/super:user/src");
-    sb.append("\n");
-    sb.append("excludeFiles_old user/super/com/google/gwt/junit");
-    sb.append("\n\n");
-
-    sb.append("name_new         gwtEmulatorCopy");
-    sb.append("\n");
-    sb.append("dirRoot_new      ../gwt-14/");
-    sb.append("\n");
-    sb.append("sourceFiles_new  dev/core:user/super:user/src");
-    sb.append("\n");
-    sb.append("excludeFiles_new user/super/com/google/gwt/junit");
-    sb.append("\n\n");
-
-    System.out.println(sb.toString());
-  }
-
   // interface for testing, since do not want to build ApiDiff frequently
   static Collection<ApiChange> getApiDiff(ApiDiffGenerator apiDiff,
       Set<String> whiteList, boolean removeDuplicates) throws NotFoundException {
@@ -244,6 +552,330 @@
     return apiChangeList;
   }
 
+  private Properties configProperties;
+
+  private JarFile gwtDevJar;
+  private JarFile gwtUserJar;
+
+  // prints the API of the two containers, false by default.
+  private boolean printAllApi = false;
+  private JarFile refJars[];
+  // default log output
+  private TreeLogger.Type type = TreeLogger.WARN;
+  private Set<String> whiteList;
+
+  protected ApiCompatibilityChecker() {
+
+    /*
+     * register handlers for gwtDevJar, gwtUserJar, refJar, logLevel,
+     * printAllApi, configProperties
+     */
+
+    // handler for gwtDevJar
+    registerHandler(new ArgHandlerString() {
+
+      @Override
+      public String getPurpose() {
+        return "Path of the gwt dev jar";
+      }
+
+      @Override
+      public String getTag() {
+        return "-gwtDevJar";
+      }
+
+      @Override
+      public String[] getTagArgs() {
+        return new String[] {"gwt_dev_jar_path"};
+      }
+
+      @Override
+      public boolean setString(String str) {
+        gwtDevJar = getJarFromString(str);
+        return gwtDevJar != null;
+      }
+    });
+
+    // handler for gwtUserJar
+    registerHandler(new ArgHandlerString() {
+
+      @Override
+      public String getPurpose() {
+        return "Path of the gwt user jar";
+      }
+
+      @Override
+      public String getTag() {
+        return "-gwtUserJar";
+      }
+
+      @Override
+      public String[] getTagArgs() {
+        return new String[] {"gwt_user_jar_path"};
+      }
+
+      @Override
+      public boolean setString(String str) {
+        gwtUserJar = getJarFromString(str);
+        return gwtUserJar != null;
+      }
+    });
+
+    // handler for refJar
+    registerHandler(new ArgHandlerString() {
+      @Override
+      public String getPurpose() {
+        return "Path of the reference jar";
+      }
+
+      @Override
+      public String getTag() {
+        return "-refJar";
+      }
+
+      @Override
+      public String[] getTagArgs() {
+        return new String[] {"reference_jar_path"};
+      }
+
+      @Override
+      public boolean setString(String str) {
+        String refJarStrings[] = str.split(":");
+        refJars = new JarFile[refJarStrings.length];
+        int count = 0;
+        for (String refJarString : refJarStrings) {
+          refJars[count++] = getJarFromString(refJarString);
+          if (refJars[count - 1] == null) {
+            return false; // bail-out early
+          }
+        }
+        return refJars != null;
+      }
+    });
+
+    // handler for logLevel
+    registerHandler(new ArgHandlerString() {
+
+      @Override
+      public String getPurpose() {
+        return "Sets the log level of the TreeLogger";
+      }
+
+      @Override
+      public String getTag() {
+        return "-logLevel";
+      }
+
+      @Override
+      public String[] getTagArgs() {
+        String values[] = new String[TreeLogger.Type.values().length];
+        int count = 0;
+        for (TreeLogger.Type tempType : TreeLogger.Type.values()) {
+          values[count++] = tempType.name();
+        }
+        return values;
+      }
+
+      @Override
+      public boolean setString(String str) {
+        for (TreeLogger.Type tempType : TreeLogger.Type.values()) {
+          if (tempType.name().equals(str)) {
+            type = tempType;
+            return true;
+          }
+        }
+        return false;
+      }
+
+    });
+
+    // handler for printAllApi
+    registerHandler(new ArgHandlerFlag() {
+
+      @Override
+      public String getPurpose() {
+        return "Prints all api";
+      }
+
+      @Override
+      public String getTag() {
+        return "-printAllApi";
+      }
+
+      @Override
+      public boolean setFlag() {
+        printAllApi = true;
+        return true;
+      }
+
+    });
+
+    // handler for configFile
+    registerHandler(new ArgHandlerString() {
+
+      @Override
+      public String getPurpose() {
+        return "Path of the configuration file";
+      }
+
+      @Override
+      public String getTag() {
+        return "-configFile";
+      }
+
+      @Override
+      public String[] getTagArgs() {
+        return new String[] {"config_file_path"};
+      }
+
+      @Override
+      public boolean isRequired() {
+        return true;
+      }
+
+      @Override
+      public boolean setString(String str) {
+        setPropertiesAndWhitelist(str);
+        return configProperties != null && whiteList != null;
+      }
+    });
+  }
+
+  @Override
+  public void printHelp() {
+    super.printHelp();
+    StringBuffer sb = new StringBuffer();
+    sb.append("\n");
+    sb.append("The config file must specify two repositories of java source files: ");
+    sb.append("'_old' and '_new', which are to be compared for API source compatibility.\n");
+    sb.append("An optional whitelist is present at the end of ");
+    sb.append("the config file. The format of the whitelist is same as the output of ");
+    sb.append("the tool without the whitelist.\n");
+    sb.append("Each repository is specified by the following four properties:\n");
+    sb.append("name             specifies how the api should be refered to in the output\n");
+    sb.append("dirRoot          optional argument that specifies the base directory of all other file/directory names\n");
+    sb.append("sourceFiles      a colon-separated list of files/directories that specify the roots of the the filesystem trees to be included.\n");
+    sb.append("excludeFiles     a colon-separated lists of files/directories that specify the roots of the filesystem trees to be excluded");
+
+    sb.append("\n\n");
+    sb.append("Example api.conf file:\n");
+    sb.append("name_old         gwtEmulator");
+    sb.append("\n");
+    sb.append("dirRoot_old      ./");
+    sb.append("\n");
+    sb.append("sourceFiles_old  dev/core/super:user/super:user/src");
+    sb.append("\n");
+    sb.append("excludeFiles_old user/super/com/google/gwt/junit");
+    sb.append("\n\n");
+
+    sb.append("name_new         gwtEmulatorCopy");
+    sb.append("\n");
+    sb.append("dirRoot_new      ../gwt-14/");
+    sb.append("\n");
+    sb.append("sourceFiles_new  dev/core:user/super:user/src");
+    sb.append("\n");
+    sb.append("excludeFiles_new user/super/com/google/gwt/junit");
+    sb.append("\n\n");
+
+    System.err.println(sb.toString());
+  }
+
+  protected JarFile getJarFromString(String str) {
+    try {
+      return new JarFile(str);
+    } catch (IOException ex) {
+      System.err.println("exception in getting jar from name "
+          + ex.getMessage());
+      return null;
+    }
+  }
+
+  protected void setPropertiesAndWhitelist(String fileName)
+      throws IllegalArgumentException {
+    try {
+      // load config properties
+      FileInputStream fis = new FileInputStream(fileName);
+      configProperties = new Properties();
+      configProperties.load(fis);
+      fis.close();
+
+      // load whitelist
+      FileReader fr = new FileReader(fileName);
+      whiteList = readWhiteListFromFile(fr);
+      fr.close();
+    } catch (IOException ex) {
+      System.err.println("Have you specified the path of the config file correctly?");
+      throw new IllegalArgumentException(ex);
+    }
+  }
+
+  private Set<String> getConfigPropertyAsSet(String key) {
+    Set<String> set = new HashSet<String>();
+    String propertyValue = configProperties.getProperty(key);
+    if (propertyValue == null) {
+      return set;
+    }
+    for (String element : propertyValue.split(":")) {
+      element = element.trim();
+      set.add(element);
+    }
+    return set;
+  }
+
+  private Set<CompilationUnit> getGwtCompilationUnits(TreeLogger logger)
+      throws FileNotFoundException, IOException, NotFoundException,
+      UnableToCompleteException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    if (gwtDevJar == null || gwtUserJar == null) {
+      if (gwtDevJar != null) {
+        System.err.println("Argument gwtUserJar must be provided for gwtDevJar to be used");
+      }
+      if (gwtUserJar != null) {
+        System.err.println("Argument gwtDevJar must be provided for gwtUserJar to be used");
+      }
+      return units;
+    }
+    // gwt-user.jar
+    Set<String> gwtIncludedPaths = new HashSet<String>(
+        Arrays.asList(new String[] {"com/google/gwt"}));
+    Set<String> gwtExcludedPaths = new HashSet<String>(
+        Arrays.asList(new String[] {
+            "com/google/gwt/benchmarks",
+            "com/google/gwt/i18n/rebind",
+            "com/google/gwt/i18n/tools",
+            "com/google/gwt/json",
+            "com/google/gwt/junit",
+            "com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java",
+            "com/google/gwt/user/rebind", "com/google/gwt/user/server",
+            "com/google/gwt/user/tools",}));
+    CompilationUnits cu = new JarFileCompilationUnits(
+        new JarFile[] {gwtUserJar}, gwtIncludedPaths, gwtExcludedPaths, logger);
+    units.addAll(cu.getCompilationUnits());
+
+    // gwt-dev-*.jar
+    gwtIncludedPaths = new HashSet<String>(Arrays.asList(new String[] {
+        "com/google/gwt/core/client",
+        "com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang",
+        "com/google/gwt/lang",}));
+    cu = new JarFileCompilationUnits(new JarFile[] {gwtDevJar},
+        gwtIncludedPaths, null, logger);
+    units.addAll(cu.getCompilationUnits());
+    return units;
+  }
+
+  /*
+   * excludedPackages is used in only one place: to determine whether some class
+   * is an api class or not
+   */
+  private Set<String> getSetOfExcludedPackages(Properties config) {
+    String allExcludedPackages = config.getProperty("excludedPackages");
+    if (allExcludedPackages == null) {
+      allExcludedPackages = "";
+    }
+    String excludedPackagesArray[] = allExcludedPackages.split(":");
+    return new HashSet<String>(Arrays.asList(excludedPackagesArray));
+  }
+
   /**
    * Each whiteList element is an {@link ApiElement} and
    * {@link ApiChange.Status} separated by space. For example,
@@ -252,13 +884,8 @@
    * method on {@link ApiElement}.
    * 
    */
-  private static Set<String> readWhiteListFromFile(String fileName)
-      throws IOException {
-    if (fileName == null) {
-      throw new IllegalArgumentException("fileName is null");
-    }
+  private Set<String> readWhiteListFromFile(FileReader fr) throws IOException {
     Set<String> hashSet = new HashSet<String>();
-    FileReader fr = new FileReader(fileName);
     BufferedReader br = new BufferedReader(fr);
     String str = null;
     while ((str = br.readLine()) != null) {
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiContainer.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiContainer.java
index 8aeb887..40319b4 100644
--- a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiContainer.java
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiContainer.java
@@ -27,26 +27,17 @@
 import com.google.gwt.dev.javac.CompilationUnitInvalidator;
 import com.google.gwt.dev.javac.JdtCompiler;
 import com.google.gwt.dev.javac.TypeOracleMediator;
-import com.google.gwt.dev.javac.impl.FileCompilationUnit;
 
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileReader;
 import java.io.IOException;
-import java.net.MalformedURLException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Properties;
 import java.util.Set;
-import java.util.Vector;
 
 /**
  * {@link ApiContainer} Encapsulates an API.
@@ -57,91 +48,32 @@
   private final Map<JClassType, Boolean> apiClassCache = new HashMap<JClassType, Boolean>();
   private final Map<String, ApiPackage> apiPackages = new HashMap<String, ApiPackage>();
 
-  private final Set<String> excludedFiles = new HashSet<String>();
-  private final Set<String> excludedPackages = new HashSet<String>();
+  private final Set<String> excludedPackages;
   private final TreeLogger logger;
-
   private final String name;
 
-  private int numFilesCount = 0;
-  private Collection<File> sourceTrees = null;
   private final TypeOracle typeOracle;
 
   /**
-   * A public constructor to create an {@code ApiContainer} from a config file.
+   * A public constructor used for programmatic invocation and testing.
    * 
-   * @param fileName the config file
-   * @param suffix The code looks for the values of the properties: dirRoot,
-   *          name, sourceFiles, excludedFiles, ending in "suffix"
-   * @param logger The logger for the code.
+   * @param name Api name
+   * @param units a set of CompilationUnits
+   * @param excludedPackages a set of excludedPackages
+   * @param logger TreeLogger for logging messages
    * @throws IllegalArgumentException if one of the arguments is illegal
    * @throws UnableToCompleteException if there is a TypeOracle exception
    */
-  public ApiContainer(String fileName, String suffix, TreeLogger logger)
-      throws IllegalArgumentException, UnableToCompleteException {
+  ApiContainer(String name, Set<CompilationUnit> units,
+      Set<String> excludedPackages, TreeLogger logger)
+      throws UnableToCompleteException {
+    this.name = name;
     this.logger = logger;
-    FileInputStream fis = null;
-    if (fileName == null) {
-      throw new IllegalArgumentException("fileName is null");
-    }
+    logger.log(TreeLogger.INFO, "name = " + name + ", units.size = " + units.size(), null);
     try {
-      fis = new FileInputStream(fileName);
-      Properties config = new Properties();
-      config.load(fis);
-      String apiName = config.getProperty("name" + suffix);
-      String allSourceFiles = config.getProperty("sourceFiles" + suffix);
-      String allExcludedFiles = config.getProperty("excludedFiles" + suffix);
-      if (allExcludedFiles == null) {
-        allExcludedFiles = "";
-      }
-      String allExcludedPackages = config.getProperty("excludedPackages");
-      if (allExcludedPackages == null) {
-        allExcludedPackages = "";
-      }
-
-      if (apiName == null || allSourceFiles == null) {
-        throw new IllegalArgumentException(
-            "in apiContainer constructor, either name (" + apiName
-                + ") or sourceFiles (" + allSourceFiles + ") is null");
-      }
-      logger.log(TreeLogger.DEBUG, "read from config file " + fileName
-          + ", name = " + apiName + ", allSourceFiles = " + allSourceFiles
-          + ", allExcludedFiles = " + allExcludedFiles
-          + ", allExcludedPackages = " + allExcludedPackages, null);
-
-      String dirRoot = config.getProperty("dirRoot" + suffix);
-      if (dirRoot == null) {
-        dirRoot = "";
-      }
-      String sourceFilesArray[] = allSourceFiles.split(":");
-      Collection<File> fileCollection = new Vector<File>();
-      for (String tempStr : sourceFilesArray) {
-        tempStr = tempStr.trim();
-        checkFileExistence("source file: ", dirRoot + tempStr);
-        fileCollection.add(new File(dirRoot + tempStr));
-      }
-      logger.log(TreeLogger.DEBUG, "fileCollection " + fileCollection, null);
-      this.sourceTrees = fileCollection;
-
-      String excludedFilesArray[] = allExcludedFiles.split(":");
-      for (String excludedFile : excludedFilesArray) {
-        checkFileExistence("excluded file: ", dirRoot + excludedFile);
-      }
-      generateCanonicalFileSet(excludedFilesArray, dirRoot, excludedFiles);
-
-      String excludedPackagesArray[] = allExcludedPackages.split(":");
-      excludedPackages.addAll(Arrays.asList(excludedPackagesArray));
-      this.name = apiName;
-      this.typeOracle = createTypeOracleFromSources();
-      initializeApiPackages();
-    } catch (MalformedURLException e1) {
-      throw new IllegalArgumentException(e1);
+      this.typeOracle = getTypeOracleFromCompilationUnits(units);
     } catch (FileNotFoundException e2) {
-      if (fis == null) {
-        System.err.println("Have you specified the path of the config file correctly?");
-      } else {
-        System.err.println("Do you have a reference version of the API checked out?");
-      }
+      System.err.println("Do you have a reference version of the API checked out?");
       throw new IllegalArgumentException(e2);
     } catch (IOException e3) {
       throw new IllegalArgumentException(e3);
@@ -149,19 +81,7 @@
       logger.log(TreeLogger.ERROR, "logged a NotFoundException", e4);
       throw new UnableToCompleteException();
     }
-  }
-
-  /**
-   * Another public constructor. Used for programmatic invocation and testing.
-   * 
-   * @param name
-   * @param logger
-   * @param typeOracle
-   */
-  ApiContainer(String name, TreeLogger logger, TypeOracle typeOracle) {
-    this.name = name;
-    this.logger = logger;
-    this.typeOracle = typeOracle;
+    this.excludedPackages = excludedPackages;
     initializeApiPackages();
   }
 
@@ -241,65 +161,6 @@
     return isApiClass(classType) && isSubclassable(classType);
   }
 
-  private void addCompilationUnitsInPath(Set<CompilationUnit> units,
-      File sourcePathEntry) throws NotFoundException, IOException,
-      UnableToCompleteException {
-    logger.log(TreeLogger.SPAM, "entering addCompilationUnitsInPath, file = "
-        + sourcePathEntry, null);
-    File[] files = sourcePathEntry.listFiles();
-    if (files == null) {
-      // No files found.
-      return;
-    }
-
-    for (int i = 0; i < files.length; i++) {
-      final File file = files[i];
-      logger.log(TreeLogger.SPAM, "deciding the fate of file " + file, null);
-      // Ignore files like .svn and .cvs
-      if (file.getName().startsWith(".") || file.getName().equals("CVS")) {
-        continue;
-      }
-      if (isExcludedFile(file.getCanonicalPath())) {
-        // do not process the subtree
-        logger.log(TreeLogger.DEBUG, "not traversing "
-            + file.toURL().toString(), null);
-        continue;
-      }
-      if (file.isFile()) {
-        String pkgName = null;
-        if (file.getName().endsWith("java")) {
-          pkgName = extractPackageNameFromFile(file);
-          if (pkgName == null) {
-            logger.log(TreeLogger.WARN, "Not adding file = " + file.toString()
-                + ", because packageName = null", null);
-          } else {
-            logger.log(TreeLogger.DEBUG, "adding pkgName = " + pkgName
-                + ", file = " + file.toString(), null);
-          }
-        }
-        if (isValidPackage(pkgName, sourcePathEntry.toURL().toString())) {
-          // Add if it's a source file and the package and fileNames are okay
-          CompilationUnit unit = new FileCompilationUnit(file, pkgName);
-          units.add(unit);
-          numFilesCount++;
-        } else {
-          logger.log(TreeLogger.SPAM, " not adding file " + file.toURL(), null);
-        }
-      } else {
-        // Recurse into subDirs
-        addCompilationUnitsInPath(units, file);
-      }
-    }
-  }
-
-  private void checkFileExistence(String tag, String pathName)
-      throws FileNotFoundException {
-    File tempFile = new File(pathName);
-    if (!tempFile.exists()) {
-      throw new FileNotFoundException(tag + "file " + pathName + " not found");
-    }
-  }
-
   /**
    * Assumption: Clients may subclass an API class, but they will not add their
    * class to the package.
@@ -330,13 +191,9 @@
     return false;
   }
 
-  private TypeOracle createTypeOracleFromSources() throws NotFoundException,
-      IOException, UnableToCompleteException {
-    numFilesCount = 0;
-    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
-    for (File tempFile : sourceTrees) {
-      addCompilationUnitsInPath(units, tempFile);
-    }
+  private TypeOracle getTypeOracleFromCompilationUnits(
+      Set<CompilationUnit> units) throws NotFoundException, IOException,
+      UnableToCompleteException {
     JdtCompiler.compile(units);
     if (CompilationUnitInvalidator.invalidateUnitsWithErrors(logger, units)) {
       logger.log(TreeLogger.ERROR, "Unable to build typeOracle for "
@@ -347,55 +204,11 @@
     TypeOracleMediator mediator = new TypeOracleMediator();
     mediator.refresh(logger, units);
     logger.log(TreeLogger.INFO, "API " + name
-        + ", Finished with building typeOracle, added " + numFilesCount
+        + ", Finished with building typeOracle, added " + units.size()
         + " files", null);
     return mediator.getTypeOracle();
   }
 
-  private String extractPackageNameFromFile(File file) {
-    if (!file.exists()) {
-      return null;
-    }
-    String fileName = null;
-    try {
-      fileName = file.toURL().toString();
-      FileReader fr = new FileReader(file);
-      BufferedReader br = new BufferedReader(fr);
-      String str = null;
-      while ((str = br.readLine()) != null) {
-        if (str.indexOf("package") != 0) {
-          continue;
-        }
-        String parts[] = str.split("[\b\t\n\r ]+");
-        if ((parts.length == 2) && parts[0].equals("package")) {
-          return parts[1].substring(0, parts[1].length() - 1); // the ; char
-        }
-      }
-      return null;
-    } catch (Exception ex) {
-      logger.log(TreeLogger.ERROR,
-          "error in parsing and obtaining the packageName from file "
-              + fileName + "error's message " + ex.getMessage(), ex);
-      return null;
-    }
-  }
-
-  /**
-   * Convert a set into a HashMap for faster lookups.
-   */
-  private void generateCanonicalFileSet(String strArray[], String dirRoot,
-      Set<String> result) throws IOException {
-    if (strArray == null) {
-      return;
-    }
-    for (String str : strArray) {
-      str = str.trim();
-      File tempFile = new File(dirRoot + str);
-      str = tempFile.getCanonicalPath();
-      result.add(str);
-    }
-  }
-
   private boolean hasPublicOrProtectedConstructor(JClassType classType) {
     JConstructor[] constructors = classType.getConstructors();
     for (JConstructor constructor : constructors) {
@@ -431,6 +244,8 @@
     }
   }
 
+
+
   private boolean isAnySubtypeAnApiClass(JClassType classType) {
     JClassType subTypes[] = classType.getSubtypes();
     for (JClassType tempType : subTypes) {
@@ -469,14 +284,6 @@
     return false;
   }
 
-  private boolean isExcludedFile(String fileName) {
-    String pattern = "file:";
-    if (fileName.indexOf(pattern) == 0) {
-      fileName = fileName.substring(pattern.length());
-    }
-    return excludedFiles.contains(fileName);
-  }
-
   /**
    * @return returns true if classType is public AND an outer class
    */
@@ -488,20 +295,4 @@
     return !classType.isFinal() && hasPublicOrProtectedConstructor(classType);
   }
 
-  private boolean isValidPackage(String packageName, String filePath) {
-    logger.log(TreeLogger.SPAM, "packageName = " + packageName
-        + ", filePath = " + filePath, null);
-    if (packageName == null) {
-      return false;
-    }
-    int lastSlashPosition = filePath.lastIndexOf("/");
-    if (lastSlashPosition == -1) {
-      return false;
-    }
-    String dirPath = filePath.substring(0, lastSlashPosition);
-    String packageNameAsPath = packageName.replace('.', '/');
-    logger.log(TreeLogger.SPAM, "packageNameAsPath " + packageNameAsPath
-        + ", dirPath = " + dirPath, null);
-    return dirPath.endsWith(packageNameAsPath);
-  }
 }
diff --git a/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiCompatibilityTest.java b/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiCompatibilityTest.java
index cc4cba9..54117a9 100644
--- a/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiCompatibilityTest.java
+++ b/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiCompatibilityTest.java
@@ -18,12 +18,14 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.dev.javac.CompilationUnit;
 import com.google.gwt.dev.util.log.AbstractTreeLogger;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
-import com.google.gwt.tools.apichecker.ApiContainerTest.StaticCompilationUnit;
+import com.google.gwt.tools.apichecker.ApiCompatibilityChecker.StaticCompilationUnit;
 
 import junit.framework.TestCase;
 
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
 
@@ -59,7 +61,7 @@
             getSourceForRuntimeException()),};
   }
 
-  private static char[] getSourceForApiClass() {
+  private static String getSourceForApiClass() {
     StringBuffer sb = new StringBuffer();
     sb.append("package test.apicontainer;\n");
     sb.append("public class ApiClass extends NonApiClass {\n");
@@ -67,10 +69,10 @@
     sb.append("\tpublic void checkParametersAndReturnTypes(java.lang.Object x) throws java.lang.Throwable { };\n");
     sb.append("\tpublic final void checkParametersAndReturnTypesFinalVersion(java.lang.Object x) throws java.lang.Throwable { };\n");
     sb.append("};\n");
-    return sb.toString().toCharArray();
+    return sb.toString();
   }
 
-  private static char[] getSourceForNonApiClass() {
+  private static String getSourceForNonApiClass() {
     StringBuffer sb = new StringBuffer();
     sb.append("package test.apicontainer;\n");
     sb.append("class NonApiClass extends java.lang.Object {\n");
@@ -81,10 +83,10 @@
     sb.append("\tprotected ApiClassInNonApiClass() { }\n");
     sb.append("\t}\n");
     sb.append("}\n");
-    return sb.toString().toCharArray();
+    return sb.toString();
   }
 
-  private static char[] getSourceForObject() {
+  private static String getSourceForObject() {
     StringBuffer sb = new StringBuffer();
     sb.append("package java.lang;\n");
     sb.append("public class Object {\n");
@@ -97,10 +99,10 @@
     sb.append("\tprivate int internalField = 0;\n");
     sb.append("\tprotected static int protectedField=2;\n");
     sb.append("}\n");
-    return sb.toString().toCharArray();
+    return sb.toString();
   }
 
-  private static char[] getSourceForOneMoreApiClass() {
+  private static String getSourceForOneMoreApiClass() {
     StringBuffer sb = new StringBuffer();
     sb.append("package test.apicontainer;\n");
     sb.append("public class OneMoreApiClass extends java.lang.Object {\n");
@@ -108,32 +110,32 @@
     sb.append("\tprotected final void checkOverloadedMethodAccounted(test.apicontainer.ApiClass b) throws java.lang.Throwable { }\n");
     sb.append("\tpublic void testUncheckedExceptions() throws RuntimeException { }\n");
     sb.append("};\n");
-    return sb.toString().toCharArray();
+    return sb.toString();
   }
 
-  private static char[] getSourceForRuntimeException() {
+  private static String getSourceForRuntimeException() {
     StringBuffer sb = new StringBuffer();
     sb.append("package java.lang;\n");
     sb.append("public class RuntimeException extends Throwable {\n");
     sb.append("}\n");
-    return sb.toString().toCharArray();
+    return sb.toString();
   }
 
-  private static char[] getSourceForTestClass() {
+  private static String getSourceForTestClass() {
     StringBuffer sb = new StringBuffer();
     sb.append("package test.nonapipackage;\n");
     sb.append("class TestClass extends java.lang.Object {\n");
     sb.append("\tpublic void method() { }\n");
     sb.append("}\n");
-    return sb.toString().toCharArray();
+    return sb.toString();
   }
 
-  private static char[] getSourceForThrowable() {
+  private static String getSourceForThrowable() {
     StringBuffer sb = new StringBuffer();
     sb.append("package java.lang;\n");
     sb.append("public class Throwable extends Object {\n");
     sb.append("}\n");
-    return sb.toString().toCharArray();
+    return sb.toString();
   }
 
   ApiContainer api1 = null;
@@ -145,15 +147,14 @@
     AbstractTreeLogger logger = new PrintWriterTreeLogger();
     logger.setMaxDetail(TreeLogger.ERROR);
 
-    api1 = new ApiContainer("Api1", logger,
-        ApiContainerTest.getNewTypeOracleFromCompilationUnits(
-            ApiContainerTest.getScuArray(), logger));
-    apiSameAs1 = new ApiContainer("Api2", logger,
-        ApiContainerTest.getNewTypeOracleFromCompilationUnits(
-            ApiContainerTest.getScuArray(), logger));
-    api2 = new ApiContainer("Api2", logger,
-        ApiContainerTest.getNewTypeOracleFromCompilationUnits(getScuArray(),
-            logger));
+    api1 = new ApiContainer("Api1", new HashSet<CompilationUnit>(
+        Arrays.asList(ApiContainerTest.getScuArray())), new HashSet<String>(),
+        logger);
+    apiSameAs1 = new ApiContainer("ApiSameAs1", new HashSet<CompilationUnit>(
+        Arrays.asList(ApiContainerTest.getScuArray())), new HashSet<String>(),
+        logger);
+    api2 = new ApiContainer("Api2", new HashSet<CompilationUnit>(
+        Arrays.asList(getScuArray())), new HashSet<String>(), logger);
   }
 
   // setup is called before every test*. To avoid the overhead of setUp() each
diff --git a/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiContainerTest.java b/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiContainerTest.java
index bf21c88..8bba24c 100644
--- a/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiContainerTest.java
+++ b/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiContainerTest.java
@@ -17,12 +17,10 @@
 
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
-import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.javac.CompilationUnit;
-import com.google.gwt.dev.javac.JdtCompiler;
-import com.google.gwt.dev.javac.TypeOracleMediator;
 import com.google.gwt.dev.util.log.AbstractTreeLogger;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+import com.google.gwt.tools.apichecker.ApiCompatibilityChecker.StaticCompilationUnit;
 
 import junit.framework.TestCase;
 
@@ -31,51 +29,10 @@
 import java.util.Set;
 
 /**
- * Test ApiContainer.
+ * Test {@link ApiContainer}.
  */
 public class ApiContainerTest extends TestCase {
-  static class StaticCompilationUnit extends CompilationUnit {
 
-    private final char[] source;
-    private final String typeName;
-
-    public StaticCompilationUnit(String typeName, char[] source) {
-      this.typeName = typeName;
-      this.source = source;
-    }
-
-    @Override
-    public String getDisplayLocation() {
-      return "/mock/" + typeName;
-    }
-
-    @Override
-    public long getLastModified() {
-      return 0;
-    }
-
-    @Override
-    public String getSource() {
-      return String.valueOf(source);
-    }
-
-    @Override
-    public String getTypeName() {
-      return typeName;
-    }
-
-    @Override
-    public boolean isGenerated() {
-      return false;
-    }
-
-    @Override
-    public boolean isSuperSource() {
-      return false;
-    }
-  }
-
-  @SuppressWarnings("unused")
   class TestA {
     public TestA(String args) {
     }
@@ -104,19 +61,6 @@
     }
   }
 
-  // TODO (amitmanjhi): Try using UnitTestTreeLogger to capture log messages
-  public static TypeOracle getNewTypeOracleFromCompilationUnits(
-      StaticCompilationUnit tempScuArray[], AbstractTreeLogger logger)
-      throws UnableToCompleteException {
-
-    TypeOracleMediator mediator = new TypeOracleMediator();
-    Set<CompilationUnit> units = new HashSet<CompilationUnit>(
-        Arrays.asList(tempScuArray));
-    JdtCompiler.compile(units);
-    mediator.refresh(logger, units);
-    return mediator.getTypeOracle();
-  }
-
   public static StaticCompilationUnit[] getScuArray() {
     return new StaticCompilationUnit[] {
         new StaticCompilationUnit("test.apicontainer.ApiClass",
@@ -136,7 +80,7 @@
         new ApiAbstractMethod[0])[0]).getMethod();
   }
 
-  private static char[] getSourceForApiClass() {
+  private static String getSourceForApiClass() {
     StringBuffer sb = new StringBuffer();
     sb.append("package test.apicontainer;\n");
     sb.append("public class ApiClass extends NonApiClass {\n");
@@ -144,10 +88,10 @@
     sb.append("\tpublic java.lang.Object checkParametersAndReturnTypes(ApiClass a) { return this; };\n");
     sb.append("\tpublic final java.lang.Object checkParametersAndReturnTypesFinalVersion(ApiClass a) { return this; };\n");
     sb.append("};\n");
-    return sb.toString().toCharArray();
+    return sb.toString();
   }
 
-  private static char[] getSourceForNewObject() {
+  private static String getSourceForNewObject() {
     StringBuffer sb = new StringBuffer();
     sb.append("package java.lang;\n");
     sb.append("public class Object {\n");
@@ -156,10 +100,10 @@
     sb.append("}\n");
     sb.append("class Temp {\n");
     sb.append("}");
-    return sb.toString().toCharArray();
+    return sb.toString();
   }
 
-  private static char[] getSourceForNonApiClass() {
+  private static String getSourceForNonApiClass() {
     StringBuffer sb = new StringBuffer();
     sb.append("package test.apicontainer;\n");
     sb.append("class NonApiClass extends java.lang.Object {\n");
@@ -172,10 +116,10 @@
     sb.append("\tprivate AnotherApiClassInNonApiClass() { }\n");
     sb.append("\t}\n");
     sb.append("}\n");
-    return sb.toString().toCharArray();
+    return sb.toString();
   }
 
-  private static char[] getSourceForObject() {
+  private static String getSourceForObject() {
     StringBuffer sb = new StringBuffer();
     sb.append("package java.lang;\n");
     sb.append("public class Object {\n");
@@ -189,33 +133,33 @@
     sb.append("\tprivate int internalField = 0;\n");
     sb.append("\tprotected int protectedField=2;\n");
     sb.append("}\n");
-    return sb.toString().toCharArray();
+    return sb.toString();
   }
 
-  private static char[] getSourceForOneMoreApiClass() {
+  private static String getSourceForOneMoreApiClass() {
     StringBuffer sb = new StringBuffer();
     sb.append("package test.apicontainer;\n");
     sb.append("public class OneMoreApiClass extends java.lang.Object {\n");
     sb.append("\tprotected final void checkOverloadedMethodAccounted(test.apicontainer.OneMoreApiClass b) { }\n");
     sb.append("\tpublic void testUncheckedExceptions () { }\n");
     sb.append("};\n");
-    return sb.toString().toCharArray();
+    return sb.toString();
   }
 
-  private static char[] getSourceForTest() {
+  private static String getSourceForTest() {
     StringBuffer sb = new StringBuffer();
     sb.append("package java.newpackage;\n");
     sb.append("public class Test { }\n");
-    return sb.toString().toCharArray();
+    return sb.toString();
   }
 
-  private static char[] getSourceForTestClass() {
+  private static String getSourceForTestClass() {
     StringBuffer sb = new StringBuffer();
     sb.append("package test.nonapipackage;\n");
     sb.append("class TestClass extends java.lang.Object {\n");
     sb.append("\tpublic void method() { }\n");
     sb.append("}\n");
-    return sb.toString().toCharArray();
+    return sb.toString();
   }
 
   ApiContainer apiCheck = null;
@@ -230,13 +174,15 @@
   public void setUp() throws UnableToCompleteException {
     AbstractTreeLogger logger = new PrintWriterTreeLogger();
     logger.setMaxDetail(com.google.gwt.core.ext.TreeLogger.ERROR);
-    apiCheckLoop = new ApiContainer("ApiClassTest", logger,
-        getNewTypeOracleFromCompilationUnits(
-            new StaticCompilationUnit[] {new StaticCompilationUnit(
-                "java.lang.Object", getSourceForNewObject()),}, logger));
-
-    apiCheck = new ApiContainer("ApiContainerTest", logger,
-        getNewTypeOracleFromCompilationUnits(getScuArray(), logger));
+    apiCheckLoop = new ApiContainer(
+        "ApiClassTest",
+        new HashSet<CompilationUnit>(
+            Arrays.asList(new StaticCompilationUnit[] {new StaticCompilationUnit(
+                "java.lang.Object", getSourceForNewObject()),})),
+        new HashSet<String>(), logger);
+    apiCheck = new ApiContainer("ApiContainerTest",
+        new HashSet<CompilationUnit>(Arrays.asList(getScuArray())),
+        new HashSet<String>(), logger);
   }
 
   public void testEverything() {