Tweaking the code generator to do imports and qualified names properly.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@436 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/extensions/compiletime/compiletime.iml b/extensions/compiletime/compiletime.iml
index 0a650bf..64bbec6 100644
--- a/extensions/compiletime/compiletime.iml
+++ b/extensions/compiletime/compiletime.iml
@@ -7,7 +7,7 @@
       <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
     </content>
     <content url="file://$MODULE_DIR$/../../generatedsrc">
-      <sourceFolder url="file://$MODULE_DIR$/../../generatedsrc" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/../../generatedsrc" isTestSource="true" />
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
diff --git a/extensions/compiletime/src/com/google/inject/CodeGenReflectionFactory.java b/extensions/compiletime/src/com/google/inject/CodeGenReflectionFactory.java
index c4f9676..9308407 100644
--- a/extensions/compiletime/src/com/google/inject/CodeGenReflectionFactory.java
+++ b/extensions/compiletime/src/com/google/inject/CodeGenReflectionFactory.java
@@ -20,10 +20,10 @@
 import com.google.inject.internal.*;
 import static com.google.inject.internal.Objects.nonNull;
 
-import java.io.*;
+import java.io.File;
+import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Member;
-import java.lang.reflect.Type;
 import java.util.*;
 
 /**
@@ -92,15 +92,10 @@
    * Writes generated .java files to {@code generatedSourceDirectory}.
    */
   void writeToFile(File generatedSourceDirectory) throws IOException {
-    File directory = generatedSourceDirectory;
-    for (String packagePart : new String[] { "com", "google", "inject" }) {
-      directory = new File(directory, packagePart);
-    }
-    directory.mkdirs();
-    File sourceFile = new File(directory, generatedClassSimpleName() + ".java");
-    Writer writer = new OutputStreamWriter(new FileOutputStream(sourceFile), "ISO-8859-1");
-    new ClassWriter(writer).writeClass();
-    writer.close();
+    JavaCodeGenerator out = JavaCodeGenerator.open(generatedSourceDirectory,
+        generatedCodePackage, generatedClassSimpleName());
+    new ClassWriter(out).writeClass();
+    out.close();
   }
 
   private String generatedClassSimpleName() {
@@ -108,32 +103,40 @@
   }
 
   private class ClassWriter {
-    final Writer writer;
+    final JavaCodeGenerator out;
 
-    ClassWriter(Writer writer) {
-      this.writer = writer;
+    ClassWriter(JavaCodeGenerator out) {
+      this.out = out;
     }
 
     void writeClass() throws IOException {
-      writeLine("// Generated by Guice. Do not edit!");
-      writeLine("package %s;", generatedCodePackage);
-      writeLine();
-      writeLine("import %s;", typeName(Reflection.class));
-      writeLine("import %s;", typeName(ConstructionProxy.class));
-      writeLine("import %s;", typeName(InvocationTargetException.class));
-      writeLine("import %s;", typeName(Parameter.class));
-      writeLine("import %s;", typeName(List.class));
-      writeLine("import %s;", typeName(Member.class));
-      writeLine("import %s;", typeName(Parameter.class));
-      writeLine("import %s;", typeName(Nullability.class));
-      writeLine("import %s;", typeName(Arrays.class));
-      writeLine("import %s;", typeName(Key.class));
-      writeLine();
-      writeLine("public class %s implements Reflection {", generatedClassSimpleName());
-      writeLine();
+      out.writePackageHeader();
+      out.writeImport(Reflection.class);
+      out.writeImport(ConstructionProxy.class);
+      out.writeImport(InvocationTargetException.class);
+      out.writeImport(Parameter.class);
+      out.writeImport(List.class);
+      out.writeImport(Member.class);
+      out.writeImport(Parameter.class);
+      out.writeImport(Nullability.class);
+      out.writeImport(Arrays.class);
+      out.writeImport(SuppressWarnings.class);
+      out.writeImport(Key.class);
+      out.writeImport(IllegalArgumentException.class);
+      out.writeImport(Object.class);
+      for (Map.Entry<Class<?>, ConstructionProxy<?>> entry : constructionProxies.entrySet()) {
+        out.writeImport(entry.getKey());
+        for (Parameter<?> parameter : entry.getValue().getParameters()) {
+          out.writeImport(parameter.getKey().getTypeLiteral().getType());
+        }
+      }
+
+      out.writeLine();
+      out.openScope("public class %s implements Reflection {", generatedClassSimpleName());
+      out.writeLine();
       writeGetConstructionProxy();
-      writeLine();
-      writeLine("}");
+      out.writeLine();
+      out.closeScope("}");
     }
 
     String keyLiteral(Key<?> key) {
@@ -143,72 +146,49 @@
       if (key.getAnnotationType() != null) {
         throw new UnsupportedOperationException("TODO");
       }
-      return String.format("Key.get(%s.class)", typeName(key.getTypeLiteral().getType()));
+      return String.format("Key.get(%s.class)", out.typeName(key.getTypeLiteral().getType()));
     }
 
-    String typeName(Type type) {
-      if (type instanceof Class<?>) {
-        Class<?> clas = (Class<?>) type;
-        StringBuilder result = new StringBuilder();
-        result.append(clas.getPackage().getName());
-        for (Class<?> enclosing = clas.getEnclosingClass(); enclosing != null;
-            enclosing = enclosing.getEnclosingClass()) {
-          result.append(".").append(enclosing.getSimpleName());
-        }
-        result.append(".").append(clas.getSimpleName());
-        return result.toString();
-
-      } else {
-        throw new UnsupportedOperationException();
-      }
-    }
 
     void writeGetConstructionProxy() throws IOException {
-      writeLine("  public <T> ConstructionProxy<T> getConstructionProxy(Class<T> implementation) {");
+      out.writeLine("@%s(\"unchecked\")", SuppressWarnings.class);
+      out.openScope("public <T> ConstructionProxy<T> getConstructionProxy(Class<T> implementation) {");
 
       for (Map.Entry<Class<?>, ConstructionProxy<?>> entry : constructionProxies.entrySet()) {
-        String implementation = typeName(entry.getKey());
-        writeLine("    if (implementation == %s.class) {", implementation);
-        writeLine("      return (ConstructionProxy) new ConstructionProxy<%s>() {", implementation);
-        writeLine("        public %s newInstance(final Object... arguments) throws InvocationTargetException {", implementation);
-        writeLine("          return new %s(", implementation);
+        Class<?> implementation = entry.getKey();
+        out.openScope("if (implementation == %s.class) {", implementation);
+        out.openScope("return (%s) new %s<%s>() {", ConstructionProxy.class, ConstructionProxy.class, implementation);
+        out.openScope("public %s newInstance(final %s... arguments) throws %s {", implementation, Object.class, InvocationTargetException.class);
+        out.openScope("return new %s(", implementation);
         int argument = 0;
         for (Iterator<Parameter<?>> i = entry.getValue().getParameters().iterator(); i.hasNext(); ) {
           Parameter<?> parameter = i.next();
           String separator = i.hasNext() ? "," : "";
-          writeLine("              (%s) arguments[%d]%s", typeName(parameter.getKey().getTypeLiteral().getType()), argument, separator);
+          out.writeLine("(%s) arguments[%d]%s", parameter.getKey().getTypeLiteral().getType(), argument, separator);
           argument++;
         }
-        writeLine("          );");
-        writeLine("        }");
-        writeLine("        public List<Parameter<?>> getParameters() {");
-        writeLine("          return Arrays.<Parameter<?>>asList(");
+        out.closeScope(");");
+        out.closeScope("}");
+        out.openScope("public %s<%s<?>> getParameters() {", List.class, Parameter.class);
+        out.openScope("return %s.<%s<?>>asList(", Arrays.class, Parameter.class);
+        argument = 0;
         for (Iterator<Parameter<?>> i = entry.getValue().getParameters().iterator(); i.hasNext(); ) {
           Parameter<?> parameter = i.next();
           String separator = i.hasNext() ? "," : "";
-          writeLine("              Parameter.create(%s, %s, Nullability.%s)%s", argument, keyLiteral(parameter.getKey()), parameter.getNullability(), separator);
+          out.writeLine("%s.create(%s, %s, %s.%s)%s", Parameter.class, argument, keyLiteral(parameter.getKey()), Nullability.class, parameter.getNullability(), separator);
           argument++;
         }
-        writeLine("          );");
-        writeLine("        }");
-        writeLine("        public Member getMember() {");
-        writeLine("          return null;");
-        writeLine("        }");
-        writeLine("      };");
-        writeLine("    }");
+        out.closeScope(");");
+        out.closeScope("}");
+        out.openScope("public %s getMember() {", Member.class);
+        out.writeLine("return null;");
+        out.closeScope("}");
+        out.closeScope("};");
+        out.closeScope("}");
       }
-      writeLine();
-      writeLine("    throw new IllegalArgumentException();");
-      writeLine("  }");
-    }
-
-    void writeLine(String format, Object... args) throws IOException {
-      writer.append(String.format(format, args));
-      writeLine();
-    }
-
-    void writeLine() throws IOException {
-      writer.append("\n");
+      out.writeLine();
+      out.writeLine("throw new %s();", IllegalArgumentException.class);
+      out.closeScope("}");
     }
   }
 }
