Additional infrastructure for generating source code outside of a
GWT generator.

Review at http://gwt-code-reviews.appspot.com/1421805

Review by: unnurg@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10114 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/codegen/rebind/GwtCodeGenContext.java b/user/src/com/google/gwt/codegen/rebind/GwtCodeGenContext.java
new file mode 100644
index 0000000..7c04f43
--- /dev/null
+++ b/user/src/com/google/gwt/codegen/rebind/GwtCodeGenContext.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2011 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.codegen.rebind;
+
+import com.google.gwt.codegen.server.AbortablePrintWriter;
+import com.google.gwt.codegen.server.CodeGenContext;
+import com.google.gwt.codegen.server.JavaSourceWriterBuilder;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+
+import java.io.PrintWriter;
+
+/**
+ * {@link CodeGenContext} implementation for use within a GWT
+ * {@link com.google.gwt.core.ext.Generator}.
+ * <p>
+ * Experimental API - subject to change.
+ */
+public class GwtCodeGenContext implements CodeGenContext {
+
+  private final TreeLogger logger;
+  private final GeneratorContext ctx;
+
+  /**
+   * @param logger
+   * @param ctx
+   */
+  public GwtCodeGenContext(TreeLogger logger, GeneratorContext ctx) {
+    this.logger = logger;
+    this.ctx = ctx;
+  }
+
+  public JavaSourceWriterBuilder addClass(String pkgName, String className) {
+    return addClass(null, pkgName, className);
+  }
+
+  public JavaSourceWriterBuilder addClass(String superPath, String pkgName, String className) {
+    String superPkg = superPath == null ? pkgName : superPath + "." + pkgName;
+    final PrintWriter pw = ctx.tryCreate(logger, superPkg, className);
+    if (pw == null) {
+      return null;
+    }
+    return new JavaSourceWriterBuilder(new AbortablePrintWriter(pw) {
+      @Override
+      protected void onClose(boolean aborted) {
+        if (!aborted) {
+          ctx.commit(logger, pw);
+        }
+      }
+    }, pkgName, className);
+  }
+
+  public void error(String msg) {
+    logger.log(TreeLogger.ERROR, msg);
+  }
+
+  public void error(String msg, Throwable cause) {
+    logger.log(TreeLogger.ERROR, msg, cause);
+  }
+
+  public void error(Throwable cause) {
+    logger.log(TreeLogger.ERROR, cause.getMessage(), cause);
+  }
+
+  public void warn(String msg) {
+    logger.log(TreeLogger.WARN, msg);
+  }
+
+  public void warn(String msg, Throwable cause) {
+    logger.log(TreeLogger.WARN, msg, cause);
+  }
+
+  public void warn(Throwable cause) {
+    logger.log(TreeLogger.WARN, cause.getMessage(), cause);
+  }
+}
diff --git a/user/src/com/google/gwt/codegen/server/AbortablePrintWriter.java b/user/src/com/google/gwt/codegen/server/AbortablePrintWriter.java
new file mode 100644
index 0000000..112e9e2
--- /dev/null
+++ b/user/src/com/google/gwt/codegen/server/AbortablePrintWriter.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2011 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.codegen.server;
+
+import java.io.PrintWriter;
+
+/**
+ * Wrapper for a {@link PrintWriter} that adds the ability to abort creation
+ * and an onClose hook
+ * <p>
+ * Experimental API - subject to change.
+ */
+public class AbortablePrintWriter extends PrintWriter {
+
+  private boolean isClosed = false;
+
+  /**
+   * Wrap a {@link PrintWriter} instance.
+   * 
+   * @param pw
+   * @throws RuntimeException if there are reflection errors accessing the out
+   *     field in pw
+   */
+  public AbortablePrintWriter(PrintWriter pw) {
+    super(pw);
+  }
+
+  /**
+   * Abort creation of this output.
+   */
+  public void abort() {
+    if (!isClosed) {
+      flush();
+      super.close();
+      isClosed = true;
+      onClose(true);
+    }
+  }
+
+  @Override
+  public void close() {
+    if (!isClosed) {
+      flush();
+      super.close();
+      isClosed = true;
+      onClose(false);
+    }
+  }
+
+  /**
+   * Called exactly once when this {@link PrintWriter} is closed or aborted.
+   * 
+   * @param aborted
+   */
+  protected void onClose(boolean aborted) {
+    // Do nothing by default.
+  }
+}
diff --git a/user/src/com/google/gwt/codegen/server/CodeGenContext.java b/user/src/com/google/gwt/codegen/server/CodeGenContext.java
new file mode 100644
index 0000000..705ff97
--- /dev/null
+++ b/user/src/com/google/gwt/codegen/server/CodeGenContext.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2011 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.codegen.server;
+
+/**
+ * Context for code generators.
+ * <p>
+ * Experimental API - subject to change.
+ */
+public interface CodeGenContext {
+
+  /**
+   * An exception which can be thrown by a code generator to abort - callers of
+   * code generators should catch this exception.
+   */
+  class AbortCodeGenException extends RuntimeException {
+    public AbortCodeGenException() {
+      super();
+    }
+
+    public AbortCodeGenException(String msg) {
+      super(msg);
+    }
+
+    public AbortCodeGenException(String msg, Throwable cause) {
+      super(msg, cause);
+    }
+
+    public AbortCodeGenException(Throwable cause) {
+      super(cause);
+    }
+  }
+
+  /**
+   * Begin generating a new class.
+   * 
+   * @param pkgName
+   * @param className
+   * @return a {@link JavaSourceWriterBuilder} for the requested class or null if it
+   *     could not be created, such as if it already exists
+   */
+  JavaSourceWriterBuilder addClass(String pkgName, String className);
+
+  /**
+   * Begin generating a new class, possibly using GWT super-source.
+   * 
+   * @param superPath super-source prefix, or null if a regular class
+   * @param pkgName
+   * @param className
+   * @return a {@link JavaSourceWriterBuilder} for the requested class or null if it
+   *     could not be created, such as if it already exists
+   */
+  JavaSourceWriterBuilder addClass(String superPath, String pkgName, String className);
+
+  /**
+   * Log a fatal error during code generation.
+   * 
+   * @param msg
+   */
+  void error(String msg);
+
+  /**
+   * Log a fatal error during code generation.
+   * 
+   * @param msg
+   * @param cause
+   */
+  void error(String msg, Throwable cause);
+
+  /**
+   * Log a fatal error during code generation.
+   * 
+   * @param cause
+   */
+  void error(Throwable cause);
+
+  /**
+   * Log a non-fatal warning during code generation.
+   * 
+   * @param msg
+   */
+  void warn(String msg);
+
+  /**
+   * Log a non-fatal warning during code generation.
+   * 
+   * @param msg
+   * @param cause
+   */
+  void warn(String msg, Throwable cause);
+
+  /**
+   * Log a non-fatal warning during code generation.
+   * 
+   * @param cause
+   */
+  void warn(Throwable cause);
+}
diff --git a/user/src/com/google/gwt/codegen/server/CodeGenUtils.java b/user/src/com/google/gwt/codegen/server/CodeGenUtils.java
new file mode 100644
index 0000000..bc9107e
--- /dev/null
+++ b/user/src/com/google/gwt/codegen/server/CodeGenUtils.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2011 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.codegen.server;
+
+/**
+ * Helper class for code generators.
+ */
+public class CodeGenUtils {
+
+  /**
+   * Produce a string which represents a string literal in Java source,
+   * including null values.
+   * 
+   * @param literal
+   * @return Java source representation of the string literal
+   */
+  public static String asStringLiteral(String literal) {
+    return literal == null ? "null" : '"' + escape(literal) + '"';
+  }
+
+  /**
+   * Escapes string content to be a valid string literal.
+   * 
+   * @param unescaped 
+   * @return an escaped version of <code>unescaped</code>, suitable for being
+   *         enclosed in double quotes in Java source
+   */
+  public static String escape(String unescaped) {
+    int extra = 0;
+    for (int in = 0, n = unescaped.length(); in < n; ++in) {
+      switch (unescaped.charAt(in)) {
+        case '\0':
+        case '\n':
+        case '\r':
+        case '\"':
+        case '\\':
+          ++extra;
+          break;
+      }
+    }
+
+    if (extra == 0) {
+      return unescaped;
+    }
+
+    char[] oldChars = unescaped.toCharArray();
+    char[] newChars = new char[oldChars.length + extra];
+    for (int in = 0, out = 0, n = oldChars.length; in < n; ++in, ++out) {
+      char c = oldChars[in];
+      switch (c) {
+        case '\0':
+          newChars[out++] = '\\';
+          c = '0';
+          break;
+        case '\n':
+          newChars[out++] = '\\';
+          c = 'n';
+          break;
+        case '\r':
+          newChars[out++] = '\\';
+          c = 'r';
+          break;
+        case '\"':
+          newChars[out++] = '\\';
+          c = '"';
+          break;
+        case '\\':
+          newChars[out++] = '\\';
+          c = '\\';
+          break;
+      }
+      newChars[out] = c;
+    }
+
+    return String.valueOf(newChars);
+  }
+
+  private CodeGenUtils() {
+  }
+}
diff --git a/user/src/com/google/gwt/codegen/server/JavaSourceWriter.java b/user/src/com/google/gwt/codegen/server/JavaSourceWriter.java
new file mode 100644
index 0000000..0d91400
--- /dev/null
+++ b/user/src/com/google/gwt/codegen/server/JavaSourceWriter.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2011 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.codegen.server;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A mechanism to write Java source files.
+ * 
+ * @see JavaSourceWriterBuilder
+ * <p>
+ * Experimental API - subject to change.
+ */
+public class JavaSourceWriter extends SourceWriterBase {
+
+  private static final Pattern PKG_REGEX_BOTH = Pattern.compile("(com\\.google|javax?)\\..*");
+  private static final Pattern PKG_REGEX_GOOGLE = Pattern.compile("com\\.google\\..*");
+  private static final Pattern PKG_REGEX_JAVA = Pattern.compile("javax?\\..*");
+
+  private final AbortablePrintWriter printWriter;
+
+  /**
+   * @param printWriter
+   * @param targetPackageName
+   * @param imports
+   * @param isClass
+   * @param classJavaDocComment
+   * @param annotationDeclarations
+   * @param targetClassShortName
+   * @param superClassName
+   * @param interfaceNames
+   */
+  public JavaSourceWriter(AbortablePrintWriter printWriter, String targetPackageName,
+      Iterable<String> imports, boolean isClass, String classJavaDocComment,
+      Iterable<String> annotationDeclarations, String targetClassShortName, String superClassName,
+      Iterable<String> interfaceNames) {
+    this.printWriter = printWriter;
+    if (targetPackageName == null) {
+      throw new IllegalArgumentException("Cannot supply a null package name to"
+          + targetClassShortName);
+    }
+    // TODO: support a user-specified file header
+    if (targetPackageName.length() > 0) {
+      println("package " + targetPackageName + ";");
+    }
+
+    // Write imports, splitting into com.google, other, and java/javax groups
+    writeImportGroup(imports, PKG_REGEX_GOOGLE, true);
+    writeImportGroup(imports, PKG_REGEX_BOTH, false);
+    writeImportGroup(imports, PKG_REGEX_JAVA, true);
+
+    // Write class header
+    if (classJavaDocComment != null) {
+      beginJavaDocComment();
+      print(classJavaDocComment);
+      endJavaDocComment();
+    } else {
+      // beginJavaDocComment adds its own leading newline, make up for it here.
+      println();
+    }
+    for (String annotation : annotationDeclarations) {
+      println('@' + annotation);
+    }
+    if (isClass) {
+      emitClassDecl(targetClassShortName, superClassName, interfaceNames);
+    } else {
+      emitInterfaceDecl(targetClassShortName, superClassName, interfaceNames);
+    }
+    println(" {");
+    indent();
+  }
+
+  @Override
+  public void abort() {
+    printWriter.abort();
+  }
+
+  @Override
+  public void close() {
+    super.close();
+    printWriter.close();
+  }
+
+  @Override
+  protected void writeString(String s) {
+    printWriter.print(s);
+  }
+
+  private void emitClassDecl(String targetClassShortName,
+      String superClassName, Iterable<String> interfaceNames) {
+    print("public class " + targetClassShortName);
+    if (superClassName != null) {
+      print(" extends " + superClassName);
+    }
+    boolean first = true;
+    for (String interfaceName : interfaceNames) {
+      if (first) {
+        print(" implements ");
+        first = false;
+      } else {
+        print(", ");
+      }
+      print(interfaceName);
+    }
+  }
+
+  private void emitInterfaceDecl(String targetClassShortName,
+      String superClassName, Iterable<String> interfaceNames) {
+    if (superClassName != null) {
+      throw new IllegalArgumentException("Cannot set superclass name "
+          + superClassName + " on a interface.");
+    }
+    print("public interface " + targetClassShortName);
+    boolean first = true;
+    for (String interfaceName : interfaceNames) {
+      if (first) {
+        print(" extends ");
+        first = false;
+      } else {
+        print(", ");
+      }
+      print(interfaceName);
+    }
+  }
+
+  /**
+   * Write a group of imports matching or not matching a regex.
+   * 
+   * @param imports
+   * @param regex
+   * @param includeMatches true to include imports matching the regex, false to
+   *     include only those that don't match
+   */
+  private void writeImportGroup(Iterable<String> imports, Pattern regex, boolean includeMatches) {
+    boolean firstOfGroup = true;
+    for (String importEntry : imports) {
+      Matcher matcher = regex.matcher(importEntry);
+      if (matcher.matches() == includeMatches) {
+        if (firstOfGroup) {
+          println();
+          firstOfGroup = false;
+        }
+        println("import " + importEntry + ";");
+      }
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/codegen/server/JavaSourceWriterBuilder.java b/user/src/com/google/gwt/codegen/server/JavaSourceWriterBuilder.java
new file mode 100644
index 0000000..eb99420
--- /dev/null
+++ b/user/src/com/google/gwt/codegen/server/JavaSourceWriterBuilder.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2011 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.codegen.server;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * A builder for {@link JavaSourceWriter} instances.
+ * <p>
+ * Experimental API - subject to change.
+ */
+public class JavaSourceWriterBuilder {
+ 
+  private final String className;
+  private final String packageName;
+  private final AbortablePrintWriter printWriter;
+
+  private final List<String> annotations = new ArrayList<String>();
+
+  private boolean isClass = true;
+
+  private String classComment;
+
+  private final Set<String> imports = new TreeSet<String>();
+
+  private final Set<String> interfaceNames = new LinkedHashSet<String>();
+
+  private String superClassName;
+
+  /**
+   * @param printWriter
+   * @param packageName
+   * @param className
+   */
+  public JavaSourceWriterBuilder(AbortablePrintWriter printWriter, String packageName,
+      String className) {
+    this.printWriter = printWriter;
+    this.packageName = packageName;
+    this.className = className;
+  }
+
+  /**
+   * Add an class/interface annotation.
+   * 
+   * @param declaration
+   */
+  public void addAnnotationDeclaration(String declaration) {
+    annotations.add(declaration);
+  }
+
+  /**
+   * Add an implemented/extended interface.
+   * 
+   * @param intfName
+   */
+  public void addImplementedInterface(String intfName) {
+    interfaceNames.add(intfName);
+  }
+
+  /**
+   * Add an import entry.
+   * 
+   * @param typeName fully-qualified source name
+   */
+  public void addImport(String typeName) {
+    imports.add(typeName);
+  }
+
+  /**
+   * Creates an implementation of {@link JavaSourceWriter} that can be used to write
+   * the innards of a class. Note that the subsequent changes to this factory do
+   * not affect the returned instance.
+   * 
+   * @return a {@link JavaSourceWriter} instance
+   * @throws RuntimeException If the settings on this factory are inconsistent
+   *           or invalid
+   */
+  public SourceWriter createSourceWriter() {
+    return new JavaSourceWriter(printWriter, packageName, imports, isClass, classComment,
+        annotations, className, superClassName, interfaceNames);
+  }
+
+  /**
+   * Get the annotations.
+   * 
+   * @return list of annotations
+   */
+  public Iterable<String> getAnnotationDeclarations() {
+    return annotations;
+  }
+
+  /**
+   * Get the simple name of the class being created.
+   * 
+   * @return class name
+   */
+  public String getClassName() {
+    return className;
+  }
+
+  /**
+   * Get the fully-qualified source name of the class being created.
+   * 
+   * @return fqcn
+   */
+  public String getFullyQualifiedClassName() {
+    return getPackageName() + "." + getClassName();
+  }
+
+  /**
+   * Get the implemented/extended interfaces for the class being created.
+   * 
+   * @return list of interface names
+   */
+  public Iterable<String> getInterfaceNames() {
+    return interfaceNames;
+  }
+
+  /**
+   * Get the package of the class being created.
+   * 
+   * @return package name
+   */
+  public String getPackageName() {
+    return packageName;
+  }
+
+  /**
+   * Get the superclass for the class being created.
+   * 
+   * @return superclass name
+   */
+  public String getSuperclassName() {
+    return superClassName;
+  }
+
+  /**
+   * We are creating an interface instead of a class.
+   */
+  public void makeInterface() {
+    isClass = false;
+  }
+
+  /**
+   * Sets the java doc comment for <code>this</code>.
+   * 
+   * @param comment java doc comment.
+   */
+  public void setJavaDocCommentForClass(String comment) {
+    classComment = comment;
+  }
+
+  /**
+   * Set the superclass of the class being created.
+   * 
+   * @param superclassName
+   */
+  public void setSuperclass(String superclassName) {
+    superClassName = superclassName;
+  }
+}
diff --git a/user/src/com/google/gwt/codegen/server/LoggingCodeGenContext.java b/user/src/com/google/gwt/codegen/server/LoggingCodeGenContext.java
new file mode 100644
index 0000000..5980195
--- /dev/null
+++ b/user/src/com/google/gwt/codegen/server/LoggingCodeGenContext.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2011 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.codegen.server;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Base implementation of {@link CodeGenContext} which logs via
+ * {@link java.util.logging.Logger}.
+ * <p>
+ * Experimental API - subject to change.
+ */
+public abstract class LoggingCodeGenContext implements CodeGenContext {
+
+  private final Logger logger;
+
+  protected LoggingCodeGenContext() {
+    this(Logger.getAnonymousLogger());
+  }
+
+  protected LoggingCodeGenContext(String loggerName) {
+    this(Logger.getLogger(loggerName));
+  }
+
+  protected LoggingCodeGenContext(Logger logger) {
+    this.logger = logger;
+  }
+
+  public JavaSourceWriterBuilder addClass(String pkgName, String className) {
+    return addClass(null, pkgName, className);
+  }
+
+  public abstract JavaSourceWriterBuilder addClass(String superPkg, String pkgName,
+      String className);
+
+  public void error(String msg) {
+    logger.log(Level.SEVERE, msg);
+  }
+
+  public void error(String msg, Throwable cause) {
+    logger.log(Level.SEVERE, msg, cause);
+  }
+
+  public void error(Throwable cause) {
+    logger.log(Level.SEVERE, cause.getMessage(), cause);
+  }
+
+  public void warn(String msg) {
+    logger.log(Level.WARNING, msg);
+  }
+
+  public void warn(String msg, Throwable cause) {
+    logger.log(Level.WARNING, msg);
+  }
+
+  public void warn(Throwable cause) {
+    logger.log(Level.WARNING, cause.getMessage(), cause);
+  }
+}
diff --git a/user/src/com/google/gwt/codegen/server/SourceWriter.java b/user/src/com/google/gwt/codegen/server/SourceWriter.java
new file mode 100644
index 0000000..681bc0d
--- /dev/null
+++ b/user/src/com/google/gwt/codegen/server/SourceWriter.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2011 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.codegen.server;
+
+/**
+ * A mechanism to write source files.
+ * 
+ * @see JavaSourceWriterBuilder
+ * <p>
+ * Experimental API - subject to change.
+ */
+public interface SourceWriter {
+
+  /**
+   * Abort the source file being generated.
+   */
+  void abort();
+
+  /**
+   * Begin emitting a JavaDoc comment.
+   */
+  void beginJavaDocComment();
+
+  /**
+   * Close the source file being generated.
+   */
+  void close();
+
+  /**
+   * End emitting a JavaDoc comment.
+   */
+  void endJavaDocComment();
+
+  /**
+   * Increase indent level.
+   */
+  void indent();
+
+  /**
+   * Print a line at an increased indentation level without altering the indent
+   * level for the next line.
+   * 
+   * @param string
+   */
+  void indentln(String string);
+
+  /**
+   * Format and print a line at an increased indentation level without altering
+   * the indent level for the next line.
+   * 
+   * @param format format string, as in {@link String#format(String, Object...)}
+   * @param args arguments for the format string
+   */
+  void indentln(String format, Object... args);
+
+  /**
+   * Decrease indent level.
+   */
+  void outdent();
+
+  /**
+   * Write a string without a line terminator.
+   * 
+   * @param s
+   */
+  void print(String s);
+
+  /**
+   * Format and print a string without a line terminator.
+   * 
+   * @param format format string, as in {@link String#format(String, Object...)}
+   * @param args arguments for the format string
+   */
+  void print(String format, Object... args);
+
+  /**
+   * Write a line terminator.
+   */
+  void println();
+
+  /**
+   * Write a string with a line terminator.
+   * 
+   * @param string
+   */
+  void println(String string);
+
+  /**
+   * Format and print a string with a line terminator.
+   * 
+   * @param format format string, as in {@link String#format(String, Object...)}
+   * @param args arguments for the format string
+   */
+  void println(String format, Object... args);
+}
diff --git a/user/src/com/google/gwt/codegen/server/SourceWriterBase.java b/user/src/com/google/gwt/codegen/server/SourceWriterBase.java
new file mode 100644
index 0000000..d5c9def
--- /dev/null
+++ b/user/src/com/google/gwt/codegen/server/SourceWriterBase.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2011 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.codegen.server;
+
+/**
+ * Base implementation of {@link SourceWriter} that implements all the indenting
+ * and keeping track of comments.
+ * <p>
+ * Experimental API - subject to change.
+ */
+public abstract class SourceWriterBase implements SourceWriter {
+
+  private boolean atStart;
+
+  /**
+   * Are we currently in a comment?
+   */
+  private boolean inComment;
+
+  private int indent;
+
+  public abstract void abort();
+
+  public void beginJavaDocComment() {
+    println("\n/**");
+    inComment = true;
+  }
+
+  public void close() {
+    outdent();
+    println("}");
+  }
+
+  public void endJavaDocComment() {
+    inComment = false;
+    println("\n */");
+  }
+
+  public void indent() {
+    indent++;
+  }
+
+  public void indentln(String string) {
+    indent();
+    println(string);
+    outdent();
+  }
+
+  public void indentln(String format, Object... args) {
+    indentln(String.format(format, args));
+  }
+
+  public void outdent() {
+    if (indent > 0) {
+      --indent;
+    }
+  }
+
+  public void print(String s) {
+    // If we just printed a newline, print an indent.
+    //
+    if (atStart) {
+      for (int j = 0; j < indent; ++j) {
+        writeString("  ");
+      }
+      if (inComment) {
+        writeString(" * ");
+      }
+      atStart = false;
+    }
+    // Now print up to the end of the string or the next newline.
+    //
+    String rest = null;
+    int i = s.indexOf("\n");
+    if (i > -1 && i < s.length() - 1) {
+      rest = s.substring(i + 1);
+      s = s.substring(0, i + 1);
+    }
+    writeString(s);
+    // If rest is non-null, then s ended with a newline and we recurse.
+    //
+    if (rest != null) {
+      atStart = true;
+      print(rest);
+    }
+  }
+
+  public void print(String format, Object... args) {
+    print(String.format(format, args));
+  }
+
+  public void println() {
+    print("\n");
+    atStart = true;
+  }
+
+  public void println(String string) {
+    print(string);
+    println();
+  }
+
+  public void println(String format, Object... args) {
+    println(String.format(format, args));
+  }
+
+  /**
+   * Write a string to the underlying output.
+   * 
+   * @param s
+   */
+  protected abstract void writeString(String s);
+}
diff --git a/user/src/com/google/gwt/codegen/server/StringSourceWriter.java b/user/src/com/google/gwt/codegen/server/StringSourceWriter.java
new file mode 100644
index 0000000..67092d2
--- /dev/null
+++ b/user/src/com/google/gwt/codegen/server/StringSourceWriter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 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.codegen.server;
+
+/**
+ * A SourceWriter that accumulates source and returns it in the
+ * {@link #toString()} method.
+ * <p>
+ * Experimental API - subject to change.
+ */
+public class StringSourceWriter extends SourceWriterBase {
+
+  private final StringBuilder buf = new StringBuilder();
+  private boolean aborted = false;
+
+  @Override
+  public void abort() {
+    aborted = true;
+  }
+
+  @Override
+  public String toString() {
+    return aborted ? null : buf.toString();
+  }
+
+  @Override
+  protected void writeString(String s) {
+    buf.append(s);
+  }
+}