Merge "Upgrade javapoet to javapoet-1.12.1" am: c658b69bb6 am: ec30e84294 am: 52b44f4f61 am: 86f627dac2 am: 985c07c5dd

Change-Id: I21ad3753352c98e14d6b347138fc016e3e48d158
diff --git a/.buildscript/deploy_snapshot.sh b/.buildscript/deploy_snapshot.sh
index 395a873..f25e18d 100755
--- a/.buildscript/deploy_snapshot.sh
+++ b/.buildscript/deploy_snapshot.sh
@@ -6,7 +6,7 @@
 # https://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/
 
 SLUG="square/javapoet"
-JDK="oraclejdk8"
+JDK="openjdk8"
 BRANCH="master"
 
 set -e
diff --git a/.travis.yml b/.travis.yml
index 4e0a881..af37274 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,27 +2,13 @@
 
 matrix:
   include:
-    - env: JDK='Oracle JDK 8'
-      jdk: oraclejdk8
-    - env: JDK='Oracle JDK 9'
-      jdk: oraclejdk9
-    - env: JDK='Oracle JDK 10'
-      install: . ./install-jdk.sh -F 10 -L BCL
-    - env: JDK='OpenJDK 10'
-      install: . ./install-jdk.sh -F 10 -L GPL
-    - env: JDK='Oracle JDK 11'
-      install: . ./install-jdk.sh -F 11 -L BCL
-    - env: JDK='OpenJDK 11'
-      install: . ./install-jdk.sh -F 11 -L GPL
+    - jdk: openjdk8
+    - jdk: openjdk11
   allow_failures:
-    # ErrorProne/javac is not yet working on JDK 11
-    - env: JDK='Oracle JDK 11'
-    - env: JDK='OpenJDK 11'
+    - jdk: openjdk11
 
-# Direct usage of `install-jdk.sh` might be superseded by https://github.com/travis-ci/travis-build/pull/1347
 before_install:
   - unset _JAVA_OPTIONS
-  - wget https://github.com/sormuras/bach/raw/1.0.1/install-jdk.sh
 
 after_success:
   - .buildscript/deploy_snapshot.sh
@@ -39,8 +25,6 @@
 notifications:
   email: false
 
-sudo: false
-
 cache:
   directories:
     - $HOME/.m2
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3933dd0..e02a975 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,30 @@
 Change Log
 ==========
 
+JavaPoet 1.12.0 *(2020-01-09)*
+-----------------------------
+
+ * New: Add `JavaFile.writeToPath()` and `JavaFile.writeToFile()` methods that return paths to the 
+   generated file as `Path` and `File` respectively.
+ * New: Add `TypeSpec.alwaysQualify()` API to avoid clashes involving nested type names.
+ * New: Add overloads accepting `CodeBlock`s to `MethodSpec`'s control flow methods.
+ * New: Make list fields of all `Builder` types mutable.
+ * New: Add `CodeBlock.clear()`.
+ * New: Allow passing a custom `Charset` to `JavaFile.writeTo()`.
+ * New: Improved performance of `ClassName.simpleNames()` by memoizing results.
+ * New: Significant performance improvements for `CodeWriter.resolve()` as all nested simple names 
+   of a `TypeSpec` get pre-computed.
+ * New: Add `TypeName.Builder.setName()` to allow overriding names passed in the constructor.
+ * New: Add `TypeName.canonicalName()`.
+ * Fix: Use `\\R` instead of `\n` as line separator in `CodeWriter.emitAndIndent()`.
+ * Fix: Copy originating elements in `TypeSpec.toBuilder()`.
+ * Fix: Ensure trailing newlines in Javadocs and method bodies.
+ * Fix: Copy annotations when creating a `ParameterSpec` from a `VariableElement`.
+ * Fix: Properly handle classes located in empty packages in `ClassName`.
+ * Fix: Only allow `final` modifier on a `ParameterSpec`.
+ * Fix: Use fully-qualified names for type names that are masked by type variable names.
+
+
 JavaPoet 1.11.1 *(2018-05-16)*
 -----------------------------
 
diff --git a/METADATA b/METADATA
index ff1fa49..673bc9c 100644
--- a/METADATA
+++ b/METADATA
@@ -1,7 +1,5 @@
 name: "JavaPoet"
-description:
-    "A Java API for generating .java source files"
-
+description: "A Java API for generating .java source files"
 third_party {
   url {
     type: HOMEPAGE
@@ -11,7 +9,10 @@
     type: GIT
     value: "https://github.com/square/javapoet.git"
   }
-  version: "javapoet-1.11.1"
-  last_upgrade_date { year: 2018 month: 11 day: 19 }
+  version: "javapoet-1.12.1"
+  last_upgrade_date {
+    year: 2020
+    month: 2
+    day: 1
+  }
 }
-
diff --git a/README.md b/README.md
index 1dffbad..0b627aa 100644
--- a/README.md
+++ b/README.md
@@ -128,6 +128,60 @@
 Methods generating methods! And since JavaPoet generates source instead of bytecode, you can
 read through it to make sure it's right.
 
+Some control flow statements, such as `if/else`, can have unlimited control flow possibilities.
+You can handle those options using `nextControlFlow()`:
+
+```java
+MethodSpec main = MethodSpec.methodBuilder("main")
+    .addStatement("long now = $T.currentTimeMillis()", System.class)
+    .beginControlFlow("if ($T.currentTimeMillis() < now)", System.class)
+    .addStatement("$T.out.println($S)", System.class, "Time travelling, woo hoo!")
+    .nextControlFlow("else if ($T.currentTimeMillis() == now)", System.class)
+    .addStatement("$T.out.println($S)", System.class, "Time stood still!")
+    .nextControlFlow("else")
+    .addStatement("$T.out.println($S)", System.class, "Ok, time still moving forward")
+    .endControlFlow()
+    .build();
+```
+
+Which generates:
+
+```java
+void main() {
+  long now = System.currentTimeMillis();
+  if (System.currentTimeMillis() < now)  {
+    System.out.println("Time travelling, woo hoo!");
+  } else if (System.currentTimeMillis() == now) {
+    System.out.println("Time stood still!");
+  } else {
+    System.out.println("Ok, time still moving forward");
+  }
+}
+``` 
+
+Catching exceptions using `try/catch` is also a use case for `nextControlFlow()`:
+
+```java
+MethodSpec main = MethodSpec.methodBuilder("main")
+    .beginControlFlow("try")
+    .addStatement("throw new Exception($S)", "Failed")
+    .nextControlFlow("catch ($T e)", Exception.class)
+    .addStatement("throw new $T(e)", RuntimeException.class)
+    .endControlFlow()
+    .build();
+```
+
+Which produces:
+
+```java
+void main() {
+  try {
+    throw new Exception("Failed");
+  } catch (Exception e) {
+    throw new RuntimeException(e);
+  }
+}
+```
 
 ### $L for Literals
 
@@ -674,7 +728,7 @@
 
 ### Anonymous Inner Classes
 
-In the enum code, we used `Types.anonymousInnerClass()`. Anonymous inner classes can also be used in
+In the enum code, we used `TypeSpec.anonymousInnerClass()`. Anonymous inner classes can also be used in
 code blocks. They are values that can be referenced with `$L`:
 
 ```java
@@ -838,12 +892,12 @@
 <dependency>
   <groupId>com.squareup</groupId>
   <artifactId>javapoet</artifactId>
-  <version>1.11.1</version>
+  <version>1.12.0</version>
 </dependency>
 ```
 or Gradle:
 ```groovy
-compile 'com.squareup:javapoet:1.11.1'
+compile 'com.squareup:javapoet:1.12.0'
 ```
 
 Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap].
diff --git a/pom.xml b/pom.xml
index a531637..6011c8d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
 
   <groupId>com.squareup</groupId>
   <artifactId>javapoet</artifactId>
-  <version>1.11.1</version>
+  <version>1.12.1</version>
 
   <name>JavaPoet</name>
   <description>Use beautiful Java code to generate beautiful Java code.</description>
@@ -94,7 +94,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
-        <version>3.7.0</version>
+        <version>3.8.0</version>
         <configuration>
           <compilerId>javac-with-errorprone</compilerId>
           <forceJavacCompilerUse>true</forceJavacCompilerUse>
@@ -123,7 +123,7 @@
           <dependency>
             <groupId>com.puppycrawl.tools</groupId>
             <artifactId>checkstyle</artifactId>
-            <version>8.7</version>
+            <version>8.18</version>
           </dependency>
         </dependencies>
         <configuration>
diff --git a/src/main/java/com/squareup/javapoet/AnnotationSpec.java b/src/main/java/com/squareup/javapoet/AnnotationSpec.java
index d1c5e53..5525d7b 100644
--- a/src/main/java/com/squareup/javapoet/AnnotationSpec.java
+++ b/src/main/java/com/squareup/javapoet/AnnotationSpec.java
@@ -192,7 +192,8 @@
 
   public static final class Builder {
     private final TypeName type;
-    private final Map<String, List<CodeBlock>> members = new LinkedHashMap<>();
+
+    public final Map<String, List<CodeBlock>> members = new LinkedHashMap<>();
 
     private Builder(TypeName type) {
       this.type = type;
@@ -203,8 +204,6 @@
     }
 
     public Builder addMember(String name, CodeBlock codeBlock) {
-      checkNotNull(name, "name == null");
-      checkArgument(SourceVersion.isName(name), "not a valid name: %s", name);
       List<CodeBlock> values = members.computeIfAbsent(name, k -> new ArrayList<>());
       values.add(codeBlock);
       return this;
@@ -238,6 +237,10 @@
     }
 
     public AnnotationSpec build() {
+      for (String name : members.keySet()) {
+        checkNotNull(name, "name == null");
+        checkArgument(SourceVersion.isName(name), "not a valid name: %s", name);
+      }
       return new AnnotationSpec(this);
     }
   }
diff --git a/src/main/java/com/squareup/javapoet/ClassName.java b/src/main/java/com/squareup/javapoet/ClassName.java
index 99c4ed2..4bef49d 100644
--- a/src/main/java/com/squareup/javapoet/ClassName.java
+++ b/src/main/java/com/squareup/javapoet/ClassName.java
@@ -20,6 +20,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.PackageElement;
 import javax.lang.model.element.TypeElement;