diff --git a/extensions/compiletime/src/com/google/inject/JavaCodeGenerator.java b/extensions/compiletime/src/com/google/inject/JavaCodeGenerator.java
new file mode 100644
index 0000000..cd2ed09
--- /dev/null
+++ b/extensions/compiletime/src/com/google/inject/JavaCodeGenerator.java
@@ -0,0 +1,136 @@
+/**
+ * Copyright (C) 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.inject;
+
+import java.io.*;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class JavaCodeGenerator {
+
+  private final Writer writer;
+  private final String packageName;
+  private int indent = 0;
+
+  /**
+   * Imported classes, by their simple name. If multiple classes with the same
+   * simplename have been imported, the fully qualified names will be used for
+   * all but one of those classes.
+   */
+  private final Map<String, Class<?>> importedClasses = new HashMap<String, Class<?>>();
+
+  private JavaCodeGenerator(Writer writer, String packageName) {
+    this.writer = writer;
+    this.packageName = packageName;
+  }
+
+  public static JavaCodeGenerator open(File generatedSourceDirectory,
+      String packageName, String topLevelClassName) throws IOException {
+    File directory = generatedSourceDirectory;
+    for (String packagePart : packageName.split("\\.")) {
+      directory = new File(directory, packagePart);
+    }
+    directory.mkdirs();
+    File sourceFile = new File(directory, topLevelClassName + ".java");
+    return new JavaCodeGenerator(
+        new OutputStreamWriter(new FileOutputStream(sourceFile), "ISO-8859-1"), packageName);
+  }
+
+  public void writePackageHeader() throws IOException {
+    writeLine("// Generated by Guice. Do not edit!");
+    writeLine("package %s;", packageName);
+    writeLine();
+  }
+
+  public void writeImport(Type type) throws IOException {
+    if (!(type instanceof Class)) {
+      throw new UnsupportedOperationException();
+    }
+
+    Class<?> clas = (Class<?>) type;
+
+    if (importedClasses.containsKey(clas.getSimpleName())) {
+      return;
+    }
+
+    if (!"java.lang".equals(clas.getPackage().getName())) {
+      writeLine("import %s;", typeName(type));
+    }
+    importedClasses.put(clas.getSimpleName(), clas);
+  }
+
+  public String typeName(Type type) {
+    if (type instanceof Class<?>) {
+      Class<?> clas = (Class<?>) type;
+      if (importedClasses.get(clas.getSimpleName()) == type) {
+        return clas.getSimpleName();
+      }
+      StringBuilder result = new StringBuilder();
+      result.append(clas.getPackage().getName());
+      for (Class<?> enclosing = clas.getEnclosingClass(); enclosing != null;
+          enclosing = enclosing.getEnclosingClass()) {
+        result.append(".").append(enclosing.getSimpleName());
+      }
+      result.append(".").append(clas.getSimpleName());
+      return result.toString();
+
+    } else {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  private Object[] processArgs(Object... args) {
+    args = args.clone();
+    for (int i = 0; i < args.length; i++) {
+      if (args[i] instanceof Class<?>) {
+        args[i] = typeName((Class<?>) args[i]);
+      }
+    }
+    return args;
+  }
+
+  public void writeLine(String format, Object... args) throws IOException {
+    for (int i = 0; i < indent; i++) {
+      writer.append("  ");
+    }
+    writer.append(String.format(format, processArgs(args)));
+    writeLine();
+  }
+
+  public void openScope(String format, Object... args) throws IOException {
+    writeLine(format, args);
+    indent++;
+  }
+
+  public void closeScope(String format, Object... args) throws IOException {
+    indent--;
+    writeLine(format, args);
+  }
+
+  public void writeLine() throws IOException {
+    writer.append("\n");
+  }
+
+  public void close() throws IOException {
+    writer.close();
+  }
+}