@@ -32,6 +33,9 @@
 public final class ClassName extends TypeName implements Comparable<ClassName> {
   public static final ClassName OBJECT = ClassName.get(Object.class);
 
+  /** The name representing the default Java package. */
+  private static final String NO_PACKAGE = "";
+
   /** The package name of this class, or "" if this is in the default package. */
   final String packageName;
 
@@ -41,6 +45,8 @@
   /** This class name, like "Entry" for java.util.Map.Entry. */
   final String simpleName;
 
+  private List<String> simpleNames;
+
   /** The full class name like "java.util.Map.Entry". */
   final String canonicalName;
 
@@ -51,7 +57,7 @@
   private ClassName(String packageName, ClassName enclosingClassName, String simpleName,
       List<AnnotationSpec> annotations) {
     super(annotations);
-    this.packageName = packageName;
+    this.packageName = Objects.requireNonNull(packageName, "packageName == null");
     this.enclosingClassName = enclosingClassName;
     this.simpleName = simpleName;
     this.canonicalName = enclosingClassName != null
@@ -108,11 +114,18 @@
   }
 
   public List<String> simpleNames() {
-    List<String> simpleNames = new ArrayList<>();
-    if (enclosingClassName != null) {
-      simpleNames.addAll(enclosingClassName().simpleNames());
+    if (simpleNames != null) {
+      return simpleNames;
     }
-    simpleNames.add(simpleName);
+
+    if (enclosingClassName == null) {
+      simpleNames = Collections.singletonList(simpleName);
+    } else {
+      List<String> mutableNames = new ArrayList<>();
+      mutableNames.addAll(enclosingClassName().simpleNames());
+      mutableNames.add(simpleName);
+      simpleNames = Collections.unmodifiableList(mutableNames);
+    }
     return simpleNames;
   }
 
@@ -138,6 +151,14 @@
     return simpleName;
   }
 
+  /**
+   * Returns the full class name of this class.
+   * Like {@code "java.util.Map.Entry"} for {@link Map.Entry}.
+   * */
+  public String canonicalName() {
+    return canonicalName;
+  }
+
   public static ClassName get(Class<?> clazz) {
     checkNotNull(clazz, "clazz == null");
     checkArgument(!clazz.isPrimitive(), "primitive types cannot be represented as a ClassName");
@@ -155,7 +176,7 @@
     if (clazz.getEnclosingClass() == null) {
       // Avoid unreliable Class.getPackage(). https://github.com/square/javapoet/issues/295
       int lastDot = clazz.getName().lastIndexOf('.');
-      String packageName = (lastDot != -1) ? clazz.getName().substring(0, lastDot) : null;
+      String packageName = (lastDot != -1) ? clazz.getName().substring(0, lastDot) : NO_PACKAGE;
       return new ClassName(packageName, null, name);
     }
 
@@ -177,7 +198,7 @@
       p = classNameString.indexOf('.', p) + 1;
       checkArgument(p != 0, "couldn't make a guess for %s", classNameString);
     }
-    String packageName = p == 0 ? "" : classNameString.substring(0, p - 1);
+    String packageName = p == 0 ? NO_PACKAGE : classNameString.substring(0, p - 1);
 
     // Add class names like "Map" and "Entry".
     ClassName className = null;
diff --git a/src/main/java/com/squareup/javapoet/CodeBlock.java b/src/main/java/com/squareup/javapoet/CodeBlock.java
index 33e3846..02542f5 100644
--- a/src/main/java/com/squareup/javapoet/CodeBlock.java
+++ b/src/main/java/com/squareup/javapoet/CodeBlock.java
@@ -189,7 +189,7 @@
       while (p < format.length()) {
         int nextP = format.indexOf("$", p);
         if (nextP == -1) {
-          formatParts.add(format.substring(p, format.length()));
+          formatParts.add(format.substring(p));
           break;
         }
 
@@ -424,6 +424,12 @@
       return this;
     }
 
+    public Builder clear() {
+      formatParts.clear();
+      args.clear();
+      return this;
+    }
+
     public CodeBlock build() {
       return new CodeBlock(this);
     }
diff --git a/src/main/java/com/squareup/javapoet/CodeWriter.java b/src/main/java/com/squareup/javapoet/CodeWriter.java
index 542f434..3b2f188 100644
--- a/src/main/java/com/squareup/javapoet/CodeWriter.java
+++ b/src/main/java/com/squareup/javapoet/CodeWriter.java
@@ -54,9 +54,11 @@
   private final List<TypeSpec> typeSpecStack = new ArrayList<>();
   private final Set<String> staticImportClassNames;
   private final Set<String> staticImports;
+  private final Set<String> alwaysQualify;
   private final Map<String, ClassName> importedTypes;
   private final Map<String, ClassName> importableTypes = new LinkedHashMap<>();
   private final Set<String> referencedNames = new LinkedHashSet<>();
+  private final Multiset<String> currentTypeVariables = new Multiset<>();
   private boolean trailingNewline;
 
   /**
@@ -67,19 +69,23 @@
   int statementLine = -1;
 
   CodeWriter(Appendable out) {
-    this(out, "  ", Collections.emptySet());
+    this(out, "  ", Collections.emptySet(), Collections.emptySet());
   }
 
-  CodeWriter(Appendable out, String indent, Set<String> staticImports) {
-    this(out, indent, Collections.emptyMap(), staticImports);
+  CodeWriter(Appendable out, String indent, Set<String> staticImports, Set<String> alwaysQualify) {
+    this(out, indent, Collections.emptyMap(), staticImports, alwaysQualify);
   }
 
-  CodeWriter(Appendable out, String indent, Map<String, ClassName> importedTypes,
-      Set<String> staticImports) {
+  CodeWriter(Appendable out,
+      String indent,
+      Map<String, ClassName> importedTypes,
+      Set<String> staticImports,
+      Set<String> alwaysQualify) {
     this.out = new LineWrapper(out, indent, 100);
     this.indent = checkNotNull(indent, "indent == null");
     this.importedTypes = checkNotNull(importedTypes, "importedTypes == null");
     this.staticImports = checkNotNull(staticImports, "staticImports == null");
+    this.alwaysQualify = checkNotNull(alwaysQualify, "alwaysQualify == null");
     this.staticImportClassNames = new LinkedHashSet<>();
     for (String signature : staticImports) {
       staticImportClassNames.add(signature.substring(0, signature.lastIndexOf('.')));
@@ -148,7 +154,7 @@
     emit("/**\n");
     javadoc = true;
     try {
-      emit(javadocCodeBlock);
+      emit(javadocCodeBlock, true);
     } finally {
       javadoc = false;
     }
@@ -187,6 +193,8 @@
   public void emitTypeVariables(List<TypeVariableName> typeVariables) throws IOException {
     if (typeVariables.isEmpty()) return;
 
+    typeVariables.forEach(typeVariable -> currentTypeVariables.add(typeVariable.name));
+
     emit("<");
     boolean firstTypeVariable = true;
     for (TypeVariableName typeVariable : typeVariables) {
@@ -203,6 +211,10 @@
     emit(">");
   }
 
+  public void popTypeVariables(List<TypeVariableName> typeVariables) throws IOException {
+    typeVariables.forEach(typeVariable -> currentTypeVariables.remove(typeVariable.name));
+  }
+
   public CodeWriter emit(String s) throws IOException {
     return emitAndIndent(s);
   }
@@ -212,6 +224,10 @@
   }
 
   public CodeWriter emit(CodeBlock codeBlock) throws IOException {
+    return emit(codeBlock, false);
+  }
+
+  public CodeWriter emit(CodeBlock codeBlock, boolean ensureTrailingNewline) throws IOException {
     int a = 0;
     ClassName deferredTypeName = null; // used by "import static" logic
     ListIterator<String> partIterator = codeBlock.formatParts.listIterator();
@@ -300,6 +316,9 @@
           break;
       }
     }
+    if (ensureTrailingNewline && out.lastChar() != '\n') {
+      emit("\n");
+    }
     return this;
   }
 
@@ -353,6 +372,12 @@
    * names visible due to inheritance.
    */
   String lookupName(ClassName className) {
+    // If the top level simple name is masked by a current type variable, use the canonical name.
+    String topLevelSimpleName = className.topLevelClassName().simpleName();
+    if (currentTypeVariables.contains(topLevelSimpleName)) {
+      return className.canonicalName;
+    }
+
     // Find the shortest suffix of className that resolves to className. This uses both local type
     // names (so `Entry` in `Map` refers to `Map.Entry`). Also uses imports.
     boolean nameResolved = false;
@@ -374,7 +399,7 @@
 
     // If the class is in the same package, we're done.
     if (Objects.equals(packageName, className.packageName())) {
-      referencedNames.add(className.topLevelClassName().simpleName());
+      referencedNames.add(topLevelSimpleName);
       return join(".", className.simpleNames());
     }
 
@@ -389,6 +414,9 @@
   private void importableType(ClassName className) {
     if (className.packageName().isEmpty()) {
       return;
+    } else if (alwaysQualify.contains(className.simpleName)) {
+      // TODO what about nested types like java.util.Map.Entry?
+      return;
     }
     ClassName topLevelClassName = className.topLevelClassName();
     String simpleName = topLevelClassName.simpleName();
@@ -407,10 +435,8 @@
     // Match a child of the current (potentially nested) class.
     for (int i = typeSpecStack.size() - 1; i >= 0; i--) {
       TypeSpec typeSpec = typeSpecStack.get(i);
-      for (TypeSpec visibleChild : typeSpec.typeSpecs) {
-        if (Objects.equals(visibleChild.name, simpleName)) {
-          return stackClassName(i, simpleName);
-        }
+      if (typeSpec.nestedTypesSimpleNames.contains(simpleName)) {
+        return stackClassName(i, simpleName);
       }
     }
 
@@ -443,7 +469,7 @@
    */
   CodeWriter emitAndIndent(String s) throws IOException {
     boolean first = true;
-    for (String line : s.split("\n", -1)) {
+    for (String line : s.split("\\R", -1)) {
       // Emit a newline character. Make sure blank lines in Javadoc & comments look good.
       if (!first) {
         if ((javadoc || comment) && trailingNewline) {
@@ -494,4 +520,26 @@
     result.keySet().removeAll(referencedNames);
     return result;
   }
+
+  // A makeshift multi-set implementation
+  private static final class Multiset<T> {
+    private final Map<T, Integer> map = new LinkedHashMap<>();
+
+    void add(T t) {
+      int count = map.getOrDefault(t, 0);
+      map.put(t, count + 1);
+    }
+
+    void remove(T t) {
+      int count = map.getOrDefault(t, 0);
+      if (count == 0) {
+        throw new IllegalStateException(t + " is not in the multiset");
+      }
+      map.put(t, count - 1);
+    }
+
+    boolean contains(T t) {
+      return map.getOrDefault(t, 0) > 0;
+    }
+  }
 }
diff --git a/src/main/java/com/squareup/javapoet/FieldSpec.java b/src/main/java/com/squareup/javapoet/FieldSpec.java
index 851b36d..f530d6e 100644
--- a/src/main/java/com/squareup/javapoet/FieldSpec.java
+++ b/src/main/java/com/squareup/javapoet/FieldSpec.java
@@ -111,10 +111,11 @@
     private final String name;
 
     private final CodeBlock.Builder javadoc = CodeBlock.builder();
-    private final List<AnnotationSpec> annotations = new ArrayList<>();
-    private final List<Modifier> modifiers = new ArrayList<>();
     private CodeBlock initializer = null;
 
+    public final List<AnnotationSpec> annotations = new ArrayList<>();
+    public final List<Modifier> modifiers = new ArrayList<>();
+
     private Builder(TypeName type, String name) {
       this.type = type;
       this.name = name;
diff --git a/src/main/java/com/squareup/javapoet/JavaFile.java b/src/main/java/com/squareup/javapoet/JavaFile.java
index e7662dd..da3dd86 100644
--- a/src/main/java/com/squareup/javapoet/JavaFile.java
+++ b/src/main/java/com/squareup/javapoet/JavaFile.java
@@ -22,10 +22,12 @@
 import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.net.URI;
+import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -59,6 +61,7 @@
   public final TypeSpec typeSpec;
   public final boolean skipJavaLangImports;
   private final Set<String> staticImports;
+  private final Set<String> alwaysQualify;
   private final String indent;
 
   private JavaFile(Builder builder) {
@@ -68,21 +71,63 @@
     this.skipJavaLangImports = builder.skipJavaLangImports;
     this.staticImports = Util.immutableSet(builder.staticImports);
     this.indent = builder.indent;
+
+    Set<String> alwaysQualifiedNames = new LinkedHashSet<>();
+    fillAlwaysQualifiedNames(builder.typeSpec, alwaysQualifiedNames);
+    this.alwaysQualify = Util.immutableSet(alwaysQualifiedNames);
+  }
+
+  private void fillAlwaysQualifiedNames(TypeSpec spec, Set<String> alwaysQualifiedNames) {
+    alwaysQualifiedNames.addAll(spec.alwaysQualifiedNames);
+    for (TypeSpec nested : spec.typeSpecs) {
+      fillAlwaysQualifiedNames(nested, alwaysQualifiedNames);
+    }
   }
 
   public void writeTo(Appendable out) throws IOException {
     // First pass: emit the entire class, just to collect the types we'll need to import.
-    CodeWriter importsCollector = new CodeWriter(NULL_APPENDABLE, indent, staticImports);
+    CodeWriter importsCollector = new CodeWriter(
+        NULL_APPENDABLE,
+        indent,
+        staticImports,
+        alwaysQualify
+    );
     emit(importsCollector);
     Map<String, ClassName> suggestedImports = importsCollector.suggestedImports();
 
     // Second pass: write the code, taking advantage of the imports.
-    CodeWriter codeWriter = new CodeWriter(out, indent, suggestedImports, staticImports);
+    CodeWriter codeWriter
+        = new CodeWriter(out, indent, suggestedImports, staticImports, alwaysQualify);
     emit(codeWriter);
   }
 
   /** Writes this to {@code directory} as UTF-8 using the standard directory structure. */
   public void writeTo(Path directory) throws IOException {
+    writeToPath(directory);
+  }
+
+  /**
+   * Writes this to {@code directory} with the provided {@code charset} using the standard directory
+   * structure.
+   */
+  public void writeTo(Path directory, Charset charset) throws IOException {
+    writeToPath(directory, charset);
+  }
+
+  /**
+   * Writes this to {@code directory} as UTF-8 using the standard directory structure.
+   * Returns the {@link Path} instance to which source is actually written.
+   */
+  public Path writeToPath(Path directory) throws IOException {
+    return writeToPath(directory, UTF_8);
+  }
+
+  /**
+   * Writes this to {@code directory} with the provided {@code charset} using the standard directory
+   * structure.
+   * Returns the {@link Path} instance to which source is actually written.
+   */
+  public Path writeToPath(Path directory, Charset charset) throws IOException {
     checkArgument(Files.notExists(directory) || Files.isDirectory(directory),
         "path %s exists but is not a directory.", directory);
     Path outputDirectory = directory;
@@ -94,9 +139,11 @@
     }
 
     Path outputPath = outputDirectory.resolve(typeSpec.name + ".java");
-    try (Writer writer = new OutputStreamWriter(Files.newOutputStream(outputPath), UTF_8)) {
+    try (Writer writer = new OutputStreamWriter(Files.newOutputStream(outputPath), charset)) {
       writeTo(writer);
     }
+
+    return outputPath;
   }
 
   /** Writes this to {@code directory} as UTF-8 using the standard directory structure. */
@@ -104,6 +151,15 @@
     writeTo(directory.toPath());
   }
 
+  /**
+   * Writes this to {@code directory} as UTF-8 using the standard directory structure.
+   * Returns the {@link File} instance to which source is actually written.
+   */
+  public File writeToFile(File directory) throws IOException {
+    final Path outputPath = writeToPath(directory.toPath());
+    return outputPath.toFile();
+  }
+
   /** Writes this to {@code filer}. */
   public void writeTo(Filer filer) throws IOException {
     String fileName = packageName.isEmpty()
@@ -144,7 +200,12 @@
 
     int importedTypesCount = 0;
     for (ClassName className : new TreeSet<>(codeWriter.importedTypes().values())) {
-      if (skipJavaLangImports && className.packageName().equals("java.lang")) continue;
+      // TODO what about nested types like java.util.Map.Entry?
+      if (skipJavaLangImports
+          && className.packageName().equals("java.lang")
+          && !alwaysQualify.contains(className.simpleName)) {
+        continue;
+      }
       codeWriter.emit("import $L;\n", className.withoutAnnotations());
       importedTypesCount++;
     }
@@ -216,10 +277,11 @@
     private final String packageName;
     private final TypeSpec typeSpec;
     private final CodeBlock.Builder fileComment = CodeBlock.builder();
-    private final Set<String> staticImports = new TreeSet<>();
     private boolean skipJavaLangImports;
     private String indent = "  ";
 
+    public final Set<String> staticImports = new TreeSet<>();
+
     private Builder(String packageName, TypeSpec typeSpec) {
       this.packageName = packageName;
       this.typeSpec = typeSpec;
diff --git a/src/main/java/com/squareup/javapoet/LineWrapper.java b/src/main/java/com/squareup/javapoet/LineWrapper.java
index 6aa3131..928d9f4 100644
--- a/src/main/java/com/squareup/javapoet/LineWrapper.java
+++ b/src/main/java/com/squareup/javapoet/LineWrapper.java
@@ -24,7 +24,7 @@
  * or soft-wrapping spaces using {@link #wrappingSpace}.
  */
 final class LineWrapper {
-  private final Appendable out;
+  private final RecordingAppendable out;
   private final String indent;
   private final int columnLimit;
   private boolean closed;
@@ -47,11 +47,16 @@
 
   LineWrapper(Appendable out, String indent, int columnLimit) {
     checkNotNull(out, "out == null");
-    this.out = out;
+    this.out = new RecordingAppendable(out);
     this.indent = indent;
     this.columnLimit = columnLimit;
   }
 
+  /** @return the last emitted char or {@link Character#MIN_VALUE} if nothing emitted yet. */
+  char lastChar() {
+    return out.lastChar;
+  }
+
   /** Emit {@code s}. This may be buffered to permit line wraps to be inserted. */
   void append(String s) throws IOException {
     if (closed) throw new IllegalStateException("closed");
@@ -134,4 +139,33 @@
   private enum FlushType {
     WRAP, SPACE, EMPTY;
   }
+
+  /** A delegating {@link Appendable} that records info about the chars passing through it. */
+  static final class RecordingAppendable implements Appendable {
+    private final Appendable delegate;
+
+    char lastChar = Character.MIN_VALUE;
+
+    RecordingAppendable(Appendable delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override public Appendable append(CharSequence csq) throws IOException {
+      int length = csq.length();
+      if (length != 0) {
+        lastChar = csq.charAt(length - 1);
+      }
+      return delegate.append(csq);
+    }
+
+    @Override public Appendable append(CharSequence csq, int start, int end) throws IOException {
+      CharSequence sub = csq.subSequence(start, end);
+      return append(sub);
+    }
+
+    @Override public Appendable append(char c) throws IOException {
+      lastChar = c;
+      return delegate.append(c);
+    }
+  }
 }
diff --git a/src/main/java/com/squareup/javapoet/MethodSpec.java b/src/main/java/com/squareup/javapoet/MethodSpec.java
index a2c7c43..2284ef5 100644
--- a/src/main/java/com/squareup/javapoet/MethodSpec.java
+++ b/src/main/java/com/squareup/javapoet/MethodSpec.java
@@ -24,6 +24,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 import javax.lang.model.SourceVersion;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ExecutableElement;
@@ -82,7 +83,7 @@
 
   void emit(CodeWriter codeWriter, String enclosingName, Set<Modifier> implicitModifiers)
       throws IOException {
-    codeWriter.emitJavadoc(javadoc);
+    codeWriter.emitJavadoc(javadocWithParameters());
     codeWriter.emitAnnotations(annotations, false);
     codeWriter.emitModifiers(modifiers, implicitModifiers);
 
@@ -132,11 +133,26 @@
       codeWriter.emit(" {\n");
 
       codeWriter.indent();
-      codeWriter.emit(code);
+      codeWriter.emit(code, true);
       codeWriter.unindent();
 
       codeWriter.emit("}\n");
     }
+    codeWriter.popTypeVariables(typeVariables);
+  }
+
+  private CodeBlock javadocWithParameters() {
+    CodeBlock.Builder builder = javadoc.toBuilder();
+    boolean emitTagNewline = true;
+    for (ParameterSpec parameterSpec : parameters) {
+      if (!parameterSpec.javadoc.isEmpty()) {
+        // Emit a new line before @param section only if the method javadoc is present.
+        if (emitTagNewline && !javadoc.isEmpty()) builder.add("\n");
+        emitTagNewline = false;
+        builder.add("@param $L $L", parameterSpec.name, parameterSpec.javadoc);
+      }
+    }
+    return builder.build();
   }
 
   public boolean hasModifier(Modifier modifier) {
@@ -217,7 +233,16 @@
     }
 
     methodBuilder.returns(TypeName.get(method.getReturnType()));
-    methodBuilder.addParameters(ParameterSpec.parametersOf(method));
+    // Copying parameter annotations from the overridden method can be incorrect so we're
+    // deliberately dropping them. See https://github.com/square/javapoet/issues/482.
+    methodBuilder.addParameters(ParameterSpec.parametersOf(method)
+        .stream()
+        .map(parameterSpec -> {
+          ParameterSpec.Builder builder = parameterSpec.toBuilder();
+          builder.annotations.clear();
+          return builder.build();
+        })
+        .collect(Collectors.toList()));
     methodBuilder.varargs(method.isVarArgs());
 
     for (TypeMirror thrownType : method.getThrownTypes()) {
@@ -277,25 +302,31 @@
   }
 
   public static final class Builder {
-    private final String name;
+    private String name;
 
     private final CodeBlock.Builder javadoc = CodeBlock.builder();
-    private final List<AnnotationSpec> annotations = new ArrayList<>();
-    private final List<Modifier> modifiers = new ArrayList<>();
-    private List<TypeVariableName> typeVariables = new ArrayList<>();
     private TypeName returnType;
-    private final List<ParameterSpec> parameters = new ArrayList<>();
     private final Set<TypeName> exceptions = new LinkedHashSet<>();
     private final CodeBlock.Builder code = CodeBlock.builder();
     private boolean varargs;
     private CodeBlock defaultValue;
 
+    public final List<TypeVariableName> typeVariables = new ArrayList<>();
+    public final List<AnnotationSpec> annotations = new ArrayList<>();
+    public final List<Modifier> modifiers = new ArrayList<>();
+    public final List<ParameterSpec> parameters = new ArrayList<>();
+
     private Builder(String name) {
+      setName(name);
+    }
+
+    public Builder setName(String name) {
       checkNotNull(name, "name == null");
       checkArgument(name.equals(CONSTRUCTOR) || SourceVersion.isName(name),
           "not a valid name: %s", name);
       this.name = name;
       this.returnType = name.equals(CONSTRUCTOR) ? null : TypeName.VOID;
+      return this;
     }
 
     public Builder addJavadoc(String format, Object... args) {
@@ -454,6 +485,14 @@
     }
 
     /**
+     * @param codeBlock the control flow construct and its code, such as "if (foo == 5)".
+     * Shouldn't contain braces or newline characters.
+     */
+    public Builder beginControlFlow(CodeBlock codeBlock) {
+      return beginControlFlow("$L", codeBlock);
+    }
+
+    /**
      * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
      *     Shouldn't contain braces or newline characters.
      */
@@ -462,6 +501,14 @@
       return this;
     }
 
+    /**
+     * @param codeBlock the control flow construct and its code, such as "else if (foo == 10)".
+     *     Shouldn't contain braces or newline characters.
+     */
+    public Builder nextControlFlow(CodeBlock codeBlock) {
+      return nextControlFlow("$L", codeBlock);
+    }
+
     public Builder endControlFlow() {
       code.endControlFlow();
       return this;
@@ -476,6 +523,14 @@
       return this;
     }
 
+    /**
+     * @param codeBlock the optional control flow construct and its code, such as
+     *     "while(foo == 20)". Only used for "do/while" control flows.
+     */
+    public Builder endControlFlow(CodeBlock codeBlock) {
+      return endControlFlow("$L", codeBlock);
+    }
+
     public Builder addStatement(String format, Object... args) {
       code.addStatement(format, args);
       return this;
diff --git a/src/main/java/com/squareup/javapoet/ParameterSpec.java b/src/main/java/com/squareup/javapoet/ParameterSpec.java
index 63da3f2..b8f3129 100644
--- a/src/main/java/com/squareup/javapoet/ParameterSpec.java
+++ b/src/main/java/com/squareup/javapoet/ParameterSpec.java
@@ -21,7 +21,9 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
+import java.util.stream.Collectors;
 import javax.lang.model.SourceVersion;
+import javax.lang.model.element.ElementKind;
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.Modifier;
 import javax.lang.model.element.VariableElement;
@@ -35,12 +37,14 @@
   public final List<AnnotationSpec> annotations;
   public final Set<Modifier> modifiers;
   public final TypeName type;
+  public final CodeBlock javadoc;
 
   private ParameterSpec(Builder builder) {
     this.name = checkNotNull(builder.name, "name == null");
     this.annotations = Util.immutableList(builder.annotations);
     this.modifiers = Util.immutableSet(builder.modifiers);
     this.type = checkNotNull(builder.type, "type == null");
+    this.javadoc = builder.javadoc.build();
   }
 
   public boolean hasModifier(Modifier modifier) {
@@ -81,10 +85,19 @@
   }
 
   public static ParameterSpec get(VariableElement element) {
+    checkArgument(element.getKind().equals(ElementKind.PARAMETER), "element is not a parameter");
+
+    // Copy over any annotations from element.
+    List<AnnotationSpec> annotations = element.getAnnotationMirrors()
+        .stream()
+        .map((mirror) -> AnnotationSpec.get(mirror))
+        .collect(Collectors.toList());
+
     TypeName type = TypeName.get(element.asType());
     String name = element.getSimpleName().toString();
     return ParameterSpec.builder(type, name)
         .addModifiers(element.getModifiers())
+        .addAnnotations(annotations)
         .build();
   }
 
@@ -121,15 +134,26 @@
   public static final class Builder {
     private final TypeName type;
     private final String name;
+    private final CodeBlock.Builder javadoc = CodeBlock.builder();
 
-    private final List<AnnotationSpec> annotations = new ArrayList<>();
-    private final List<Modifier> modifiers = new ArrayList<>();
+    public final List<AnnotationSpec> annotations = new ArrayList<>();
+    public final List<Modifier> modifiers = new ArrayList<>();
 
     private Builder(TypeName type, String name) {
       this.type = type;
       this.name = name;
     }
 
+    public Builder addJavadoc(String format, Object... args) {
+      javadoc.add(format, args);
+      return this;
+    }
+
+    public Builder addJavadoc(CodeBlock block) {
+      javadoc.add(block);
+      return this;
+    }
+
     public Builder addAnnotations(Iterable<AnnotationSpec> annotationSpecs) {
       checkArgument(annotationSpecs != null, "annotationSpecs == null");
       for (AnnotationSpec annotationSpec : annotationSpecs) {
@@ -160,6 +184,9 @@
     public Builder addModifiers(Iterable<Modifier> modifiers) {
       checkNotNull(modifiers, "modifiers == null");
       for (Modifier modifier : modifiers) {
+        if (!modifier.equals(Modifier.FINAL)) {
+          throw new IllegalStateException("unexpected parameter modifier: " + modifier);
+        }
         this.modifiers.add(modifier);
       }
       return this;
diff --git a/src/main/java/com/squareup/javapoet/TypeName.java b/src/main/java/com/squareup/javapoet/TypeName.java
index 38877f7..c0986bb 100644
--- a/src/main/java/com/squareup/javapoet/TypeName.java
+++ b/src/main/java/com/squareup/javapoet/TypeName.java
@@ -44,7 +44,7 @@
  * identifies composite types like {@code char[]} and {@code Set<Long>}.
  *
  * <p>Type names are dumb identifiers only and do not model the values they name. For example, the
- * type name for {@code java.lang.List} doesn't know about the {@code size()} method, the fact that
+ * type name for {@code java.util.List} doesn't know about the {@code size()} method, the fact that
  * lists are collections, or even that it accepts a single type parameter.
  *
  * <p>Instances of this class are immutable value objects that implement {@code equals()} and {@code
diff --git a/src/main/java/com/squareup/javapoet/TypeSpec.java b/src/main/java/com/squareup/javapoet/TypeSpec.java
index 46de3a5..5fb2bb3 100644
--- a/src/main/java/com/squareup/javapoet/TypeSpec.java
+++ b/src/main/java/com/squareup/javapoet/TypeSpec.java
@@ -16,13 +16,16 @@
 package com.squareup.javapoet;
 
 import java.io.IOException;
+import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.EnumSet;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -30,6 +33,11 @@
 import javax.lang.model.SourceVersion;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.NoType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
 
 import static com.squareup.javapoet.Util.checkArgument;
 import static com.squareup.javapoet.Util.checkNotNull;
@@ -53,7 +61,9 @@
   public final CodeBlock initializerBlock;
   public final List<MethodSpec> methodSpecs;
   public final List<TypeSpec> typeSpecs;
+  final Set<String> nestedTypesSimpleNames;
   public final List<Element> originatingElements;
+  public final Set<String> alwaysQualifiedNames;
 
   private TypeSpec(Builder builder) {
     this.kind = builder.kind;
@@ -71,12 +81,16 @@
     this.initializerBlock = builder.initializerBlock.build();
     this.methodSpecs = Util.immutableList(builder.methodSpecs);
     this.typeSpecs = Util.immutableList(builder.typeSpecs);
+    this.alwaysQualifiedNames = Util.immutableSet(builder.alwaysQualifiedNames);
 
+    nestedTypesSimpleNames = new HashSet<>(builder.typeSpecs.size());
     List<Element> originatingElementsMutable = new ArrayList<>();
     originatingElementsMutable.addAll(builder.originatingElements);
     for (TypeSpec typeSpec : builder.typeSpecs) {
+      nestedTypesSimpleNames.add(typeSpec.name);
       originatingElementsMutable.addAll(typeSpec.originatingElements);
     }
+
     this.originatingElements = Util.immutableList(originatingElementsMutable);
   }
 
@@ -102,6 +116,8 @@
     this.methodSpecs = Collections.emptyList();
     this.typeSpecs = Collections.emptyList();
     this.originatingElements = Collections.emptyList();
+    this.nestedTypesSimpleNames = Collections.emptySet();
+    this.alwaysQualifiedNames = Collections.emptySet();
   }
 
   public boolean hasModifier(Modifier modifier) {
@@ -133,9 +149,7 @@
   }
 
   public static Builder anonymousClassBuilder(String typeArgumentsFormat, Object... args) {
-    return anonymousClassBuilder(CodeBlock.builder()
-        .add(typeArgumentsFormat, args)
-        .build());
+    return anonymousClassBuilder(CodeBlock.of(typeArgumentsFormat, args));
   }
 
   public static Builder anonymousClassBuilder(CodeBlock typeArguments) {
@@ -164,6 +178,8 @@
     builder.typeSpecs.addAll(typeSpecs);
     builder.initializerBlock.add(initializerBlock);
     builder.staticBlock.add(staticBlock);
+    builder.originatingElements.addAll(originatingElements);
+    builder.alwaysQualifiedNames.addAll(alwaysQualifiedNames);
     return builder;
   }
 
@@ -316,6 +332,7 @@
 
       codeWriter.unindent();
       codeWriter.popType();
+      codeWriter.popTypeVariables(typeVariables);
 
       codeWriter.emit("}");
       if (enumName == null && anonymousTypeArguments == null) {
@@ -395,18 +412,20 @@
     private final CodeBlock anonymousTypeArguments;
 
     private final CodeBlock.Builder javadoc = CodeBlock.builder();
-    private final List<AnnotationSpec> annotations = new ArrayList<>();
-    private final List<Modifier> modifiers = new ArrayList<>();
-    private final List<TypeVariableName> typeVariables = new ArrayList<>();
     private TypeName superclass = ClassName.OBJECT;
-    private final List<TypeName> superinterfaces = new ArrayList<>();
-    private final Map<String, TypeSpec> enumConstants = new LinkedHashMap<>();
-    private final List<FieldSpec> fieldSpecs = new ArrayList<>();
     private final CodeBlock.Builder staticBlock = CodeBlock.builder();
     private final CodeBlock.Builder initializerBlock = CodeBlock.builder();
-    private final List<MethodSpec> methodSpecs = new ArrayList<>();
-    private final List<TypeSpec> typeSpecs = new ArrayList<>();
-    private final List<Element> originatingElements = new ArrayList<>();
+
+    public final Map<String, TypeSpec> enumConstants = new LinkedHashMap<>();
+    public final List<AnnotationSpec> annotations = new ArrayList<>();
+    public final List<Modifier> modifiers = new ArrayList<>();
+    public final List<TypeVariableName> typeVariables = new ArrayList<>();
+    public final List<TypeName> superinterfaces = new ArrayList<>();
+    public final List<FieldSpec> fieldSpecs = new ArrayList<>();
+    public final List<MethodSpec> methodSpecs = new ArrayList<>();
+    public final List<TypeSpec> typeSpecs = new ArrayList<>();
+    public final List<Element> originatingElements = new ArrayList<>();
+    public final Set<String> alwaysQualifiedNames = new LinkedHashSet<>();
 
     private Builder(Kind kind, String name,
         CodeBlock anonymousTypeArguments) {
@@ -449,16 +468,11 @@
     }
 
     public Builder addModifiers(Modifier... modifiers) {
-      checkState(anonymousTypeArguments == null, "forbidden on anonymous types.");
-      for (Modifier modifier : modifiers) {
-        checkArgument(modifier != null, "modifiers contain null");
-        this.modifiers.add(modifier);
-      }
+      Collections.addAll(this.modifiers, modifiers);
       return this;
     }
 
     public Builder addTypeVariables(Iterable<TypeVariableName> typeVariables) {
-      checkState(anonymousTypeArguments == null, "forbidden on anonymous types.");
       checkArgument(typeVariables != null, "typeVariables == null");
       for (TypeVariableName typeVariable : typeVariables) {
         this.typeVariables.add(typeVariable);
@@ -467,7 +481,6 @@
     }
 
     public Builder addTypeVariable(TypeVariableName typeVariable) {
-      checkState(anonymousTypeArguments == null, "forbidden on anonymous types.");
       typeVariables.add(typeVariable);
       return this;
     }
@@ -482,7 +495,32 @@
     }
 
     public Builder superclass(Type superclass) {
-      return superclass(TypeName.get(superclass));
+      return superclass(superclass, true);
+    }
+
+    public Builder superclass(Type superclass, boolean avoidNestedTypeNameClashes) {
+      superclass(TypeName.get(superclass));
+      if (avoidNestedTypeNameClashes) {
+        Class<?> clazz = getRawType(superclass);
+        if (clazz != null) {
+          avoidClashesWithNestedClasses(clazz);
+        }
+      }
+      return this;
+    }
+
+    public Builder superclass(TypeMirror superclass) {
+      return superclass(superclass, true);
+    }
+
+    public Builder superclass(TypeMirror superclass, boolean avoidNestedTypeNameClashes) {
+      superclass(TypeName.get(superclass));
+      if (avoidNestedTypeNameClashes && superclass instanceof DeclaredType) {
+        TypeElement superInterfaceElement =
+            (TypeElement) ((DeclaredType) superclass).asElement();
+        avoidClashesWithNestedClasses(superInterfaceElement);
+      }
+      return this;
     }
 
     public Builder addSuperinterfaces(Iterable<? extends TypeName> superinterfaces) {
@@ -500,7 +538,43 @@
     }
 
     public Builder addSuperinterface(Type superinterface) {
-      return addSuperinterface(TypeName.get(superinterface));
+      return addSuperinterface(superinterface, true);
+    }
+
+    public Builder addSuperinterface(Type superinterface, boolean avoidNestedTypeNameClashes) {
+      addSuperinterface(TypeName.get(superinterface));
+      if (avoidNestedTypeNameClashes) {
+        Class<?> clazz = getRawType(superinterface);
+        if (clazz != null) {
+          avoidClashesWithNestedClasses(clazz);
+        }
+      }
+      return this;
+    }
+
+    private Class<?> getRawType(Type type) {
+      if (type instanceof Class<?>) {
+        return (Class<?>) type;
+      } else if (type instanceof ParameterizedType) {
+        return getRawType(((ParameterizedType) type).getRawType());
+      } else {
+        return null;
+      }
+    }
+
+    public Builder addSuperinterface(TypeMirror superinterface) {
+      return addSuperinterface(superinterface, true);
+    }
+
+    public Builder addSuperinterface(TypeMirror superinterface,
+        boolean avoidNestedTypeNameClashes) {
+      addSuperinterface(TypeName.get(superinterface));
+      if (avoidNestedTypeNameClashes && superinterface instanceof DeclaredType) {
+        TypeElement superInterfaceElement =
+            (TypeElement) ((DeclaredType) superinterface).asElement();
+        avoidClashesWithNestedClasses(superInterfaceElement);
+      }
+      return this;
     }
 
     public Builder addEnumConstant(String name) {
@@ -508,10 +582,6 @@
     }
 
     public Builder addEnumConstant(String name, TypeSpec typeSpec) {
-      checkState(kind == Kind.ENUM, "%s is not enum", this.name);
-      checkArgument(typeSpec.anonymousTypeArguments != null,
-          "enum constants must have anonymous type arguments");
-      checkArgument(SourceVersion.isName(name), "not a valid enum constant: %s", name);
       enumConstants.put(name, typeSpec);
       return this;
     }
@@ -525,12 +595,6 @@
     }
 
     public Builder addField(FieldSpec fieldSpec) {
-      if (kind == Kind.INTERFACE || kind == Kind.ANNOTATION) {
-        requireExactlyOneOf(fieldSpec.modifiers, Modifier.PUBLIC, Modifier.PRIVATE);
-        Set<Modifier> check = EnumSet.of(Modifier.STATIC, Modifier.FINAL);
-        checkState(fieldSpec.modifiers.containsAll(check), "%s %s.%s requires modifiers %s",
-            kind, name, fieldSpec.name, check);
-      }
       fieldSpecs.add(fieldSpec);
       return this;
     }
@@ -569,23 +633,6 @@
     }
 
     public Builder addMethod(MethodSpec methodSpec) {
-      if (kind == Kind.INTERFACE) {
-        requireExactlyOneOf(methodSpec.modifiers, Modifier.ABSTRACT, Modifier.STATIC,
-            Modifier.DEFAULT);
-        requireExactlyOneOf(methodSpec.modifiers, Modifier.PUBLIC, Modifier.PRIVATE);
-      } else if (kind == Kind.ANNOTATION) {
-        checkState(methodSpec.modifiers.equals(kind.implicitMethodModifiers),
-            "%s %s.%s requires modifiers %s",
-            kind, name, methodSpec.name, kind.implicitMethodModifiers);
-      }
-      if (kind != Kind.ANNOTATION) {
-        checkState(methodSpec.defaultValue == null, "%s %s.%s cannot have a default value",
-            kind, name, methodSpec.name);
-      }
-      if (kind != Kind.INTERFACE) {
-        checkState(!methodSpec.hasModifier(Modifier.DEFAULT), "%s %s.%s cannot be default",
-            kind, name, methodSpec.name);
-      }
       methodSpecs.add(methodSpec);
       return this;
     }
@@ -599,9 +646,6 @@
     }
 
     public Builder addType(TypeSpec typeSpec) {
-      checkArgument(typeSpec.modifiers.containsAll(kind.implicitTypeModifiers),
-          "%s %s.%s requires modifiers %s", kind, name, typeSpec.name,
-          kind.implicitTypeModifiers);
       typeSpecs.add(typeSpec);
       return this;
     }
@@ -611,10 +655,171 @@
       return this;
     }
 
+    public Builder alwaysQualify(String... simpleNames) {
+      checkArgument(simpleNames != null, "simpleNames == null");
+      for (String name : simpleNames) {
+        checkArgument(
+            name != null,
+            "null entry in simpleNames array: %s",
+            Arrays.toString(simpleNames)
+        );
+        alwaysQualifiedNames.add(name);
+      }
+      return this;
+    }
+
+    /**
+     * Call this to always fully qualify any types that would conflict with possibly nested types of
+     * this {@code typeElement}. For example - if the following type was passed in as the
+     * typeElement:
+     *
+     * <pre><code>
+     *   class Foo {
+     *     class NestedTypeA {
+     *
+     *     }
+     *     class NestedTypeB {
+     *
+     *     }
+     *   }
+     * </code></pre>
+     *
+     * <p>
+     * Then this would add {@code "NestedTypeA"} and {@code "NestedTypeB"} as names that should
+     * always be qualified via {@link #alwaysQualify(String...)}. This way they would avoid
+     * possible import conflicts when this JavaFile is written.
+     *
+     * @param typeElement the {@link TypeElement} with nested types to avoid clashes with.
+     * @return this builder instance.
+     */
+    public Builder avoidClashesWithNestedClasses(TypeElement typeElement) {
+      checkArgument(typeElement != null, "typeElement == null");
+      for (TypeElement nestedType : ElementFilter.typesIn(typeElement.getEnclosedElements())) {
+        alwaysQualify(nestedType.getSimpleName().toString());
+      }
+      TypeMirror superclass = typeElement.getSuperclass();
+      if (!(superclass instanceof NoType) && superclass instanceof DeclaredType) {
+        TypeElement superclassElement = (TypeElement) ((DeclaredType) superclass).asElement();
+        avoidClashesWithNestedClasses(superclassElement);
+      }
+      for (TypeMirror superinterface : typeElement.getInterfaces()) {
+        if (superinterface instanceof DeclaredType) {
+          TypeElement superinterfaceElement
+              = (TypeElement) ((DeclaredType) superinterface).asElement();
+          avoidClashesWithNestedClasses(superinterfaceElement);
+        }
+      }
+      return this;
+    }
+
+    /**
+     * Call this to always fully qualify any types that would conflict with possibly nested types of
+     * this {@code typeElement}. For example - if the following type was passed in as the
+     * typeElement:
+     *
+     * <pre><code>
+     *   class Foo {
+     *     class NestedTypeA {
+     *
+     *     }
+     *     class NestedTypeB {
+     *
+     *     }
+     *   }
+     * </code></pre>
+     *
+     * <p>
+     * Then this would add {@code "NestedTypeA"} and {@code "NestedTypeB"} as names that should
+     * always be qualified via {@link #alwaysQualify(String...)}. This way they would avoid
+     * possible import conflicts when this JavaFile is written.
+     *
+     * @param clazz the {@link Class} with nested types to avoid clashes with.
+     * @return this builder instance.
+     */
+    public Builder avoidClashesWithNestedClasses(Class<?> clazz) {
+      checkArgument(clazz != null, "clazz == null");
+      for (Class<?> nestedType : clazz.getDeclaredClasses()) {
+        alwaysQualify(nestedType.getSimpleName());
+      }
+      Class<?> superclass = clazz.getSuperclass();
+      if (superclass != null && !Object.class.equals(superclass)) {
+        avoidClashesWithNestedClasses(superclass);
+      }
+      for (Class<?> superinterface : clazz.getInterfaces()) {
+        avoidClashesWithNestedClasses(superinterface);
+      }
+      return this;
+    }
+
     public TypeSpec build() {
+      for (AnnotationSpec annotationSpec : annotations) {
+        checkNotNull(annotationSpec, "annotationSpec == null");
+      }
+
+      if (!modifiers.isEmpty()) {
+        checkState(anonymousTypeArguments == null, "forbidden on anonymous types.");
+        for (Modifier modifier : modifiers) {
+          checkArgument(modifier != null, "modifiers contain null");
+        }
+      }
+
       checkArgument(kind != Kind.ENUM || !enumConstants.isEmpty(),
           "at least one enum constant is required for %s", name);
 
+      for (TypeName superinterface : superinterfaces) {
+        checkArgument(superinterface != null, "superinterfaces contains null");
+      }
+
+      if (!typeVariables.isEmpty()) {
+        checkState(anonymousTypeArguments == null,
+            "typevariables are forbidden on anonymous types.");
+        for (TypeVariableName typeVariableName : typeVariables) {
+          checkArgument(typeVariableName != null, "typeVariables contain null");
+        }
+      }
+
+      for (Map.Entry<String, TypeSpec> enumConstant : enumConstants.entrySet()) {
+        checkState(kind == Kind.ENUM, "%s is not enum", this.name);
+        checkArgument(enumConstant.getValue().anonymousTypeArguments != null,
+            "enum constants must have anonymous type arguments");
+        checkArgument(SourceVersion.isName(name), "not a valid enum constant: %s", name);
+      }
+
+      for (FieldSpec fieldSpec : fieldSpecs) {
+        if (kind == Kind.INTERFACE || kind == Kind.ANNOTATION) {
+          requireExactlyOneOf(fieldSpec.modifiers, Modifier.PUBLIC, Modifier.PRIVATE);
+          Set<Modifier> check = EnumSet.of(Modifier.STATIC, Modifier.FINAL);
+          checkState(fieldSpec.modifiers.containsAll(check), "%s %s.%s requires modifiers %s",
+              kind, name, fieldSpec.name, check);
+        }
+      }
+
+      for (MethodSpec methodSpec : methodSpecs) {
+        if (kind == Kind.INTERFACE) {
+          requireExactlyOneOf(methodSpec.modifiers, Modifier.ABSTRACT, Modifier.STATIC,
+              Modifier.DEFAULT);
+          requireExactlyOneOf(methodSpec.modifiers, Modifier.PUBLIC, Modifier.PRIVATE);
+        } else if (kind == Kind.ANNOTATION) {
+          checkState(methodSpec.modifiers.equals(kind.implicitMethodModifiers),
+              "%s %s.%s requires modifiers %s",
+              kind, name, methodSpec.name, kind.implicitMethodModifiers);
+        }
+        if (kind != Kind.ANNOTATION) {
+          checkState(methodSpec.defaultValue == null, "%s %s.%s cannot have a default value",
+              kind, name, methodSpec.name);
+        }
+        if (kind != Kind.INTERFACE) {
+          checkState(!methodSpec.hasModifier(Modifier.DEFAULT), "%s %s.%s cannot be default",
+              kind, name, methodSpec.name);
+        }
+      }
+
+      for (TypeSpec typeSpec : typeSpecs) {
+        checkArgument(typeSpec.modifiers.containsAll(kind.implicitTypeModifiers),
+            "%s %s.%s requires modifiers %s", kind, name, typeSpec.name,
+            kind.implicitTypeModifiers);
+      }
+
       boolean isAbstract = modifiers.contains(Modifier.ABSTRACT) || kind != Kind.CLASS;
       for (MethodSpec methodSpec : methodSpecs) {
         checkArgument(isAbstract || !methodSpec.hasModifier(Modifier.ABSTRACT),
diff --git a/src/test/java/ClassNameNoPackageTest.java b/src/test/java/ClassNameNoPackageTest.java
new file mode 100644
index 0000000..8b8545d
--- /dev/null
+++ b/src/test/java/ClassNameNoPackageTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 Square, 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.
+ */
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.squareup.javapoet.ClassName;
+import org.junit.Test;
+
+/**
+ * Since it is impossible to import classes from the default package into other
+ * modules, this test must live in this package.
+ */
+public final class ClassNameNoPackageTest {
+  @Test public void shouldSupportClassInDefaultPackage() {
+    ClassName className = ClassName.get(ClassNameNoPackageTest.class);
+    assertThat(className.packageName()).isEqualTo("");
+    assertThat(className.simpleName()).isEqualTo("ClassNameNoPackageTest");
+    assertThat(className.toString()).isEqualTo("ClassNameNoPackageTest");
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/AnnotationSpecTest.java b/src/test/java/com/squareup/javapoet/AnnotationSpecTest.java
index 49606c7..97c1e6e 100644
--- a/src/test/java/com/squareup/javapoet/AnnotationSpecTest.java
+++ b/src/test/java/com/squareup/javapoet/AnnotationSpecTest.java
@@ -20,6 +20,8 @@
 import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
 import javax.lang.model.element.TypeElement;
 import org.junit.Rule;
 import org.junit.Test;
@@ -371,6 +373,16 @@
     }
   }
 
+  @Test public void modifyMembers() {
+    AnnotationSpec.Builder builder = AnnotationSpec.builder(SuppressWarnings.class)
+            .addMember("value", "$S", "Foo");
+    
+    builder.members.clear();
+    builder.members.put("value", Arrays.asList(CodeBlock.of("$S", "Bar")));
+
+    assertThat(builder.build().toString()).isEqualTo("@java.lang.SuppressWarnings(\"Bar\")");
+  }
+
   private String toString(TypeSpec typeSpec) {
     return JavaFile.builder("com.squareup.tacos", typeSpec).build().toString();
   }
diff --git a/src/test/java/com/squareup/javapoet/ClassNameTest.java b/src/test/java/com/squareup/javapoet/ClassNameTest.java
index e2cc55e..590ad5d 100644
--- a/src/test/java/com/squareup/javapoet/ClassNameTest.java
+++ b/src/test/java/com/squareup/javapoet/ClassNameTest.java
@@ -193,4 +193,14 @@
     assertEquals("Foo$Bar$Baz", ClassName.get("", "Foo", "Bar", "Baz").reflectionName());
     assertEquals("a.b.c.Foo$Bar$Baz", ClassName.get("a.b.c", "Foo", "Bar", "Baz").reflectionName());
   }
+
+  @Test
+  public void canonicalName() {
+    assertEquals("java.lang.Object", TypeName.OBJECT.canonicalName());
+    assertEquals("java.lang.Thread.State", ClassName.get(Thread.State.class).canonicalName());
+    assertEquals("java.util.Map.Entry", ClassName.get(Map.Entry.class).canonicalName());
+    assertEquals("Foo", ClassName.get("", "Foo").canonicalName());
+    assertEquals("Foo.Bar.Baz", ClassName.get("", "Foo", "Bar", "Baz").canonicalName());
+    assertEquals("a.b.c.Foo.Bar.Baz", ClassName.get("a.b.c", "Foo", "Bar", "Baz").canonicalName());
+  }
 }
diff --git a/src/test/java/com/squareup/javapoet/CodeBlockTest.java b/src/test/java/com/squareup/javapoet/CodeBlockTest.java
index 2862809..11b75fa 100644
--- a/src/test/java/com/squareup/javapoet/CodeBlockTest.java
+++ b/src/test/java/com/squareup/javapoet/CodeBlockTest.java
@@ -339,4 +339,13 @@
     CodeBlock joined = codeBlocks.stream().collect(CodeBlock.joining(" || ", "start {", "} end"));
     assertThat(joined.toString()).isEqualTo("start {\"hello\" || world.World || need tacos} end");
   }
+
+  @Test public void clear() {
+    CodeBlock block = CodeBlock.builder()
+        .addStatement("$S", "Test string")
+        .clear()
+        .build();
+
+    assertThat(block.toString()).isEmpty();
+  }
 }
diff --git a/src/test/java/com/squareup/javapoet/CodeWriterTest.java b/src/test/java/com/squareup/javapoet/CodeWriterTest.java
new file mode 100644
index 0000000..331d000
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/CodeWriterTest.java
@@ -0,0 +1,23 @@
+package com.squareup.javapoet;
+
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static com.google.common.truth.Truth.assertThat;
+
+public class CodeWriterTest {
+
+    @Test
+    public void emptyLineInJavaDocDosEndings() throws IOException {
+        CodeBlock javadocCodeBlock = CodeBlock.of("A\r\n\r\nB\r\n");
+        StringBuilder out = new StringBuilder();
+        new CodeWriter(out).emitJavadoc(javadocCodeBlock);
+        assertThat(out.toString()).isEqualTo(
+                "/**\n" +
+                        " * A\n" +
+                        " *\n" +
+                        " * B\n" +
+                        " */\n");
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/squareup/javapoet/FieldSpecTest.java b/src/test/java/com/squareup/javapoet/FieldSpecTest.java
index 63f7aa8..bc68f6f 100644
--- a/src/test/java/com/squareup/javapoet/FieldSpecTest.java
+++ b/src/test/java/com/squareup/javapoet/FieldSpecTest.java
@@ -28,10 +28,12 @@
     FieldSpec b = FieldSpec.builder(int.class, "foo").build();
     assertThat(a.equals(b)).isTrue();
     assertThat(a.hashCode()).isEqualTo(b.hashCode());
+    assertThat(a.toString()).isEqualTo(b.toString());
     a = FieldSpec.builder(int.class, "FOO", Modifier.PUBLIC, Modifier.STATIC).build();
     b = FieldSpec.builder(int.class, "FOO", Modifier.PUBLIC, Modifier.STATIC).build();
     assertThat(a.equals(b)).isTrue();
     assertThat(a.hashCode()).isEqualTo(b.hashCode());
+    assertThat(a.toString()).isEqualTo(b.toString());
   }
 
   @Test public void nullAnnotationsAddition() {
@@ -44,4 +46,21 @@
           .isEqualTo("annotationSpecs == null");
     }
   }
-}
\ No newline at end of file
+
+  @Test public void modifyAnnotations() {
+    FieldSpec.Builder builder = FieldSpec.builder(int.class, "foo")
+          .addAnnotation(Override.class)
+          .addAnnotation(SuppressWarnings.class);
+
+    builder.annotations.remove(1);
+    assertThat(builder.build().annotations).hasSize(1);
+  }
+
+  @Test public void modifyModifiers() {
+    FieldSpec.Builder builder = FieldSpec.builder(int.class, "foo")
+          .addModifiers(Modifier.PUBLIC, Modifier.STATIC);
+
+    builder.modifiers.remove(1);
+    assertThat(builder.build().modifiers).containsExactly(Modifier.PUBLIC);
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/FileWritingTest.java b/src/test/java/com/squareup/javapoet/FileWritingTest.java
index f817ddb..58e5b62 100644
--- a/src/test/java/com/squareup/javapoet/FileWritingTest.java
+++ b/src/test/java/com/squareup/javapoet/FileWritingTest.java
@@ -216,4 +216,11 @@
         + "class Taco {\n"
         + "}\n");
   }
+
+  @Test public void writeToPathReturnsPath() throws IOException {
+    JavaFile javaFile = JavaFile.builder("foo", TypeSpec.classBuilder("Taco").build()).build();
+    Path filePath = javaFile.writeToPath(fsRoot);
+    // Cast to avoid ambiguity between assertThat(Path) and assertThat(Iterable<?>)
+    assertThat((Iterable<?>) filePath).isEqualTo(fsRoot.resolve(fs.getPath("foo", "Taco.java")));
+  }
 }
diff --git a/src/test/java/com/squareup/javapoet/JavaFileTest.java b/src/test/java/com/squareup/javapoet/JavaFileTest.java
index e056116..e75a019 100644
--- a/src/test/java/com/squareup/javapoet/JavaFileTest.java
+++ b/src/test/java/com/squareup/javapoet/JavaFileTest.java
@@ -15,12 +15,19 @@
  */
 package com.squareup.javapoet;
 
+import java.io.File;
+import com.google.testing.compile.CompilationRule;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
 import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
 import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -29,6 +36,13 @@
 
 @RunWith(JUnit4.class)
 public final class JavaFileTest {
+
+  @Rule public final CompilationRule compilation = new CompilationRule();
+
+  private TypeElement getElement(Class<?> clazz) {
+    return compilation.getElements().getTypeElement(clazz.getCanonicalName());
+  }
+
   @Test public void importStaticReadmeExample() {
     ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
     ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards");
@@ -689,4 +703,312 @@
         + "  A a;\n"
         + "}\n");
   }
+
+  @Test public void modifyStaticImports() throws Exception {
+    JavaFile.Builder builder = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .build())
+            .addStaticImport(File.class, "separator");
+
+    builder.staticImports.clear();
+    builder.staticImports.add(File.class.getCanonicalName() + ".separatorChar");
+
+    String source = builder.build().toString();
+
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import static java.io.File.separatorChar;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "}\n");
+  }
+
+  @Test public void alwaysQualifySimple() {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .addField(Thread.class, "thread")
+            .alwaysQualify("Thread")
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  java.lang.Thread thread;\n"
+        + "}\n");
+  }
+
+  @Test public void alwaysQualifySupersedesJavaLangImports() {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .addField(Thread.class, "thread")
+            .alwaysQualify("Thread")
+            .build())
+        .skipJavaLangImports(true)
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  java.lang.Thread thread;\n"
+        + "}\n");
+  }
+
+  @Test public void avoidClashesWithNestedClasses_viaClass() {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            // These two should get qualified
+            .addField(ClassName.get("other", "NestedTypeA"), "nestedA")
+            .addField(ClassName.get("other", "NestedTypeB"), "nestedB")
+            // This one shouldn't since it's not a nested type of Foo
+            .addField(ClassName.get("other", "NestedTypeC"), "nestedC")
+            // This one shouldn't since we only look at nested types
+            .addField(ClassName.get("other", "Foo"), "foo")
+            .avoidClashesWithNestedClasses(Foo.class)
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import other.Foo;\n"
+        + "import other.NestedTypeC;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  other.NestedTypeA nestedA;\n"
+        + "\n"
+        + "  other.NestedTypeB nestedB;\n"
+        + "\n"
+        + "  NestedTypeC nestedC;\n"
+        + "\n"
+        + "  Foo foo;\n"
+        + "}\n");
+  }
+
+  @Test public void avoidClashesWithNestedClasses_viaTypeElement() {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            // These two should get qualified
+            .addField(ClassName.get("other", "NestedTypeA"), "nestedA")
+            .addField(ClassName.get("other", "NestedTypeB"), "nestedB")
+            // This one shouldn't since it's not a nested type of Foo
+            .addField(ClassName.get("other", "NestedTypeC"), "nestedC")
+            // This one shouldn't since we only look at nested types
+            .addField(ClassName.get("other", "Foo"), "foo")
+            .avoidClashesWithNestedClasses(getElement(Foo.class))
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import other.Foo;\n"
+        + "import other.NestedTypeC;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  other.NestedTypeA nestedA;\n"
+        + "\n"
+        + "  other.NestedTypeB nestedB;\n"
+        + "\n"
+        + "  NestedTypeC nestedC;\n"
+        + "\n"
+        + "  Foo foo;\n"
+        + "}\n");
+  }
+
+  @Test public void avoidClashesWithNestedClasses_viaSuperinterfaceType() {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            // These two should get qualified
+            .addField(ClassName.get("other", "NestedTypeA"), "nestedA")
+            .addField(ClassName.get("other", "NestedTypeB"), "nestedB")
+            // This one shouldn't since it's not a nested type of Foo
+            .addField(ClassName.get("other", "NestedTypeC"), "nestedC")
+            // This one shouldn't since we only look at nested types
+            .addField(ClassName.get("other", "Foo"), "foo")
+            .addType(TypeSpec.classBuilder("NestedTypeA").build())
+            .addType(TypeSpec.classBuilder("NestedTypeB").build())
+            .addSuperinterface(FooInterface.class)
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo("package com.squareup.tacos;\n"
+        + "\n"
+        + "import com.squareup.javapoet.JavaFileTest;\n"
+        + "import other.Foo;\n"
+        + "import other.NestedTypeC;\n"
+        + "\n"
+        + "class Taco implements JavaFileTest.FooInterface {\n"
+        + "  other.NestedTypeA nestedA;\n"
+        + "\n"
+        + "  other.NestedTypeB nestedB;\n"
+        + "\n"
+        + "  NestedTypeC nestedC;\n"
+        + "\n"
+        + "  Foo foo;\n"
+        + "\n"
+        + "  class NestedTypeA {\n"
+        + "  }\n"
+        + "\n"
+        + "  class NestedTypeB {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  static class Foo {
+    static class NestedTypeA {
+
+    }
+    static class NestedTypeB {
+
+    }
+  }
+
+  interface FooInterface {
+    class NestedTypeA {
+
+    }
+    class NestedTypeB {
+
+    }
+  }
+
+  private TypeSpec.Builder childTypeBuilder() {
+    return TypeSpec.classBuilder("Child")
+        .addMethod(MethodSpec.methodBuilder("optionalString")
+            .returns(ParameterizedTypeName.get(Optional.class, String.class))
+            .addStatement("return $T.empty()", Optional.class)
+            .build())
+        .addMethod(MethodSpec.methodBuilder("pattern")
+            .returns(Pattern.class)
+            .addStatement("return null")
+            .build());
+  }
+
+  @Test
+  public void avoidClashes_parentChild_superclass_type() {
+    String source = JavaFile.builder("com.squareup.javapoet",
+        childTypeBuilder().superclass(Parent.class).build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo("package com.squareup.javapoet;\n"
+        + "\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "class Child extends JavaFileTest.Parent {\n"
+        + "  java.util.Optional<String> optionalString() {\n"
+        + "    return java.util.Optional.empty();\n"
+        + "  }\n"
+        + "\n"
+        + "  java.util.regex.Pattern pattern() {\n"
+        + "    return null;\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test
+  public void avoidClashes_parentChild_superclass_typeMirror() {
+    String source = JavaFile.builder("com.squareup.javapoet",
+        childTypeBuilder().superclass(getElement(Parent.class).asType()).build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo("package com.squareup.javapoet;\n"
+        + "\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "class Child extends JavaFileTest.Parent {\n"
+        + "  java.util.Optional<String> optionalString() {\n"
+        + "    return java.util.Optional.empty();\n"
+        + "  }\n"
+        + "\n"
+        + "  java.util.regex.Pattern pattern() {\n"
+        + "    return null;\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test
+  public void avoidClashes_parentChild_superinterface_type() {
+    String source = JavaFile.builder("com.squareup.javapoet",
+        childTypeBuilder().addSuperinterface(ParentInterface.class).build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo("package com.squareup.javapoet;\n"
+        + "\n"
+        + "import java.lang.String;\n"
+        + "import java.util.regex.Pattern;\n"
+        + "\n"
+        + "class Child implements JavaFileTest.ParentInterface {\n"
+        + "  java.util.Optional<String> optionalString() {\n"
+        + "    return java.util.Optional.empty();\n"
+        + "  }\n"
+        + "\n"
+        + "  Pattern pattern() {\n"
+        + "    return null;\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test
+  public void avoidClashes_parentChild_superinterface_typeMirror() {
+    String source = JavaFile.builder("com.squareup.javapoet",
+        childTypeBuilder().addSuperinterface(getElement(ParentInterface.class).asType()).build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo("package com.squareup.javapoet;\n"
+        + "\n"
+        + "import java.lang.String;\n"
+        + "import java.util.regex.Pattern;\n"
+        + "\n"
+        + "class Child implements JavaFileTest.ParentInterface {\n"
+        + "  java.util.Optional<String> optionalString() {\n"
+        + "    return java.util.Optional.empty();\n"
+        + "  }\n"
+        + "\n"
+        + "  Pattern pattern() {\n"
+        + "    return null;\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  // Regression test for https://github.com/square/javapoet/issues/77
+  // This covers class and inheritance
+  static class Parent implements ParentInterface {
+    static class Pattern {
+
+    }
+  }
+
+  interface ParentInterface {
+    class Optional {
+
+    }
+  }
+
+  // Regression test for case raised here: https://github.com/square/javapoet/issues/77#issuecomment-519972404
+  @Test
+  public void avoidClashes_mapEntry() {
+    String source = JavaFile.builder("com.squareup.javapoet",
+        TypeSpec.classBuilder("MapType")
+            .addMethod(MethodSpec.methodBuilder("optionalString")
+                .returns(ClassName.get("com.foo", "Entry"))
+                .addStatement("return null")
+                .build())
+            .addSuperinterface(Map.class)
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo("package com.squareup.javapoet;\n"
+        + "\n"
+        + "import java.util.Map;\n"
+        + "\n"
+        + "class MapType implements Map {\n"
+        + "  com.foo.Entry optionalString() {\n"
+        + "    return null;\n"
+        + "  }\n"
+        + "}\n");
+  }
 }
diff --git a/src/test/java/com/squareup/javapoet/MethodSpecTest.java b/src/test/java/com/squareup/javapoet/MethodSpecTest.java
index 5dfabaa..b768351 100644
--- a/src/test/java/com/squareup/javapoet/MethodSpecTest.java
+++ b/src/test/java/com/squareup/javapoet/MethodSpecTest.java
@@ -21,8 +21,9 @@
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Target;
 import java.util.Arrays;
-import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeoutException;
 import javax.lang.model.element.ExecutableElement;
@@ -37,6 +38,8 @@
 
 import static com.google.common.collect.Iterables.getOnlyElement;
 import static com.google.common.truth.Truth.assertThat;
+import static com.squareup.javapoet.MethodSpec.CONSTRUCTOR;
+import static com.squareup.javapoet.TestUtil.findFirst;
 import static javax.lang.model.util.ElementFilter.methodsIn;
 import static org.junit.Assert.fail;
 
@@ -55,15 +58,6 @@
     return elements.getTypeElement(clazz.getCanonicalName());
   }
 
-  private ExecutableElement findFirst(Collection<ExecutableElement> elements, String name) {
-    for (ExecutableElement executableElement : elements) {
-      if (executableElement.getSimpleName().toString().equals(name)) {
-        return executableElement;
-      }
-    }
-    throw new IllegalArgumentException(name + " not found in " + elements);
-  }
-
   @Test public void nullAnnotationsAddition() {
     try {
       MethodSpec.methodBuilder("doSomething").addAnnotations(null);
@@ -270,6 +264,59 @@
     assertThat(a.hashCode()).isEqualTo(b.hashCode());
   }
 
+  @Test public void withoutParameterJavaDoc() {
+    MethodSpec methodSpec = MethodSpec.methodBuilder("getTaco")
+        .addModifiers(Modifier.PRIVATE)
+        .addParameter(TypeName.DOUBLE, "money")
+        .addJavadoc("Gets the best Taco\n")
+        .build();
+    assertThat(methodSpec.toString()).isEqualTo(""
+        + "/**\n"
+        + " * Gets the best Taco\n"
+        + " */\n"
+        + "private void getTaco(double money) {\n"
+        + "}\n");
+  }
+
+  @Test public void withParameterJavaDoc() {
+    MethodSpec methodSpec = MethodSpec.methodBuilder("getTaco")
+        .addParameter(ParameterSpec.builder(TypeName.DOUBLE, "money")
+            .addJavadoc("the amount required to buy the taco.\n")
+            .build())
+        .addParameter(ParameterSpec.builder(TypeName.INT, "count")
+            .addJavadoc("the number of Tacos to buy.\n")
+            .build())
+        .addJavadoc("Gets the best Taco money can buy.\n")
+        .build();
+    assertThat(methodSpec.toString()).isEqualTo(""
+        + "/**\n"
+        + " * Gets the best Taco money can buy.\n"
+        + " *\n"
+        + " * @param money the amount required to buy the taco.\n"
+        + " * @param count the number of Tacos to buy.\n"
+        + " */\n"
+        + "void getTaco(double money, int count) {\n"
+        + "}\n");
+  }
+
+  @Test public void withParameterJavaDocAndWithoutMethodJavadoc() {
+    MethodSpec methodSpec = MethodSpec.methodBuilder("getTaco")
+        .addParameter(ParameterSpec.builder(TypeName.DOUBLE, "money")
+            .addJavadoc("the amount required to buy the taco.\n")
+            .build())
+        .addParameter(ParameterSpec.builder(TypeName.INT, "count")
+            .addJavadoc("the number of Tacos to buy.\n")
+            .build())
+        .build();
+    assertThat(methodSpec.toString()).isEqualTo(""
+        + "/**\n"
+        + " * @param money the amount required to buy the taco.\n"
+        + " * @param count the number of Tacos to buy.\n"
+        + " */\n"
+        + "void getTaco(double money, int count) {\n"
+        + "}\n");
+  }
+
   @Test public void duplicateExceptionsIgnored() {
     ClassName ioException = ClassName.get(IOException.class);
     ClassName timeoutException = ClassName.get(TimeoutException.class);
@@ -302,4 +349,115 @@
       assertThat(e.getMessage()).isEqualTo("modifiers == null");
     }
   }
+
+  @Test public void modifyMethodName() {
+    MethodSpec methodSpec = MethodSpec.methodBuilder("initialMethod")
+        .build()
+        .toBuilder()
+        .setName("revisedMethod")
+        .build();
+
+    assertThat(methodSpec.toString()).isEqualTo("" + "void revisedMethod() {\n" + "}\n");
+  }
+
+  @Test public void modifyAnnotations() {
+    MethodSpec.Builder builder = MethodSpec.methodBuilder("foo")
+            .addAnnotation(Override.class)
+            .addAnnotation(SuppressWarnings.class);
+
+    builder.annotations.remove(1);
+    assertThat(builder.build().annotations).hasSize(1);
+  }
+
+  @Test public void modifyModifiers() {
+    MethodSpec.Builder builder = MethodSpec.methodBuilder("foo")
+            .addModifiers(Modifier.PUBLIC, Modifier.STATIC);
+
+    builder.modifiers.remove(1);
+    assertThat(builder.build().modifiers).containsExactly(Modifier.PUBLIC);
+  }
+
+  @Test public void modifyParameters() {
+    MethodSpec.Builder builder = MethodSpec.methodBuilder("foo")
+            .addParameter(int.class, "source");
+
+    builder.parameters.remove(0);
+    assertThat(builder.build().parameters).isEmpty();
+  }
+
+  @Test public void modifyTypeVariables() {
+    TypeVariableName t = TypeVariableName.get("T");
+    MethodSpec.Builder builder = MethodSpec.methodBuilder("foo")
+            .addTypeVariable(t)
+            .addTypeVariable(TypeVariableName.get("V"));
+
+    builder.typeVariables.remove(1);
+    assertThat(builder.build().typeVariables).containsExactly(t);
+  }
+
+  @Test public void ensureTrailingNewline() {
+    MethodSpec methodSpec = MethodSpec.methodBuilder("method")
+        .addCode("codeWithNoNewline();")
+        .build();
+
+    assertThat(methodSpec.toString()).isEqualTo(""
+        + "void method() {\n"
+        + "  codeWithNoNewline();\n"
+        + "}\n");
+  }
+
+  /** Ensures that we don't add a duplicate newline if one is already present. */
+  @Test public void ensureTrailingNewlineWithExistingNewline() {
+    MethodSpec methodSpec = MethodSpec.methodBuilder("method")
+        .addCode("codeWithNoNewline();\n") // Have a newline already, so ensure we're not adding one
+        .build();
+
+    assertThat(methodSpec.toString()).isEqualTo(""
+        + "void method() {\n"
+        + "  codeWithNoNewline();\n"
+        + "}\n");
+  }
+
+  @Test public void controlFlowWithNamedCodeBlocks() {
+    Map<String, Object> m = new HashMap<>();
+    m.put("field", "valueField");
+    m.put("threshold", "5");
+
+    MethodSpec methodSpec = MethodSpec.methodBuilder("method")
+        .beginControlFlow(named("if ($field:N > $threshold:L)", m))
+        .nextControlFlow(named("else if ($field:N == $threshold:L)", m))
+        .endControlFlow()
+        .build();
+
+    assertThat(methodSpec.toString()).isEqualTo(""
+        + "void method() {\n"
+        + "  if (valueField > 5) {\n"
+        + "  } else if (valueField == 5) {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void doWhileWithNamedCodeBlocks() {
+    Map<String, Object> m = new HashMap<>();
+    m.put("field", "valueField");
+    m.put("threshold", "5");
+
+    MethodSpec methodSpec = MethodSpec.methodBuilder("method")
+        .beginControlFlow("do")
+        .addStatement(named("$field:N--", m))
+        .endControlFlow(named("while ($field:N > $threshold:L)", m))
+        .build();
+
+    assertThat(methodSpec.toString()).isEqualTo(""
+        + "void method() {\n" +
+        "  do {\n" +
+        "    valueField--;\n" +
+        "  } while (valueField > 5);\n" +
+        "}\n");
+  }
+
+  private static CodeBlock named(String format, Map<String, ?> args){
+    return CodeBlock.builder().addNamed(format, args).build();
+  }
+
 }
diff --git a/src/test/java/com/squareup/javapoet/NameAllocatorTest.java b/src/test/java/com/squareup/javapoet/NameAllocatorTest.java
index 1840107..71402c6 100644
--- a/src/test/java/com/squareup/javapoet/NameAllocatorTest.java
+++ b/src/test/java/com/squareup/javapoet/NameAllocatorTest.java
@@ -18,9 +18,11 @@
 import org.junit.Test;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 public final class NameAllocatorTest {
+
   @Test public void usage() throws Exception {
     NameAllocator nameAllocator = new NameAllocator();
     assertThat(nameAllocator.newName("foo", 1)).isEqualTo("foo");
@@ -59,6 +61,7 @@
   @Test public void characterMappingInvalidStartButValidPart() throws Exception {
     NameAllocator nameAllocator = new NameAllocator();
     assertThat(nameAllocator.newName("1ab", 1)).isEqualTo("_1ab");
+    assertThat(nameAllocator.newName("a-1", 2)).isEqualTo("a_1");
   }
 
   @Test public void characterMappingInvalidStartIsInvalidPart() throws Exception {
diff --git a/src/test/java/com/squareup/javapoet/ParameterSpecTest.java b/src/test/java/com/squareup/javapoet/ParameterSpecTest.java
index 2f81866..c3effca 100644
--- a/src/test/java/com/squareup/javapoet/ParameterSpecTest.java
+++ b/src/test/java/com/squareup/javapoet/ParameterSpecTest.java
@@ -15,23 +15,49 @@
  */
 package com.squareup.javapoet;
 
+import com.google.testing.compile.CompilationRule;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.util.Elements;
+import org.junit.Before;
+import org.junit.Rule;
+import javax.lang.model.element.Modifier;
 import org.junit.Test;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.squareup.javapoet.TestUtil.findFirst;
+import static javax.lang.model.util.ElementFilter.fieldsIn;
+import static javax.lang.model.util.ElementFilter.methodsIn;
 import static org.junit.Assert.fail;
 
-import javax.lang.model.element.Modifier;
-
 public class ParameterSpecTest {
+  @Rule public final CompilationRule compilation = new CompilationRule();
+
+  private Elements elements;
+
+  @Before public void setUp() {
+    elements = compilation.getElements();
+  }
+
+  private TypeElement getElement(Class<?> clazz) {
+    return elements.getTypeElement(clazz.getCanonicalName());
+  }
+
   @Test public void equalsAndHashCode() {
     ParameterSpec a = ParameterSpec.builder(int.class, "foo").build();
     ParameterSpec b = ParameterSpec.builder(int.class, "foo").build();
     assertThat(a.equals(b)).isTrue();
     assertThat(a.hashCode()).isEqualTo(b.hashCode());
+    assertThat(a.toString()).isEqualTo(b.toString());
     a = ParameterSpec.builder(int.class, "i").addModifiers(Modifier.STATIC).build();
     b = ParameterSpec.builder(int.class, "i").addModifiers(Modifier.STATIC).build();
     assertThat(a.equals(b)).isTrue();
     assertThat(a.hashCode()).isEqualTo(b.hashCode());
+    assertThat(a.toString()).isEqualTo(b.toString());
   }
 
   @Test public void nullAnnotationsAddition() {
@@ -43,4 +69,67 @@
           .isEqualTo("annotationSpecs == null");
     }
   }
-}
\ No newline at end of file
+
+  final class VariableElementFieldClass {
+    String name;
+  }
+
+  @Test public void fieldVariableElement() {
+    TypeElement classElement = getElement(VariableElementFieldClass.class);
+    List<VariableElement> methods = fieldsIn(elements.getAllMembers(classElement));
+    VariableElement element = findFirst(methods, "name");
+
+    try {
+      ParameterSpec.get(element);
+      fail();
+    } catch (IllegalArgumentException exception) {
+      assertThat(exception).hasMessageThat().isEqualTo("element is not a parameter");
+    }
+  }
+
+  final class VariableElementParameterClass {
+    public void foo(@Nullable final String bar) {
+    }
+  }
+
+  @Test public void parameterVariableElement() {
+    TypeElement classElement = getElement(VariableElementParameterClass.class);
+    List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
+    ExecutableElement element = findFirst(methods, "foo");
+    VariableElement parameterElement = element.getParameters().get(0);
+
+    assertThat(ParameterSpec.get(parameterElement).toString())
+        .isEqualTo("@javax.annotation.Nullable java.lang.String arg0");
+  }
+
+  @Test public void addNonFinalModifier() {
+    List<Modifier> modifiers = new ArrayList<>();
+    modifiers.add(Modifier.FINAL);
+    modifiers.add(Modifier.PUBLIC);
+
+    try {
+      ParameterSpec.builder(int.class, "foo")
+          .addModifiers(modifiers);
+      fail();
+    } catch (Exception e) {
+      assertThat(e.getMessage()).isEqualTo("unexpected parameter modifier: public");
+    }
+  }
+
+  @Test public void modifyAnnotations() {
+    ParameterSpec.Builder builder = ParameterSpec.builder(int.class, "foo")
+            .addAnnotation(Override.class)
+            .addAnnotation(SuppressWarnings.class);
+
+    builder.annotations.remove(1);
+    assertThat(builder.build().annotations).hasSize(1);
+  }
+
+  @Test public void modifyModifiers() {
+    ParameterSpec.Builder builder = ParameterSpec.builder(int.class, "foo")
+            .addModifiers(Modifier.PUBLIC, Modifier.STATIC);
+
+    builder.modifiers.remove(1);
+    assertThat(builder.build().modifiers).containsExactly(Modifier.PUBLIC);
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/TestUtil.java b/src/test/java/com/squareup/javapoet/TestUtil.java
new file mode 100644
index 0000000..f773d50
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/TestUtil.java
@@ -0,0 +1,17 @@
+package com.squareup.javapoet;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import java.util.Collection;
+
+final class TestUtil {
+  static <E extends Element> E findFirst(Collection<E> elements, String name) {
+    for (E element : elements) {
+      if (element.getSimpleName().toString().equals(name)) {
+        return element;
+      }
+    }
+    throw new IllegalArgumentException(name + " not found in " + elements);
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/TypeSpecTest.java b/src/test/java/com/squareup/javapoet/TypeSpecTest.java
index 9cd22c2..0f67c5c 100644
--- a/src/test/java/com/squareup/javapoet/TypeSpecTest.java
+++ b/src/test/java/com/squareup/javapoet/TypeSpecTest.java
@@ -17,6 +17,7 @@
 
 import com.google.common.collect.ImmutableMap;
 import com.google.testing.compile.CompilationRule;
+import java.io.File;
 import java.io.IOException;
 import java.io.Serializable;
 import java.math.BigDecimal;
@@ -985,6 +986,74 @@
         + "}\n");
   }
 
+  @Test public void simpleNameConflictsWithTypeVariable() {
+    ClassName inPackage = ClassName.get("com.squareup.tacos", "InPackage");
+    ClassName otherType = ClassName.get("com.other", "OtherType");
+    ClassName methodInPackage = ClassName.get("com.squareup.tacos", "MethodInPackage");
+    ClassName methodOtherType = ClassName.get("com.other", "MethodOtherType");
+    TypeSpec gen = TypeSpec.classBuilder("Gen")
+        .addTypeVariable(TypeVariableName.get("InPackage"))
+        .addTypeVariable(TypeVariableName.get("OtherType"))
+        .addField(FieldSpec.builder(inPackage, "inPackage").build())
+        .addField(FieldSpec.builder(otherType, "otherType").build())
+        .addMethod(MethodSpec.methodBuilder("withTypeVariables")
+            .addTypeVariable(TypeVariableName.get("MethodInPackage"))
+            .addTypeVariable(TypeVariableName.get("MethodOtherType"))
+            .addStatement("$T inPackage = null", methodInPackage)
+            .addStatement("$T otherType = null", methodOtherType)
+            .build())
+        .addMethod(MethodSpec.methodBuilder("withoutTypeVariables")
+            .addStatement("$T inPackage = null", methodInPackage)
+            .addStatement("$T otherType = null", methodOtherType)
+            .build())
+        .addMethod(MethodSpec.methodBuilder("againWithTypeVariables")
+            .addTypeVariable(TypeVariableName.get("MethodInPackage"))
+            .addTypeVariable(TypeVariableName.get("MethodOtherType"))
+            .addStatement("$T inPackage = null", methodInPackage)
+            .addStatement("$T otherType = null", methodOtherType)
+            .build())
+        // https://github.com/square/javapoet/pull/657#discussion_r205514292
+        .addMethod(MethodSpec.methodBuilder("masksEnclosingTypeVariable")
+            .addTypeVariable(TypeVariableName.get("InPackage"))
+            .build())
+        .addMethod(MethodSpec.methodBuilder("hasSimpleNameThatWasPreviouslyMasked")
+            .addStatement("$T inPackage = null", inPackage)
+            .build())
+        .build();
+    assertThat(toString(gen)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import com.other.MethodOtherType;\n"
+        + "\n"
+        + "class Gen<InPackage, OtherType> {\n"
+        + "  com.squareup.tacos.InPackage inPackage;\n"
+        + "\n"
+        + "  com.other.OtherType otherType;\n"
+        + "\n"
+        + "  <MethodInPackage, MethodOtherType> void withTypeVariables() {\n"
+        + "    com.squareup.tacos.MethodInPackage inPackage = null;\n"
+        + "    com.other.MethodOtherType otherType = null;\n"
+        + "  }\n"
+        + "\n"
+        + "  void withoutTypeVariables() {\n"
+        + "    MethodInPackage inPackage = null;\n"
+        + "    MethodOtherType otherType = null;\n"
+        + "  }\n"
+        + "\n"
+        + "  <MethodInPackage, MethodOtherType> void againWithTypeVariables() {\n"
+        + "    com.squareup.tacos.MethodInPackage inPackage = null;\n"
+        + "    com.other.MethodOtherType otherType = null;\n"
+        + "  }\n"
+        + "\n"
+        + "  <InPackage> void masksEnclosingTypeVariable() {\n"
+        + "  }\n"
+        + "\n"
+        + "  void hasSimpleNameThatWasPreviouslyMasked() {\n"
+        + "    com.squareup.tacos.InPackage inPackage = null;\n"
+        + "  }\n"
+        + "}\n");
+  }
+
   @Test public void originatingElementsIncludesThoseOfNestedTypes() {
     Element outerElement = Mockito.mock(Element.class);
     Element innerElement = Mockito.mock(Element.class);
@@ -1780,7 +1849,8 @@
         + "  }\n"
         + "\n"
         + "  /**\n"
-        + "   * chosen by fair dice roll ;) */\n"
+        + "   * chosen by fair dice roll ;)\n"
+        + "   */\n"
         + "  public int getRandomQuantity() {\n"
         + "    return 4;\n"
         + "  }\n"
@@ -1839,7 +1909,7 @@
 
   @Test public void nullModifiersAddition() {
     try {
-      TypeSpec.classBuilder("Taco").addModifiers((Modifier) null);
+      TypeSpec.classBuilder("Taco").addModifiers((Modifier) null).build();
       fail();
     } catch(IllegalArgumentException expected) {
       assertThat(expected.getMessage())
@@ -2196,6 +2266,7 @@
 
   @Test public void initializersToBuilder() {
     // Tests if toBuilder() contains correct static and instance initializers
+    Element originatingElement = getElement(TypeSpecTest.class);
     TypeSpec taco = TypeSpec.classBuilder("Taco")
         .addField(String.class, "foo", Modifier.PRIVATE)
         .addField(String.class, "FOO", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
@@ -2212,10 +2283,16 @@
         .addInitializerBlock(CodeBlock.builder()
             .addStatement("foo = $S", "FOO")
             .build())
+        .addOriginatingElement(originatingElement)
+        .alwaysQualify("com.example.AlwaysQualified")
         .build();
 
     TypeSpec recreatedTaco = taco.toBuilder().build();
     assertThat(toString(taco)).isEqualTo(toString(recreatedTaco));
+    assertThat(taco.originatingElements)
+        .containsExactlyElementsIn(recreatedTaco.originatingElements);
+    assertThat(taco.alwaysQualifiedNames)
+        .containsExactlyElementsIn(recreatedTaco.alwaysQualifiedNames);
 
     TypeSpec initializersAdded = taco.toBuilder()
         .addInitializerBlock(CodeBlock.builder()
@@ -2356,4 +2433,122 @@
     assertThat(TypeSpec.enumBuilder(className).addEnumConstant("A").build().name).isEqualTo("Example");
     assertThat(TypeSpec.annotationBuilder(className).build().name).isEqualTo("Example");
   }
+
+  @Test
+  public void modifyAnnotations() {
+    TypeSpec.Builder builder =
+        TypeSpec.classBuilder("Taco")
+            .addAnnotation(Override.class)
+            .addAnnotation(SuppressWarnings.class);
+
+    builder.annotations.remove(1);
+    assertThat(builder.build().annotations).hasSize(1);
+  }
+
+  @Test
+  public void modifyModifiers() {
+    TypeSpec.Builder builder =
+        TypeSpec.classBuilder("Taco").addModifiers(Modifier.PUBLIC, Modifier.FINAL);
+
+    builder.modifiers.remove(1);
+    assertThat(builder.build().modifiers).containsExactly(Modifier.PUBLIC);
+  }
+
+  @Test
+  public void modifyFields() {
+    TypeSpec.Builder builder = TypeSpec.classBuilder("Taco")
+        .addField(int.class, "source");
+
+    builder.fieldSpecs.remove(0);
+    assertThat(builder.build().fieldSpecs).isEmpty();
+  }
+
+  @Test
+  public void modifyTypeVariables() {
+    TypeVariableName t = TypeVariableName.get("T");
+    TypeSpec.Builder builder =
+        TypeSpec.classBuilder("Taco")
+            .addTypeVariable(t)
+            .addTypeVariable(TypeVariableName.get("V"));
+
+    builder.typeVariables.remove(1);
+    assertThat(builder.build().typeVariables).containsExactly(t);
+  }
+
+  @Test
+  public void modifySuperinterfaces() {
+    TypeSpec.Builder builder = TypeSpec.classBuilder("Taco")
+        .addSuperinterface(File.class);
+
+    builder.superinterfaces.clear();
+    assertThat(builder.build().superinterfaces).isEmpty();
+  }
+
+  @Test
+  public void modifyMethods() {
+    TypeSpec.Builder builder = TypeSpec.classBuilder("Taco")
+        .addMethod(MethodSpec.methodBuilder("bell").build());
+
+    builder.methodSpecs.clear();
+    assertThat(builder.build().methodSpecs).isEmpty();
+  }
+
+  @Test
+  public void modifyTypes() {
+    TypeSpec.Builder builder = TypeSpec.classBuilder("Taco")
+        .addType(TypeSpec.classBuilder("Bell").build());
+
+    builder.typeSpecs.clear();
+    assertThat(builder.build().typeSpecs).isEmpty();
+  }
+
+  @Test
+  public void modifyEnumConstants() {
+    TypeSpec constantType = TypeSpec.anonymousClassBuilder("").build();
+    TypeSpec.Builder builder = TypeSpec.enumBuilder("Taco")
+        .addEnumConstant("BELL", constantType)
+        .addEnumConstant("WUT", TypeSpec.anonymousClassBuilder("").build());
+
+    builder.enumConstants.remove("WUT");
+    assertThat(builder.build().enumConstants).containsExactly("BELL", constantType);
+  }
+
+  @Test
+  public void modifyOriginatingElements() {
+    TypeSpec.Builder builder = TypeSpec.classBuilder("Taco")
+        .addOriginatingElement(Mockito.mock(Element.class));
+
+    builder.originatingElements.clear();
+    assertThat(builder.build().originatingElements).isEmpty();
+  }
+    
+  @Test public void javadocWithTrailingLineDoesNotAddAnother() {
+    TypeSpec spec = TypeSpec.classBuilder("Taco")
+        .addJavadoc("Some doc with a newline\n")
+        .build();
+
+    assertThat(toString(spec)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "/**\n"
+        + " * Some doc with a newline\n"
+        + " */\n"
+        + "class Taco {\n"
+        + "}\n");
+  }
+
+  @Test public void javadocEnsuresTrailingLine() {
+    TypeSpec spec = TypeSpec.classBuilder("Taco")
+        .addJavadoc("Some doc with a newline")
+        .build();
+
+    assertThat(toString(spec)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "/**\n"
+        + " * Some doc with a newline\n"
+        + " */\n"
+        + "class Taco {\n"
+        + "}\n");
+  }
 }