Merge commit 'a5db06d'

This is the upstream commit for JavaPoet v1.11.1

Change-Id: I3372f7a59c8888b66930b67e73f8a9d3a6c51167
diff --git a/.buildscript/deploy_snapshot.sh b/.buildscript/deploy_snapshot.sh
new file mode 100755
index 0000000..395a873
--- /dev/null
+++ b/.buildscript/deploy_snapshot.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# Deploy a jar, source jar, and javadoc jar to Sonatype's snapshot repo.
+#
+# Adapted from https://coderwall.com/p/9b_lfq and
+# https://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/
+
+SLUG="square/javapoet"
+JDK="oraclejdk8"
+BRANCH="master"
+
+set -e
+
+if [ "$TRAVIS_REPO_SLUG" != "$SLUG" ]; then
+  echo "Skipping snapshot deployment: wrong repository. Expected '$SLUG' but was '$TRAVIS_REPO_SLUG'."
+elif [ "$TRAVIS_JDK_VERSION" != "$JDK" ]; then
+  echo "Skipping snapshot deployment: wrong JDK. Expected '$JDK' but was '$TRAVIS_JDK_VERSION'."
+elif [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
+  echo "Skipping snapshot deployment: was pull request."
+elif [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then
+  echo "Skipping snapshot deployment: wrong branch. Expected '$BRANCH' but was '$TRAVIS_BRANCH'."
+else
+  echo "Deploying snapshot..."
+  mvn clean source:jar javadoc:jar deploy --settings=".buildscript/settings.xml" -Dmaven.test.skip=true
+  echo "Snapshot deployed!"
+fi
diff --git a/.buildscript/settings.xml b/.buildscript/settings.xml
new file mode 100644
index 0000000..91f444b
--- /dev/null
+++ b/.buildscript/settings.xml
@@ -0,0 +1,9 @@
+<settings>
+  <servers>
+    <server>
+      <id>sonatype-nexus-snapshots</id>
+      <username>${env.CI_DEPLOY_USERNAME}</username>
+      <password>${env.CI_DEPLOY_PASSWORD}</password>
+    </server>
+  </servers>
+</settings>
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..be0d31a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+.classpath
+.project
+.settings
+.checkstyle
+eclipsebin
+
+bin
+gen
+build
+out
+lib
+
+target
+pom.xml.*
+release.properties
+
+.idea
+*.iml
+classes
+
+obj
+
+.DS_Store
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..4e0a881
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,46 @@
+language: java
+
+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
+  allow_failures:
+    # ErrorProne/javac is not yet working on JDK 11
+    - env: JDK='Oracle JDK 11'
+    - env: JDK='OpenJDK 11'
+
+# 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
+
+env:
+  global:
+    - secure: "nkVNCk8H2orIZOmow0t+Qub1lFQCYpJgNZf17zYI5x0JVqQNCqkcTYYDHqzwkvkmixXFCrfYZQuXy7x2qg9zjCX+vmhlmiMWwe8dNa34OLTseuuR2irS0C8nRGRYxKM7EGenRZSqbFVUksKRm2iWnHKxtmCzeDaS7MoMit2wdUo="
+    - secure: "j8+hPaZnyM+UlOBYOEA96fPbVWbN6bMQ28SGQnFMwxo2axHi9ww9Au1N7002HzHnxX8iyesdWFBigArnEL8zKEoXH9Bmur0sn3Ys4bu72C3ozscP4cjXfYSHj8aVLp1EIMdQPDF7MkCccx9l7ONdsW0ltmdiVUtDxzqkH+63WLU="
+
+branches:
+  except:
+    - gh-pages
+
+notifications:
+  email: false
+
+sudo: false
+
+cache:
+  directories:
+    - $HOME/.m2
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..3933dd0
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,302 @@
+Change Log
+==========
+
+JavaPoet 1.11.1 *(2018-05-16)*
+-----------------------------
+
+ * Fix: JavaPoet 1.11 had a regression where `TypeName.get()` would throw on error types, masking
+   other errors in an annotation processing round. This is fixed with a test to prevent future
+   regressions!
+
+
+JavaPoet 1.11.0 *(2018-04-29)*
+-----------------------------
+
+ * New: Support `TYPE_USE` annotations on each enclosing `ClassName`.
+ * New: Work around a compiler bug in `TypeName.get(TypeElement)`. There was a problem getting an
+   element's kind when building from source ABIs.
+
+
+JavaPoet 1.10.0 *(2018-01-27)*
+-----------------------------
+
+ * **JavaPoet now requires Java 8 or newer.**
+ * New: `$Z` as an optional newline (zero-width space) if a line may exceed 100 chars.
+ * New: `CodeBlock.join()` and `CodeBlock.joining()` let you join codeblocks by delimiters.
+ * New: Add `CodeBlock.Builder.isEmpty()`.
+ * New: `addStatement(CodeBlock)` overloads for `CodeBlock` and `MethodSpec`.
+ * Fix: Include annotations when emitting type variables.
+ * Fix: Use the right imports for annotated type parameters.
+ * Fix: Don't incorrectly escape classnames that start with `$`.
+
+
+JavaPoet 1.9.0 *(2017-05-13)*
+-----------------------------
+
+ * Fix: Don't emit incorrect code when the declared type's signature references another type with
+   the same simple name.
+ * Fix: Support anonymous inner classes in `ClassName.get()`.
+ * New: `MethodSpec.Builder.addNamedCode()` and `TypeSpec.anonymousClassBuilder(CodeBlock)`.
+
+
+JavaPoet 1.8.0 *(2016-11-09)*
+-----------------------------
+
+ * New: Basic support for line wrapping. Use `$W` to insert a Wrappable Whitespace character. It'll
+   emit either a single space or a newline with appropriate indentation.
+ * New: Named arguments in `CodeBlock`. These are intended to make larger code snippets easier to
+   read:
+
+   ```
+    Map<String, Object> map = new LinkedHashMap<>();
+    map.put("count", 3);
+    map.put("greeting", "Hello, ");
+    map.put("system", System.class);
+
+    String template = ""
+        + "for (int i = 0; i < $count:L; i++) {\n"
+        + "  $system:T.out.println($greeting:S + list.get(i));\n"
+        + "}\n";
+
+    CodeBlock.Builder builder = CodeBlock.builder();
+    builder.addNamed(template, map);
+   ```
+
+ * New: `addJavadoc(CodeBlock)` overloads for TypeSpec, MethodSpec, and FieldSpec.
+ * New: `MethodSpec.addComment()` makes it easy to add a `// single-line comment.`
+ * New: `ClassName.getReflectionName()` returns a string like `java.util.Map$Entry`.
+ * Fix: Always write UTF-8. Previously JavaPoet would use the system default charset which was
+   potentially inconsistent across environments.
+ * Fix: Permit (constant) fields to be defined in annotation types.
+
+
+JavaPoet 1.7.0 *(2016-04-26)*
+-----------------------------
+
+ * New: Support parameterized types that enclose other types, like `Outer<String>.Inner`.
+ * New: `TypeName.isBoxedPrimitive()`.
+
+
+JavaPoet 1.6.1 *(2016-03-21)*
+-----------------------------
+
+ * Fix: Double quotes and backslashes in string literals were not properly quoted in 1.6.0. This is
+   now fixed.
+
+
+JavaPoet 1.6.0 *(2016-03-19)*
+-----------------------------
+
+ * New: Revive `CodeBlock.of()`, a handy factory method for building code blocks.
+ * New: Add `TypeSpec` factory methods that take a `ClassName`.
+ * New: `TypeName.annotated()` adds an annotation to a type.
+ * New: `TypeVariableName.withBounds()` adds bounds to a type variable.
+ * New: `TypeSpec.Builder.addInitializerBlock()` adds an instance initializer.
+ * Fix: Make `TypeSpec.Kind` enum public. This can be used to check if a `TypeSpec` is a class,
+   interface, enum, or annotation.
+ * Fix: Don’t break import resolution on annotated types.
+ * Fix: Forbid unexpected modifiers like `private` on annotation members.
+ * Fix: Deduplicate exceptions in `MethodSpec.Builder`.
+ * Fix: Treat `ErrorType` like a regular `DeclaredType` in `TypeName.get()`. This should make it
+   easier to write annotation processors.
+
+
+JavaPoet 1.5.1 *(2016-01-10)*
+-----------------------------
+
+ * Fix: Annotated `TypeName` instances are only equal if their annotations are equal.
+
+JavaPoet 1.5.0 *(2016-01-10)*
+-----------------------------
+
+ * New: `import static`! See `JavaFile.Builder.addStaticImport()` variants.
+ * New: Overload `NameAllocator.newName(String)` for creating a one-off name without a tag.
+ * Fix: AnnotationSpec escapes character literals properly.
+ * Fix: Don't stack overflow when `TypeVariableName` is part of `ParameterizedTypeName`.
+ * Fix: Reporting not used indexed arguments in like `add("$1S", "a", "b")`.
+ * Fix: Prevent import of types located in the default package, i.e. have no package name.
+
+
+JavaPoet 1.4.0 *(2015-11-13)*
+-----------------------------
+
+ * New: `AnnotationSpec.get(Annotation)`.
+ * New: Type annotations! `TypeName.annotated()` can attach annotations like `@Nullable` directly to
+   types. This works for both top-level types and type parameters as in `List<@Nullable String>`.
+ * New: `equals()` and `hashCode()` on `AnnotationSpec`, `CodeBlock`, `FieldSpec`, `JavaFile`,
+   `MethodSpec`, `ParameterSpec`, `TypeName`, and `TypeSpec`.
+ * New: `NameAllocator.clone()` to refine a NameAllocator for use in an inner scope code block.
+ * Fix: Don't stack overflow when `TypeVariableName` gets a self-referential type.
+ * Fix: Better handling of name collisions on imports. Previously JavaPoet did the wrong thing when
+   a referenced type and a nested types had the same name.
+
+
+JavaPoet 1.3.0 *(2015-09-20)*
+-----------------------------
+
+ * New: `NameAllocator` API makes it easy to declare non-conflicting names.
+ * New: Support annotations on enum values.
+ * Fix: Avoid infinite recursion in `TypeName.get(TypeMirror)`.
+ * Fix: Use qualified name for conflicting simple names in the same file.
+ * Fix: Better messages for parameter indexing errors.
+
+
+JavaPoet 1.2.0 *(2015-07-04)*
+-----------------------------
+
+ * New: Arguments may have positional indexes like `$1T` and `$2N`. Indexes can be used to refer to
+   the same argument multiple times in a single format string.
+ * New: Permit Javadoc on enum constants.
+ * New: Class initializer blocks with `addStaticBlock()`.
+ * Fix: `MethodSpec.overriding()` retains annotations.
+
+
+JavaPoet 1.1.0 *(2015-05-25)*
+-----------------------------
+
+ * New: Eager validation of argument types like `$T` and `$N`.
+ * New: `MethodSpec.varargs(boolean)` to generate varags methods.
+ * New: `AnnotationSpec.get()` and `MethodSpec.overriding()` to create annotations and methods from
+   the `javax.lang.model` API.
+ * New: `JavaFile.toJavaFileObject()`.
+ * New: Java 8 `DEFAULT` modifier.
+ * New: `toBuilder()` methods to build upon objects already constructed.
+ * New: Generate `@interface` annotation types.
+ * New: `TypeName.box()` and `TypeName.unbox()` convenience APIs.
+ * Fix: `nextControlFlow()` accepts arguments.
+ * Fix: Reject duplicate calls to set the superclass.
+ * Fix: `WildcardTypeName.get(WildcardType)` no longer throws a `NullPointerException`.
+ * Fix: Don't allow double field initialization.
+
+JavaPoet 1.0.0 *(2015-01-28)*
+-----------------------------
+
+ * This update is a complete rewrite. The project name is now `javapoet`. We renamed the it so you
+   can simultaneously use the old JavaWriter API and our new builder-based APIs in one project.
+ * Immutable value objects and builders. Instead of streaming the `.java` file from top to bottom,
+   you now define members in whatever way is convenient.
+ * We now use our own models for type names.
+ * Imports are now added automatically.
+
+
+JavaWriter 2.5.1 *(2014-12-03)*
+-------------------------------
+
+ * New: `StringLiteral` type which encapsulates the behavior of `stringLiteral`.
+ * Fix: Use canonical name when emitting a class import.
+ * Fix: Apply type compression to varargs and array types.
+ * Fix: Restore binary compatibility with pre-2.5 versions.
+
+
+JavaWriter 2.5.0 *(2014-04-18)*
+-------------------------------
+
+ * New: Methods in interfaces will always have no body declaration.
+ * New: Control flow begin declaration now supports String format arguments.
+ * Fix: Truncate any generic type when emitting constructors.
+ * Fix: Do not emit trailing whitespace after '=' at end-of-line.
+
+
+JavaWriter 2.4.0 *(2014-01-10)*
+-------------------------------
+
+ * New: Properly indent hanging lines in field initializers.
+ * New: `emitEnumValue` variant which exposes a boolean of whether the current value is the last.
+
+
+JavaWriter 2.3.1 *(2013-12-16)*
+-------------------------------
+
+ * Fix: Properly handle subpackages of `java.lang` in `compressType`.
+
+
+JavaWriter 2.3.0 *(2013-11-24)*
+-------------------------------
+
+ * New: Configurable indent level via `setIndent`.
+ * New: `beginConstructor` method is a semantically clearer alternative for constructors.
+ * New: `emitEnumValues` method emits a list of values followed by semicolon.
+ * `emitImports` now supports `Class` arguments directly.
+ * Previously-deprecated, `int`-based modifier methods have been removed.
+
+
+JavaWriter 2.2.1 *(2013-10-23)*
+-------------------------------
+
+ * Fix: Do not emit trailing whitespace for empty Javadoc lines.
+
+
+JavaWriter 2.2.0 *(2013-09-25)*
+-------------------------------
+
+ * `setCompressingTypes` controls whether types are emitted as fully-qualified or not.
+
+
+JavaWriter 2.1.2 *(2013-08-23)*
+-------------------------------
+
+ * Attempt to keep annotations on a single line.
+
+
+JavaWriter 2.1.1 *(2013-07-23)*
+-------------------------------
+
+ * Fix: `stringLiteral` now correctly handles escapes and control characters.
+
+
+JavaWriter 2.1.0 *(2013-07-15)*
+-------------------------------
+
+ * New: All methods now take a `Set` of `Modifier`s rather than an `int`. The `int` methods are
+   now deprecated for removal in JavaPoet 1.0.
+ * Annotations with a single "value" attribute will now omit the key.
+
+
+JavaWriter 2.0.1 *(2013-06-17)*
+-------------------------------
+
+ * Correct casing of `emitSingleLineComment`.
+
+
+JavaWriter 2.0.0 *(2013-06-06)*
+-------------------------------
+
+ * Package name is now `com.squareup.javawriter`.
+ * Support declaring `throws` clause on methods.
+
+
+JavaWriter 1.0.5 *(2013-05-08)*
+-------------------------------
+
+ * Fix: Fully qualify types whose simple name matches an import.
+
+
+JavaWriter 1.0.4 *(2013-03-15)*
+-------------------------------
+
+ * Fix: Static import emit now properly supports method imports.
+
+
+JavaWriter 1.0.3 *(2013-02-21)*
+-------------------------------
+
+ * Add support for emitting static imports.
+
+
+JavaWriter 1.0.2 *(2013-02-11)*
+-------------------------------
+
+ * Add `type` API for helping build generic types.
+ * Minor performance improvements.
+
+
+JavaWriter 1.0.1 *(2013-02-03)*
+-------------------------------
+
+ * Expose `compressType` API.
+
+
+JavaWriter 1.0.0 *(2013-02-01)*
+-------------------------------
+
+Initial release.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..8131805
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,17 @@
+Contributing
+============
+
+If you would like to contribute code you can do so through GitHub by forking
+the repository and sending a pull request.
+
+When submitting code, please make every effort to follow existing conventions
+and style in order to keep the code as readable as possible. Please also make
+sure your code compiles by running `mvn clean verify`. Checkstyle failures
+during compilation indicate errors in your style and can be viewed in the
+`checkstyle-result.xml` file.
+
+Before your code can be accepted into the project you must also sign the
+[Individual Contributor License Agreement (CLA)][1].
+
+
+ [1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1dffbad
--- /dev/null
+++ b/README.md
@@ -0,0 +1,889 @@
+JavaPoet
+========
+
+`JavaPoet` is a Java API for generating `.java` source files.
+
+Source file generation can be useful when doing things such as annotation processing or interacting
+with metadata files (e.g., database schemas, protocol formats). By generating code, you eliminate
+the need to write boilerplate while also keeping a single source of truth for the metadata.
+
+
+### Example
+
+Here's a (boring) `HelloWorld` class:
+
+```java
+package com.example.helloworld;
+
+public final class HelloWorld {
+  public static void main(String[] args) {
+    System.out.println("Hello, JavaPoet!");
+  }
+}
+```
+
+And this is the (exciting) code to generate it with JavaPoet:
+
+```java
+MethodSpec main = MethodSpec.methodBuilder("main")
+    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+    .returns(void.class)
+    .addParameter(String[].class, "args")
+    .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
+    .build();
+
+TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
+    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+    .addMethod(main)
+    .build();
+
+JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
+    .build();
+
+javaFile.writeTo(System.out);
+```
+
+To declare the main method, we've created a `MethodSpec` "main" configured with modifiers, return
+type, parameters and code statements. We add the main method to a `HelloWorld` class, and then add
+that to a `HelloWorld.java` file.
+
+In this case we write the file to `System.out`, but we could also get it as a string
+(`JavaFile.toString()`) or write it to the file system (`JavaFile.writeTo()`).
+
+The [Javadoc][javadoc] catalogs the complete JavaPoet API, which we explore below.
+
+### Code & Control Flow
+
+Most of JavaPoet's API uses plain old immutable Java objects. There's also builders, method chaining
+and varargs to make the API friendly. JavaPoet offers models for classes & interfaces (`TypeSpec`),
+fields (`FieldSpec`), methods & constructors (`MethodSpec`), parameters (`ParameterSpec`) and
+annotations (`AnnotationSpec`).
+
+But the _body_ of methods and constructors is not modeled. There's no expression class, no
+statement class or syntax tree nodes. Instead, JavaPoet uses strings for code blocks:
+
+```java
+MethodSpec main = MethodSpec.methodBuilder("main")
+    .addCode(""
+        + "int total = 0;\n"
+        + "for (int i = 0; i < 10; i++) {\n"
+        + "  total += i;\n"
+        + "}\n")
+    .build();
+```
+
+Which generates this:
+
+```java
+void main() {
+  int total = 0;
+  for (int i = 0; i < 10; i++) {
+    total += i;
+  }
+}
+```
+
+The manual semicolons, line wrapping, and indentation are tedious and so JavaPoet offers APIs to
+make it easier. There's `addStatement()` which takes care of semicolons and newline, and
+`beginControlFlow()` + `endControlFlow()` which are used together for braces, newlines, and
+indentation:
+
+```java
+MethodSpec main = MethodSpec.methodBuilder("main")
+    .addStatement("int total = 0")
+    .beginControlFlow("for (int i = 0; i < 10; i++)")
+    .addStatement("total += i")
+    .endControlFlow()
+    .build();
+```
+
+This example is lame because the generated code is constant! Suppose instead of just adding 0 to 10,
+we want to make the operation and range configurable. Here's a method that generates a method:
+
+```java
+private MethodSpec computeRange(String name, int from, int to, String op) {
+  return MethodSpec.methodBuilder(name)
+      .returns(int.class)
+      .addStatement("int result = 1")
+      .beginControlFlow("for (int i = " + from + "; i < " + to + "; i++)")
+      .addStatement("result = result " + op + " i")
+      .endControlFlow()
+      .addStatement("return result")
+      .build();
+}
+```
+
+And here's what we get when we call `computeRange("multiply10to20", 10, 20, "*")`:
+
+```java
+int multiply10to20() {
+  int result = 1;
+  for (int i = 10; i < 20; i++) {
+    result = result * i;
+  }
+  return result;
+}
+```
+
+Methods generating methods! And since JavaPoet generates source instead of bytecode, you can
+read through it to make sure it's right.
+
+
+### $L for Literals
+
+The string-concatenation in calls to `beginControlFlow()` and `addStatement` is distracting. Too
+many operators. To address this, JavaPoet offers a syntax inspired-by but incompatible-with
+[`String.format()`][formatter]. It accepts **`$L`** to emit a **literal** value in the output. This
+works just like `Formatter`'s `%s`:
+
+```java
+private MethodSpec computeRange(String name, int from, int to, String op) {
+  return MethodSpec.methodBuilder(name)
+      .returns(int.class)
+      .addStatement("int result = 0")
+      .beginControlFlow("for (int i = $L; i < $L; i++)", from, to)
+      .addStatement("result = result $L i", op)
+      .endControlFlow()
+      .addStatement("return result")
+      .build();
+}
+```
+
+Literals are emitted directly to the output code with no escaping. Arguments for literals may be
+strings, primitives, and a few JavaPoet types described below.
+
+### $S for Strings
+
+When emitting code that includes string literals, we can use **`$S`** to emit a **string**, complete
+with wrapping quotation marks and escaping. Here's a program that emits 3 methods, each of which
+returns its own name:
+
+```java
+public static void main(String[] args) throws Exception {
+  TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
+      .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+      .addMethod(whatsMyName("slimShady"))
+      .addMethod(whatsMyName("eminem"))
+      .addMethod(whatsMyName("marshallMathers"))
+      .build();
+
+  JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
+      .build();
+
+  javaFile.writeTo(System.out);
+}
+
+private static MethodSpec whatsMyName(String name) {
+  return MethodSpec.methodBuilder(name)
+      .returns(String.class)
+      .addStatement("return $S", name)
+      .build();
+}
+```
+
+In this case, using `$S` gives us quotation marks:
+
+```java
+public final class HelloWorld {
+  String slimShady() {
+    return "slimShady";
+  }
+
+  String eminem() {
+    return "eminem";
+  }
+
+  String marshallMathers() {
+    return "marshallMathers";
+  }
+}
+```
+
+### $T for Types
+
+We Java programmers love our types: they make our code easier to understand. And JavaPoet is on
+board. It has rich built-in support for types, including automatic generation of `import`
+statements. Just use **`$T`** to reference **types**:
+
+```java
+MethodSpec today = MethodSpec.methodBuilder("today")
+    .returns(Date.class)
+    .addStatement("return new $T()", Date.class)
+    .build();
+
+TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
+    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+    .addMethod(today)
+    .build();
+
+JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
+    .build();
+
+javaFile.writeTo(System.out);
+```
+
+That generates the following `.java` file, complete with the necessary `import`:
+
+```java
+package com.example.helloworld;
+
+import java.util.Date;
+
+public final class HelloWorld {
+  Date today() {
+    return new Date();
+  }
+}
+```
+
+We passed `Date.class` to reference a class that just-so-happens to be available when we're
+generating code. This doesn't need to be the case. Here's a similar example, but this one
+references a class that doesn't exist (yet):
+
+```java
+ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
+
+MethodSpec today = MethodSpec.methodBuilder("tomorrow")
+    .returns(hoverboard)
+    .addStatement("return new $T()", hoverboard)
+    .build();
+```
+
+And that not-yet-existent class is imported as well:
+
+```java
+package com.example.helloworld;
+
+import com.mattel.Hoverboard;
+
+public final class HelloWorld {
+  Hoverboard tomorrow() {
+    return new Hoverboard();
+  }
+}
+```
+
+The `ClassName` type is very important, and you'll need it frequently when you're using JavaPoet.
+It can identify any _declared_ class. Declared types are just the beginning of Java's rich type
+system: we also have arrays, parameterized types, wildcard types, and type variables. JavaPoet has
+classes for building each of these:
+
+```java
+ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
+ClassName list = ClassName.get("java.util", "List");
+ClassName arrayList = ClassName.get("java.util", "ArrayList");
+TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);
+
+MethodSpec beyond = MethodSpec.methodBuilder("beyond")
+    .returns(listOfHoverboards)
+    .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
+    .addStatement("result.add(new $T())", hoverboard)
+    .addStatement("result.add(new $T())", hoverboard)
+    .addStatement("result.add(new $T())", hoverboard)
+    .addStatement("return result")
+    .build();
+```
+
+JavaPoet will decompose each type and import its components where possible.
+
+```java
+package com.example.helloworld;
+
+import com.mattel.Hoverboard;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class HelloWorld {
+  List<Hoverboard> beyond() {
+    List<Hoverboard> result = new ArrayList<>();
+    result.add(new Hoverboard());
+    result.add(new Hoverboard());
+    result.add(new Hoverboard());
+    return result;
+  }
+}
+```
+
+#### Import static
+
+JavaPoet supports `import static`. It does it via explicitly collecting type member names. Let's
+enhance the previous example with some static sugar:
+
+```java
+...
+ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards");
+
+MethodSpec beyond = MethodSpec.methodBuilder("beyond")
+    .returns(listOfHoverboards)
+    .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
+    .addStatement("result.add($T.createNimbus(2000))", hoverboard)
+    .addStatement("result.add($T.createNimbus(\"2001\"))", hoverboard)
+    .addStatement("result.add($T.createNimbus($T.THUNDERBOLT))", hoverboard, namedBoards)
+    .addStatement("$T.sort(result)", Collections.class)
+    .addStatement("return result.isEmpty() ? $T.emptyList() : result", Collections.class)
+    .build();
+
+TypeSpec hello = TypeSpec.classBuilder("HelloWorld")
+    .addMethod(beyond)
+    .build();
+
+JavaFile.builder("com.example.helloworld", hello)
+    .addStaticImport(hoverboard, "createNimbus")
+    .addStaticImport(namedBoards, "*")
+    .addStaticImport(Collections.class, "*")
+    .build();
+```
+
+JavaPoet will first add your `import static` block to the file as configured, match and mangle
+all calls accordingly and also import all other types as needed.
+
+```java
+package com.example.helloworld;
+
+import static com.mattel.Hoverboard.Boards.*;
+import static com.mattel.Hoverboard.createNimbus;
+import static java.util.Collections.*;
+
+import com.mattel.Hoverboard;
+import java.util.ArrayList;
+import java.util.List;
+
+class HelloWorld {
+  List<Hoverboard> beyond() {
+    List<Hoverboard> result = new ArrayList<>();
+    result.add(createNimbus(2000));
+    result.add(createNimbus("2001"));
+    result.add(createNimbus(THUNDERBOLT));
+    sort(result);
+    return result.isEmpty() ? emptyList() : result;
+  }
+}
+```
+
+### $N for Names
+
+Generated code is often self-referential. Use **`$N`** to refer to another generated declaration by
+its name. Here's a method that calls another:
+
+```java
+public String byteToHex(int b) {
+  char[] result = new char[2];
+  result[0] = hexDigit((b >>> 4) & 0xf);
+  result[1] = hexDigit(b & 0xf);
+  return new String(result);
+}
+
+public char hexDigit(int i) {
+  return (char) (i < 10 ? i + '0' : i - 10 + 'a');
+}
+```
+
+When generating the code above, we pass the `hexDigit()` method as an argument to the `byteToHex()`
+method using `$N`:
+
+```java
+MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
+    .addParameter(int.class, "i")
+    .returns(char.class)
+    .addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
+    .build();
+
+MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
+    .addParameter(int.class, "b")
+    .returns(String.class)
+    .addStatement("char[] result = new char[2]")
+    .addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
+    .addStatement("result[1] = $N(b & 0xf)", hexDigit)
+    .addStatement("return new String(result)")
+    .build();
+```
+
+### Code block format strings
+
+Code blocks may specify the values for their placeholders in a few ways. Only one style may be used
+for each operation on a code block.
+
+#### Relative Arguments
+
+Pass an argument value for each placeholder in the format string to `CodeBlock.add()`. In each
+example, we generate code to say "I ate 3 tacos"
+
+```java
+CodeBlock.builder().add("I ate $L $L", 3, "tacos")
+```
+
+#### Positional Arguments
+
+Place an integer index (1-based) before the placeholder in the format string to specify which
+ argument to use.
+
+```java
+CodeBlock.builder().add("I ate $2L $1L", "tacos", 3)
+```
+
+#### Named Arguments
+
+Use the syntax `$argumentName:X` where `X` is the format character and call `CodeBlock.addNamed()`
+with a map containing all argument keys in the format string. Argument names use characters in
+`a-z`, `A-Z`, `0-9`, and `_`, and must start with a lowercase character.
+
+```java
+Map<String, Object> map = new LinkedHashMap<>();
+map.put("food", "tacos");
+map.put("count", 3);
+CodeBlock.builder().addNamed("I ate $count:L $food:L", map)
+```
+
+### Methods
+
+All of the above methods have a code body. Use `Modifiers.ABSTRACT` to get a method without any
+body. This is only legal if the enclosing class is either abstract or an interface.
+
+```java
+MethodSpec flux = MethodSpec.methodBuilder("flux")
+    .addModifiers(Modifier.ABSTRACT, Modifier.PROTECTED)
+    .build();
+
+TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
+    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+    .addMethod(flux)
+    .build();
+```
+
+Which generates this:
+
+```java
+public abstract class HelloWorld {
+  protected abstract void flux();
+}
+```
+
+The other modifiers work where permitted. Note that when specifying modifiers, JavaPoet uses
+[`javax.lang.model.element.Modifier`][modifier], a class that is not available on Android. This
+limitation applies to code-generating-code only; the output code runs everywhere: JVMs, Android,
+and GWT.
+
+Methods also have parameters, exceptions, varargs, Javadoc, annotations, type variables, and a
+return type. All of these are configured with `MethodSpec.Builder`.
+
+### Constructors
+
+`MethodSpec` is a slight misnomer; it can also be used for constructors:
+
+```java
+MethodSpec flux = MethodSpec.constructorBuilder()
+    .addModifiers(Modifier.PUBLIC)
+    .addParameter(String.class, "greeting")
+    .addStatement("this.$N = $N", "greeting", "greeting")
+    .build();
+
+TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
+    .addModifiers(Modifier.PUBLIC)
+    .addField(String.class, "greeting", Modifier.PRIVATE, Modifier.FINAL)
+    .addMethod(flux)
+    .build();
+```
+
+Which generates this:
+
+```java
+public class HelloWorld {
+  private final String greeting;
+
+  public HelloWorld(String greeting) {
+    this.greeting = greeting;
+  }
+}
+```
+
+For the most part, constructors work just like methods. When emitting code, JavaPoet will place
+constructors before methods in the output file.
+
+### Parameters
+
+Declare parameters on methods and constructors with either `ParameterSpec.builder()` or
+`MethodSpec`'s convenient `addParameter()` API:
+
+```java
+ParameterSpec android = ParameterSpec.builder(String.class, "android")
+    .addModifiers(Modifier.FINAL)
+    .build();
+
+MethodSpec welcomeOverlords = MethodSpec.methodBuilder("welcomeOverlords")
+    .addParameter(android)
+    .addParameter(String.class, "robot", Modifier.FINAL)
+    .build();
+```
+
+Though the code above to generate `android` and `robot` parameters is different, the output is the
+same:
+
+```java
+void welcomeOverlords(final String android, final String robot) {
+}
+```
+
+The extended `Builder` form is necessary when the parameter has annotations (such as `@Nullable`).
+
+### Fields
+
+Like parameters, fields can be created either with builders or by using convenient helper methods:
+
+```java
+FieldSpec android = FieldSpec.builder(String.class, "android")
+    .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
+    .build();
+
+TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
+    .addModifiers(Modifier.PUBLIC)
+    .addField(android)
+    .addField(String.class, "robot", Modifier.PRIVATE, Modifier.FINAL)
+    .build();
+```
+
+Which generates:
+
+```java
+public class HelloWorld {
+  private final String android;
+
+  private final String robot;
+}
+```
+
+The extended `Builder` form is necessary when a field has Javadoc, annotations, or a field
+initializer. Field initializers use the same [`String.format()`][formatter]-like syntax as the code
+blocks above:
+
+```java
+FieldSpec android = FieldSpec.builder(String.class, "android")
+    .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
+    .initializer("$S + $L", "Lollipop v.", 5.0d)
+    .build();
+```
+
+Which generates:
+
+```java
+private final String android = "Lollipop v." + 5.0;
+```
+
+### Interfaces
+
+JavaPoet has no trouble with interfaces. Note that interface methods must always be `PUBLIC
+ABSTRACT` and interface fields must always be `PUBLIC STATIC FINAL`. These modifiers are necessary
+when defining the interface:
+
+```java
+TypeSpec helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
+    .addModifiers(Modifier.PUBLIC)
+    .addField(FieldSpec.builder(String.class, "ONLY_THING_THAT_IS_CONSTANT")
+        .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
+        .initializer("$S", "change")
+        .build())
+    .addMethod(MethodSpec.methodBuilder("beep")
+        .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+        .build())
+    .build();
+```
+
+But these modifiers are omitted when the code is generated. These are the defaults so we don't need
+to include them for `javac`'s benefit!
+
+```java
+public interface HelloWorld {
+  String ONLY_THING_THAT_IS_CONSTANT = "change";
+
+  void beep();
+}
+```
+
+### Enums
+
+Use `enumBuilder` to create the enum type, and `addEnumConstant()` for each value:
+
+```java
+TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
+    .addModifiers(Modifier.PUBLIC)
+    .addEnumConstant("ROCK")
+    .addEnumConstant("SCISSORS")
+    .addEnumConstant("PAPER")
+    .build();
+```
+
+To generate this:
+
+```java
+public enum Roshambo {
+  ROCK,
+
+  SCISSORS,
+
+  PAPER
+}
+```
+
+Fancy enums are supported, where the enum values override methods or call a superclass constructor.
+Here's a comprehensive example:
+
+```java
+TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
+    .addModifiers(Modifier.PUBLIC)
+    .addEnumConstant("ROCK", TypeSpec.anonymousClassBuilder("$S", "fist")
+        .addMethod(MethodSpec.methodBuilder("toString")
+            .addAnnotation(Override.class)
+            .addModifiers(Modifier.PUBLIC)
+            .addStatement("return $S", "avalanche!")
+            .returns(String.class)
+            .build())
+        .build())
+    .addEnumConstant("SCISSORS", TypeSpec.anonymousClassBuilder("$S", "peace")
+        .build())
+    .addEnumConstant("PAPER", TypeSpec.anonymousClassBuilder("$S", "flat")
+        .build())
+    .addField(String.class, "handsign", Modifier.PRIVATE, Modifier.FINAL)
+    .addMethod(MethodSpec.constructorBuilder()
+        .addParameter(String.class, "handsign")
+        .addStatement("this.$N = $N", "handsign", "handsign")
+        .build())
+    .build();
+```
+
+Which generates this:
+
+```java
+public enum Roshambo {
+  ROCK("fist") {
+    @Override
+    public String toString() {
+      return "avalanche!";
+    }
+  },
+
+  SCISSORS("peace"),
+
+  PAPER("flat");
+
+  private final String handsign;
+
+  Roshambo(String handsign) {
+    this.handsign = handsign;
+  }
+}
+```
+
+### Anonymous Inner Classes
+
+In the enum code, we used `Types.anonymousInnerClass()`. Anonymous inner classes can also be used in
+code blocks. They are values that can be referenced with `$L`:
+
+```java
+TypeSpec comparator = TypeSpec.anonymousClassBuilder("")
+    .addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class))
+    .addMethod(MethodSpec.methodBuilder("compare")
+        .addAnnotation(Override.class)
+        .addModifiers(Modifier.PUBLIC)
+        .addParameter(String.class, "a")
+        .addParameter(String.class, "b")
+        .returns(int.class)
+        .addStatement("return $N.length() - $N.length()", "a", "b")
+        .build())
+    .build();
+
+TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
+    .addMethod(MethodSpec.methodBuilder("sortByLength")
+        .addParameter(ParameterizedTypeName.get(List.class, String.class), "strings")
+        .addStatement("$T.sort($N, $L)", Collections.class, "strings", comparator)
+        .build())
+    .build();
+```
+
+This generates a method that contains a class that contains a method:
+
+```java
+void sortByLength(List<String> strings) {
+  Collections.sort(strings, new Comparator<String>() {
+    @Override
+    public int compare(String a, String b) {
+      return a.length() - b.length();
+    }
+  });
+}
+```
+
+One particularly tricky part of defining anonymous inner classes is the arguments to the superclass
+constructor. In the above code we're passing the empty string for no arguments:
+`TypeSpec.anonymousClassBuilder("")`. To pass different parameters use JavaPoet's code block
+syntax with commas to separate arguments.
+
+
+### Annotations
+
+Simple annotations are easy:
+
+```java
+MethodSpec toString = MethodSpec.methodBuilder("toString")
+    .addAnnotation(Override.class)
+    .returns(String.class)
+    .addModifiers(Modifier.PUBLIC)
+    .addStatement("return $S", "Hoverboard")
+    .build();
+```
+
+Which generates this method with an `@Override` annotation:
+
+```java
+  @Override
+  public String toString() {
+    return "Hoverboard";
+  }
+```
+
+Use `AnnotationSpec.builder()` to set properties on annotations:
+
+```java
+MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
+    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+    .addAnnotation(AnnotationSpec.builder(Headers.class)
+        .addMember("accept", "$S", "application/json; charset=utf-8")
+        .addMember("userAgent", "$S", "Square Cash")
+        .build())
+    .addParameter(LogRecord.class, "logRecord")
+    .returns(LogReceipt.class)
+    .build();
+```
+
+Which generates this annotation with `accept` and `userAgent` properties:
+
+```java
+@Headers(
+    accept = "application/json; charset=utf-8",
+    userAgent = "Square Cash"
+)
+LogReceipt recordEvent(LogRecord logRecord);
+```
+
+When you get fancy, annotation values can be annotations themselves. Use `$L` for embedded
+annotations:
+
+```java
+MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
+    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+    .addAnnotation(AnnotationSpec.builder(HeaderList.class)
+        .addMember("value", "$L", AnnotationSpec.builder(Header.class)
+            .addMember("name", "$S", "Accept")
+            .addMember("value", "$S", "application/json; charset=utf-8")
+            .build())
+        .addMember("value", "$L", AnnotationSpec.builder(Header.class)
+            .addMember("name", "$S", "User-Agent")
+            .addMember("value", "$S", "Square Cash")
+            .build())
+        .build())
+    .addParameter(LogRecord.class, "logRecord")
+    .returns(LogReceipt.class)
+    .build();
+```
+
+Which generates this:
+
+```java
+@HeaderList({
+    @Header(name = "Accept", value = "application/json; charset=utf-8"),
+    @Header(name = "User-Agent", value = "Square Cash")
+})
+LogReceipt recordEvent(LogRecord logRecord);
+```
+
+Note that you can call `addMember()` multiple times with the same property name to populate a list
+of values for that property.
+
+### Javadoc
+
+Fields, methods and types can be documented with Javadoc:
+
+```java
+MethodSpec dismiss = MethodSpec.methodBuilder("dismiss")
+    .addJavadoc("Hides {@code message} from the caller's history. Other\n"
+        + "participants in the conversation will continue to see the\n"
+        + "message in their own history unless they also delete it.\n")
+    .addJavadoc("\n")
+    .addJavadoc("<p>Use {@link #delete($T)} to delete the entire\n"
+        + "conversation for all participants.\n", Conversation.class)
+    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+    .addParameter(Message.class, "message")
+    .build();
+```
+
+Which generates this:
+
+```java
+  /**
+   * Hides {@code message} from the caller's history. Other
+   * participants in the conversation will continue to see the
+   * message in their own history unless they also delete it.
+   *
+   * <p>Use {@link #delete(Conversation)} to delete the entire
+   * conversation for all participants.
+   */
+  void dismiss(Message message);
+```
+
+Use `$T` when referencing types in Javadoc to get automatic imports.
+
+Download
+--------
+
+Download [the latest .jar][dl] or depend via Maven:
+```xml
+<dependency>
+  <groupId>com.squareup</groupId>
+  <artifactId>javapoet</artifactId>
+  <version>1.11.1</version>
+</dependency>
+```
+or Gradle:
+```groovy
+compile 'com.squareup:javapoet:1.11.1'
+```
+
+Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap].
+
+
+
+License
+-------
+
+    Copyright 2015 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.
+
+
+
+JavaWriter
+==========
+
+JavaPoet is the successor to [JavaWriter][javawriter]. New projects should prefer JavaPoet because
+it has a stronger code model: it understands types and can manage imports automatically. JavaPoet is
+also better suited to composition: rather than streaming the contents of a `.java` file
+top-to-bottom in a single pass, a file can be assembled as a tree of declarations.
+
+JavaWriter continues to be available in [GitHub][javawriter] and [Maven Central][javawriter_maven].
+
+
+ [dl]: https://search.maven.org/remote_content?g=com.squareup&a=javapoet&v=LATEST
+ [snap]: https://oss.sonatype.org/content/repositories/snapshots/com/squareup/javapoet/
+ [javadoc]: https://square.github.io/javapoet/1.x/javapoet/
+ [javawriter]: https://github.com/square/javapoet/tree/javawriter_2
+ [javawriter_maven]: https://search.maven.org/#artifactdetails%7Ccom.squareup%7Cjavawriter%7C2.5.1%7Cjar
+ [formatter]: https://developer.android.com/reference/java/util/Formatter.html
+ [modifier]: https://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/Modifier.html
diff --git a/checkstyle.xml b/checkstyle.xml
new file mode 100644
index 0000000..bbf690e
--- /dev/null
+++ b/checkstyle.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0"?>
+<!DOCTYPE module PUBLIC
+    "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
+    "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
+
+<module name="Checker">
+  <module name="SuppressWarningsFilter"/>
+  <module name="NewlineAtEndOfFile"/>
+  <module name="FileLength"/>
+  <module name="FileTabCharacter"/>
+
+  <!-- Trailing spaces -->
+  <module name="RegexpSingleline">
+    <property name="format" value="\s+$"/>
+    <property name="message" value="Line has trailing spaces."/>
+  </module>
+
+  <!-- Space after 'for' and 'if' -->
+  <module name="RegexpSingleline">
+    <property name="format" value="^\s*(for|if)\b[^ ]"/>
+    <property name="message" value="Space needed before opening parenthesis."/>
+  </module>
+
+  <!-- For each spacing -->
+  <module name="RegexpSingleline">
+    <property name="format" value="^\s*for \(.*?([^ ]:|:[^ ])"/>
+    <property name="message" value="Space needed around ':' character."/>
+  </module>
+
+  <module name="TreeWalker">
+    <property name="cacheFile" value="${checkstyle.cache.file}"/>
+
+    <!-- Checks for Javadoc comments.                     -->
+    <!-- See http://checkstyle.sf.net/config_javadoc.html -->
+    <!--module name="JavadocMethod"/-->
+    <!--module name="JavadocType"/-->
+    <!--module name="JavadocVariable"/-->
+    <module name="JavadocStyle"/>
+
+
+    <!-- Checks for Naming Conventions.                  -->
+    <!-- See http://checkstyle.sf.net/config_naming.html -->
+    <module name="ConstantName"/>
+    <module name="LocalFinalVariableName"/>
+    <module name="LocalVariableName"/>
+    <module name="MemberName"/>
+    <module name="MethodName"/>
+    <module name="PackageName"/>
+    <module name="ParameterName"/>
+    <module name="StaticVariableName"/>
+    <module name="TypeName"/>
+
+
+    <!-- Checks for imports                              -->
+    <!-- See http://checkstyle.sf.net/config_import.html -->
+    <module name="AvoidStarImport"/>
+    <module name="IllegalImport"/>
+    <!-- defaults to sun.* packages -->
+    <module name="RedundantImport"/>
+    <module name="UnusedImports">
+      <property name="processJavadoc" value="true"/>
+    </module>
+
+
+    <!-- Checks for Size Violations.                    -->
+    <!-- See http://checkstyle.sf.net/config_sizes.html -->
+    <module name="LineLength">
+      <property name="max" value="100"/>
+    </module>
+    <module name="MethodLength">
+      <property name="max" value="160"/>
+    </module>
+    <module name="ParameterNumber"/>
+
+
+    <!-- Checks for whitespace                               -->
+    <!-- See http://checkstyle.sf.net/config_whitespace.html -->
+    <module name="GenericWhitespace"/>
+    <!--<module name="EmptyForIteratorPad"/>-->
+    <module name="MethodParamPad"/>
+    <module name="NoWhitespaceAfter"/>
+    <module name="NoWhitespaceBefore"/>
+    <module name="OperatorWrap"/>
+    <module name="ParenPad"/>
+    <module name="TypecastParenPad"/>
+    <module name="WhitespaceAfter"/>
+    <module name="WhitespaceAround">
+      <property name="tokens"
+          value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN,
+          COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAND, LCURLY, LE, LITERAL_CATCH,
+          LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
+          LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
+          MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, SL, SLIST,
+          SL_ASSIGN, SR, SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, TYPE_EXTENSION_AND"/>
+    </module>
+
+
+    <!-- Modifier Checks                                    -->
+    <!-- See http://checkstyle.sf.net/config_modifiers.html -->
+    <module name="ModifierOrder"/>
+    <module name="RedundantModifier"/>
+
+
+    <!-- Checks for blocks. You know, those {}'s         -->
+    <!-- See http://checkstyle.sf.net/config_blocks.html -->
+    <module name="AvoidNestedBlocks"/>
+    <!--module name="EmptyBlock"/-->
+    <module name="LeftCurly"/>
+    <!--<module name="NeedBraces"/>-->
+    <module name="RightCurly"/>
+
+
+    <!-- Checks for common coding problems               -->
+    <!-- See http://checkstyle.sf.net/config_coding.html -->
+    <!--module name="AvoidInlineConditionals"/-->
+    <module name="CovariantEquals"/>
+    <module name="EmptyStatement"/>
+    <!--<module name="EqualsAvoidNull"/>-->
+    <module name="EqualsHashCode"/>
+    <!--module name="HiddenField"/-->
+    <module name="IllegalInstantiation"/>
+    <module name="InnerAssignment"/>
+    <!--<module name="MagicNumber"/>-->
+    <module name="MissingSwitchDefault"/>
+    <!--module name="RedundantThrows"/-->
+    <module name="SimplifyBooleanExpression"/>
+    <module name="SimplifyBooleanReturn"/>
+
+    <!-- Checks for class design                         -->
+    <!-- See http://checkstyle.sf.net/config_design.html -->
+    <!--module name="DesignForExtension"/-->
+    <module name="FinalClass"/>
+    <module name="HideUtilityClassConstructor"/>
+    <module name="InterfaceIsType"/>
+    <!--module name="VisibilityModifier"/-->
+
+
+    <!-- Miscellaneous other checks.                   -->
+    <!-- See http://checkstyle.sf.net/config_misc.html -->
+    <module name="ArrayTypeStyle"/>
+    <!--module name="FinalParameters"/-->
+    <module name="TodoComment"/>
+    <module name="UpperEll"/>
+
+    <!-- Make the @SuppressWarnings annotations available to Checkstyle -->
+    <module name="SuppressWarningsHolder"/>
+  </module>
+</module>
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..a531637
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.sonatype.oss</groupId>
+    <artifactId>oss-parent</artifactId>
+    <version>7</version>
+  </parent>
+
+  <groupId>com.squareup</groupId>
+  <artifactId>javapoet</artifactId>
+  <version>1.11.1</version>
+
+  <name>JavaPoet</name>
+  <description>Use beautiful Java code to generate beautiful Java code.</description>
+  <url>http://github.com/square/javapoet/</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+    <java.version>1.8</java.version>
+    <junit.version>4.12</junit.version>
+    <truth.version>0.39</truth.version>
+    <compile-testing.version>0.15</compile-testing.version>
+  </properties>
+
+  <scm>
+    <url>http://github.com/square/javapoet/</url>
+    <connection>scm:git:git://github.com/square/javapoet.git</connection>
+    <developerConnection>scm:git:ssh://git@github.com/square/javapoet.git</developerConnection>
+    <tag>HEAD</tag>
+  </scm>
+
+  <issueManagement>
+    <system>GitHub Issues</system>
+    <url>http://github.com/square/javapoet/issues</url>
+  </issueManagement>
+
+  <licenses>
+    <license>
+      <name>Apache 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+    </license>
+  </licenses>
+
+  <organization>
+    <name>Square, Inc.</name>
+    <url>http://squareup.com</url>
+  </organization>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.truth</groupId>
+      <artifactId>truth</artifactId>
+      <version>${truth.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.testing.compile</groupId>
+      <artifactId>compile-testing</artifactId>
+      <version>${compile-testing.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>${junit.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.jimfs</groupId>
+      <artifactId>jimfs</artifactId>
+      <version>1.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>2.13.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jdt.core.compiler</groupId>
+      <artifactId>ecj</artifactId>
+      <version>4.6.1</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.7.0</version>
+        <configuration>
+          <compilerId>javac-with-errorprone</compilerId>
+          <forceJavacCompilerUse>true</forceJavacCompilerUse>
+          <source>${java.version}</source>
+          <target>${java.version}</target>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-compiler-javac-errorprone</artifactId>
+            <version>2.8.2</version>
+          </dependency>
+          <dependency>
+            <groupId>com.google.errorprone</groupId>
+            <artifactId>error_prone_core</artifactId>
+            <version>2.3.1</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+        <version>2.17</version>
+        <dependencies>
+          <dependency>
+            <groupId>com.puppycrawl.tools</groupId>
+            <artifactId>checkstyle</artifactId>
+            <version>8.7</version>
+          </dependency>
+        </dependencies>
+        <configuration>
+          <failsOnError>true</failsOnError>
+          <configLocation>checkstyle.xml</configLocation>
+          <consoleOutput>true</consoleOutput>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>verify</phase>
+            <goals>
+              <goal>checkstyle</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <version>3.0.2</version>
+        <configuration>
+          <archive>
+            <manifestEntries>
+              <Automatic-Module-Name>com.squareup.javapoet</Automatic-Module-Name>
+            </manifestEntries>
+          </archive>
+        </configuration>
+      </plugin>
+
+    </plugins>
+  </build>
+</project>
diff --git a/src/main/java/com/squareup/javapoet/AnnotationSpec.java b/src/main/java/com/squareup/javapoet/AnnotationSpec.java
new file mode 100644
index 0000000..d1c5e53
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/AnnotationSpec.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.SimpleAnnotationValueVisitor8;
+
+import static com.squareup.javapoet.Util.characterLiteralWithoutSingleQuotes;
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+
+/** A generated annotation on a declaration. */
+public final class AnnotationSpec {
+  public final TypeName type;
+  public final Map<String, List<CodeBlock>> members;
+
+  private AnnotationSpec(Builder builder) {
+    this.type = builder.type;
+    this.members = Util.immutableMultimap(builder.members);
+  }
+
+  void emit(CodeWriter codeWriter, boolean inline) throws IOException {
+    String whitespace = inline ? "" : "\n";
+    String memberSeparator = inline ? ", " : ",\n";
+    if (members.isEmpty()) {
+      // @Singleton
+      codeWriter.emit("@$T", type);
+    } else if (members.size() == 1 && members.containsKey("value")) {
+      // @Named("foo")
+      codeWriter.emit("@$T(", type);
+      emitAnnotationValues(codeWriter, whitespace, memberSeparator, members.get("value"));
+      codeWriter.emit(")");
+    } else {
+      // Inline:
+      //   @Column(name = "updated_at", nullable = false)
+      //
+      // Not inline:
+      //   @Column(
+      //       name = "updated_at",
+      //       nullable = false
+      //   )
+      codeWriter.emit("@$T(" + whitespace, type);
+      codeWriter.indent(2);
+      for (Iterator<Map.Entry<String, List<CodeBlock>>> i
+          = members.entrySet().iterator(); i.hasNext(); ) {
+        Map.Entry<String, List<CodeBlock>> entry = i.next();
+        codeWriter.emit("$L = ", entry.getKey());
+        emitAnnotationValues(codeWriter, whitespace, memberSeparator, entry.getValue());
+        if (i.hasNext()) codeWriter.emit(memberSeparator);
+      }
+      codeWriter.unindent(2);
+      codeWriter.emit(whitespace + ")");
+    }
+  }
+
+  private void emitAnnotationValues(CodeWriter codeWriter, String whitespace,
+      String memberSeparator, List<CodeBlock> values) throws IOException {
+    if (values.size() == 1) {
+      codeWriter.indent(2);
+      codeWriter.emit(values.get(0));
+      codeWriter.unindent(2);
+      return;
+    }
+
+    codeWriter.emit("{" + whitespace);
+    codeWriter.indent(2);
+    boolean first = true;
+    for (CodeBlock codeBlock : values) {
+      if (!first) codeWriter.emit(memberSeparator);
+      codeWriter.emit(codeBlock);
+      first = false;
+    }
+    codeWriter.unindent(2);
+    codeWriter.emit(whitespace + "}");
+  }
+
+  public static AnnotationSpec get(Annotation annotation) {
+    return get(annotation, false);
+  }
+
+  public static AnnotationSpec get(Annotation annotation, boolean includeDefaultValues) {
+    Builder builder = builder(annotation.annotationType());
+    try {
+      Method[] methods = annotation.annotationType().getDeclaredMethods();
+      Arrays.sort(methods, Comparator.comparing(Method::getName));
+      for (Method method : methods) {
+        Object value = method.invoke(annotation);
+        if (!includeDefaultValues) {
+          if (Objects.deepEquals(value, method.getDefaultValue())) {
+            continue;
+          }
+        }
+        if (value.getClass().isArray()) {
+          for (int i = 0; i < Array.getLength(value); i++) {
+            builder.addMemberForValue(method.getName(), Array.get(value, i));
+          }
+          continue;
+        }
+        if (value instanceof Annotation) {
+          builder.addMember(method.getName(), "$L", get((Annotation) value));
+          continue;
+        }
+        builder.addMemberForValue(method.getName(), value);
+      }
+    } catch (Exception e) {
+      throw new RuntimeException("Reflecting " + annotation + " failed!", e);
+    }
+    return builder.build();
+  }
+
+  public static AnnotationSpec get(AnnotationMirror annotation) {
+    TypeElement element = (TypeElement) annotation.getAnnotationType().asElement();
+    AnnotationSpec.Builder builder = AnnotationSpec.builder(ClassName.get(element));
+    Visitor visitor = new Visitor(builder);
+    for (ExecutableElement executableElement : annotation.getElementValues().keySet()) {
+      String name = executableElement.getSimpleName().toString();
+      AnnotationValue value = annotation.getElementValues().get(executableElement);
+      value.accept(visitor, name);
+    }
+    return builder.build();
+  }
+
+  public static Builder builder(ClassName type) {
+    checkNotNull(type, "type == null");
+    return new Builder(type);
+  }
+
+  public static Builder builder(Class<?> type) {
+    return builder(ClassName.get(type));
+  }
+
+  public Builder toBuilder() {
+    Builder builder = new Builder(type);
+    for (Map.Entry<String, List<CodeBlock>> entry : members.entrySet()) {
+      builder.members.put(entry.getKey(), new ArrayList<>(entry.getValue()));
+    }
+    return builder;
+  }
+
+  @Override public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null) return false;
+    if (getClass() != o.getClass()) return false;
+    return toString().equals(o.toString());
+  }
+
+  @Override public int hashCode() {
+    return toString().hashCode();
+  }
+
+  @Override public String toString() {
+    StringBuilder out = new StringBuilder();
+    try {
+      CodeWriter codeWriter = new CodeWriter(out);
+      codeWriter.emit("$L", this);
+      return out.toString();
+    } catch (IOException e) {
+      throw new AssertionError();
+    }
+  }
+
+  public static final class Builder {
+    private final TypeName type;
+    private final Map<String, List<CodeBlock>> members = new LinkedHashMap<>();
+
+    private Builder(TypeName type) {
+      this.type = type;
+    }
+
+    public Builder addMember(String name, String format, Object... args) {
+      return addMember(name, CodeBlock.of(format, args));
+    }
+
+    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;
+    }
+
+    /**
+     * Delegates to {@link #addMember(String, String, Object...)}, with parameter {@code format}
+     * depending on the given {@code value} object. Falls back to {@code "$L"} literal format if
+     * the class of the given {@code value} object is not supported.
+     */
+    Builder addMemberForValue(String memberName, Object value) {
+      checkNotNull(memberName, "memberName == null");
+      checkNotNull(value, "value == null, constant non-null value expected for %s", memberName);
+      checkArgument(SourceVersion.isName(memberName), "not a valid name: %s", memberName);
+      if (value instanceof Class<?>) {
+        return addMember(memberName, "$T.class", value);
+      }
+      if (value instanceof Enum) {
+        return addMember(memberName, "$T.$L", value.getClass(), ((Enum<?>) value).name());
+      }
+      if (value instanceof String) {
+        return addMember(memberName, "$S", value);
+      }
+      if (value instanceof Float) {
+        return addMember(memberName, "$Lf", value);
+      }
+      if (value instanceof Character) {
+        return addMember(memberName, "'$L'", characterLiteralWithoutSingleQuotes((char) value));
+      }
+      return addMember(memberName, "$L", value);
+    }
+
+    public AnnotationSpec build() {
+      return new AnnotationSpec(this);
+    }
+  }
+
+  /**
+   * Annotation value visitor adding members to the given builder instance.
+   */
+  private static class Visitor extends SimpleAnnotationValueVisitor8<Builder, String> {
+    final Builder builder;
+
+    Visitor(Builder builder) {
+      super(builder);
+      this.builder = builder;
+    }
+
+    @Override protected Builder defaultAction(Object o, String name) {
+      return builder.addMemberForValue(name, o);
+    }
+
+    @Override public Builder visitAnnotation(AnnotationMirror a, String name) {
+      return builder.addMember(name, "$L", get(a));
+    }
+
+    @Override public Builder visitEnumConstant(VariableElement c, String name) {
+      return builder.addMember(name, "$T.$L", c.asType(), c.getSimpleName());
+    }
+
+    @Override public Builder visitType(TypeMirror t, String name) {
+      return builder.addMember(name, "$T.class", t);
+    }
+
+    @Override public Builder visitArray(List<? extends AnnotationValue> values, String name) {
+      for (AnnotationValue value : values) {
+        value.accept(this, name);
+      }
+      return builder;
+    }
+  }
+}
diff --git a/src/main/java/com/squareup/javapoet/ArrayTypeName.java b/src/main/java/com/squareup/javapoet/ArrayTypeName.java
new file mode 100644
index 0000000..219c3f3
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/ArrayTypeName.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.ArrayType;
+
+import static com.squareup.javapoet.Util.checkNotNull;
+
+public final class ArrayTypeName extends TypeName {
+  public final TypeName componentType;
+
+  private ArrayTypeName(TypeName componentType) {
+    this(componentType, new ArrayList<>());
+  }
+
+  private ArrayTypeName(TypeName componentType, List<AnnotationSpec> annotations) {
+    super(annotations);
+    this.componentType = checkNotNull(componentType, "rawType == null");
+  }
+
+  @Override public ArrayTypeName annotated(List<AnnotationSpec> annotations) {
+    return new ArrayTypeName(componentType, concatAnnotations(annotations));
+  }
+
+  @Override public TypeName withoutAnnotations() {
+    return new ArrayTypeName(componentType);
+  }
+
+  @Override CodeWriter emit(CodeWriter out) throws IOException {
+    return emit(out, false);
+  }
+
+  CodeWriter emit(CodeWriter out, boolean varargs) throws IOException {
+    emitLeafType(out);
+    return emitBrackets(out, varargs);
+  }
+
+  private CodeWriter emitLeafType(CodeWriter out) throws IOException {
+    if (TypeName.asArray(componentType) != null) {
+      return TypeName.asArray(componentType).emitLeafType(out);
+    }
+    return componentType.emit(out);
+  }
+
+  private CodeWriter emitBrackets(CodeWriter out, boolean varargs) throws IOException {
+    if (isAnnotated()) {
+      out.emit(" ");
+      emitAnnotations(out);
+    }
+
+    if (TypeName.asArray(componentType) == null) {
+      // Last bracket.
+      return out.emit(varargs ? "..." : "[]");
+    }
+    out.emit("[]");
+    return TypeName.asArray(componentType) .emitBrackets(out, varargs);
+  }
+
+
+  /** Returns an array type whose elements are all instances of {@code componentType}. */
+  public static ArrayTypeName of(TypeName componentType) {
+    return new ArrayTypeName(componentType);
+  }
+
+  /** Returns an array type whose elements are all instances of {@code componentType}. */
+  public static ArrayTypeName of(Type componentType) {
+    return of(TypeName.get(componentType));
+  }
+
+  /** Returns an array type equivalent to {@code mirror}. */
+  public static ArrayTypeName get(ArrayType mirror) {
+    return get(mirror, new LinkedHashMap<>());
+  }
+
+  static ArrayTypeName get(
+      ArrayType mirror, Map<TypeParameterElement, TypeVariableName> typeVariables) {
+    return new ArrayTypeName(get(mirror.getComponentType(), typeVariables));
+  }
+
+  /** Returns an array type equivalent to {@code type}. */
+  public static ArrayTypeName get(GenericArrayType type) {
+    return get(type, new LinkedHashMap<>());
+  }
+
+  static ArrayTypeName get(GenericArrayType type, Map<Type, TypeVariableName> map) {
+    return ArrayTypeName.of(get(type.getGenericComponentType(), map));
+  }
+}
diff --git a/src/main/java/com/squareup/javapoet/ClassName.java b/src/main/java/com/squareup/javapoet/ClassName.java
new file mode 100644
index 0000000..99c4ed2
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/ClassName.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.SimpleElementVisitor8;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+
+/** A fully-qualified class name for top-level and member classes. */
+public final class ClassName extends TypeName implements Comparable<ClassName> {
+  public static final ClassName OBJECT = ClassName.get(Object.class);
+
+  /** The package name of this class, or "" if this is in the default package. */
+  final String packageName;
+
+  /** The enclosing class, or null if this is not enclosed in another class. */
+  final ClassName enclosingClassName;
+
+  /** This class name, like "Entry" for java.util.Map.Entry. */
+  final String simpleName;
+
+  /** The full class name like "java.util.Map.Entry". */
+  final String canonicalName;
+
+  private ClassName(String packageName, ClassName enclosingClassName, String simpleName) {
+    this(packageName, enclosingClassName, simpleName, Collections.emptyList());
+  }
+
+  private ClassName(String packageName, ClassName enclosingClassName, String simpleName,
+      List<AnnotationSpec> annotations) {
+    super(annotations);
+    this.packageName = packageName;
+    this.enclosingClassName = enclosingClassName;
+    this.simpleName = simpleName;
+    this.canonicalName = enclosingClassName != null
+        ? (enclosingClassName.canonicalName + '.' + simpleName)
+        : (packageName.isEmpty() ? simpleName : packageName + '.' + simpleName);
+  }
+
+  @Override public ClassName annotated(List<AnnotationSpec> annotations) {
+    return new ClassName(packageName, enclosingClassName, simpleName,
+        concatAnnotations(annotations));
+  }
+
+  @Override public ClassName withoutAnnotations() {
+    if (!isAnnotated()) return this;
+    ClassName resultEnclosingClassName = enclosingClassName != null
+        ? enclosingClassName.withoutAnnotations()
+        : null;
+    return new ClassName(packageName, resultEnclosingClassName, simpleName);
+  }
+
+  @Override public boolean isAnnotated() {
+    return super.isAnnotated() || (enclosingClassName != null && enclosingClassName.isAnnotated());
+  }
+
+  /**
+   * Returns the package name, like {@code "java.util"} for {@code Map.Entry}. Returns the empty
+   * string for the default package.
+   */
+  public String packageName() {
+    return packageName;
+  }
+
+  /**
+   * Returns the enclosing class, like {@link Map} for {@code Map.Entry}. Returns null if this class
+   * is not nested in another class.
+   */
+  public ClassName enclosingClassName() {
+    return enclosingClassName;
+  }
+
+  /**
+   * Returns the top class in this nesting group. Equivalent to chained calls to {@link
+   * #enclosingClassName()} until the result's enclosing class is null.
+   */
+  public ClassName topLevelClassName() {
+    return enclosingClassName != null ? enclosingClassName.topLevelClassName() : this;
+  }
+
+  /** Return the binary name of a class. */
+  public String reflectionName() {
+    return enclosingClassName != null
+        ? (enclosingClassName.reflectionName() + '$' + simpleName)
+        : (packageName.isEmpty() ? simpleName : packageName + '.' + simpleName);
+  }
+
+  public List<String> simpleNames() {
+    List<String> simpleNames = new ArrayList<>();
+    if (enclosingClassName != null) {
+      simpleNames.addAll(enclosingClassName().simpleNames());
+    }
+    simpleNames.add(simpleName);
+    return simpleNames;
+  }
+
+  /**
+   * Returns a class that shares the same enclosing package or class. If this class is enclosed by
+   * another class, this is equivalent to {@code enclosingClassName().nestedClass(name)}. Otherwise
+   * it is equivalent to {@code get(packageName(), name)}.
+   */
+  public ClassName peerClass(String name) {
+    return new ClassName(packageName, enclosingClassName, name);
+  }
+
+  /**
+   * Returns a new {@link ClassName} instance for the specified {@code name} as nested inside this
+   * class.
+   */
+  public ClassName nestedClass(String name) {
+    return new ClassName(packageName, this, name);
+  }
+
+  /** Returns the simple name of this class, like {@code "Entry"} for {@link Map.Entry}. */
+  public String simpleName() {
+    return simpleName;
+  }
+
+  public static ClassName get(Class<?> clazz) {
+    checkNotNull(clazz, "clazz == null");
+    checkArgument(!clazz.isPrimitive(), "primitive types cannot be represented as a ClassName");
+    checkArgument(!void.class.equals(clazz), "'void' type cannot be represented as a ClassName");
+    checkArgument(!clazz.isArray(), "array types cannot be represented as a ClassName");
+
+    String anonymousSuffix = "";
+    while (clazz.isAnonymousClass()) {
+      int lastDollar = clazz.getName().lastIndexOf('$');
+      anonymousSuffix = clazz.getName().substring(lastDollar) + anonymousSuffix;
+      clazz = clazz.getEnclosingClass();
+    }
+    String name = clazz.getSimpleName() + anonymousSuffix;
+
+    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;
+      return new ClassName(packageName, null, name);
+    }
+
+    return ClassName.get(clazz.getEnclosingClass()).nestedClass(name);
+  }
+
+  /**
+   * Returns a new {@link ClassName} instance for the given fully-qualified class name string. This
+   * method assumes that the input is ASCII and follows typical Java style (lowercase package
+   * names, UpperCamelCase class names) and may produce incorrect results or throw
+   * {@link IllegalArgumentException} otherwise. For that reason, {@link #get(Class)} and
+   * {@link #get(Class)} should be preferred as they can correctly create {@link ClassName}
+   * instances without such restrictions.
+   */
+  public static ClassName bestGuess(String classNameString) {
+    // Add the package name, like "java.util.concurrent", or "" for no package.
+    int p = 0;
+    while (p < classNameString.length() && Character.isLowerCase(classNameString.codePointAt(p))) {
+      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);
+
+    // Add class names like "Map" and "Entry".
+    ClassName className = null;
+    for (String simpleName : classNameString.substring(p).split("\\.", -1)) {
+      checkArgument(!simpleName.isEmpty() && Character.isUpperCase(simpleName.codePointAt(0)),
+          "couldn't make a guess for %s", classNameString);
+      className = new ClassName(packageName, className, simpleName);
+    }
+
+    return className;
+  }
+
+  /**
+   * Returns a class name created from the given parts. For example, calling this with package name
+   * {@code "java.util"} and simple names {@code "Map"}, {@code "Entry"} yields {@link Map.Entry}.
+   */
+  public static ClassName get(String packageName, String simpleName, String... simpleNames) {
+    ClassName className = new ClassName(packageName, null, simpleName);
+    for (String name : simpleNames) {
+      className = className.nestedClass(name);
+    }
+    return className;
+  }
+
+  /** Returns the class name for {@code element}. */
+  public static ClassName get(TypeElement element) {
+    checkNotNull(element, "element == null");
+    String simpleName = element.getSimpleName().toString();
+
+    return element.getEnclosingElement().accept(new SimpleElementVisitor8<ClassName, Void>() {
+      @Override public ClassName visitPackage(PackageElement packageElement, Void p) {
+        return new ClassName(packageElement.getQualifiedName().toString(), null, simpleName);
+      }
+
+      @Override public ClassName visitType(TypeElement enclosingClass, Void p) {
+        return ClassName.get(enclosingClass).nestedClass(simpleName);
+      }
+
+      @Override public ClassName visitUnknown(Element unknown, Void p) {
+        return get("", simpleName);
+      }
+
+      @Override public ClassName defaultAction(Element enclosingElement, Void p) {
+        throw new IllegalArgumentException("Unexpected type nesting: " + element);
+      }
+    }, null);
+  }
+
+  @Override public int compareTo(ClassName o) {
+    return canonicalName.compareTo(o.canonicalName);
+  }
+
+  @Override CodeWriter emit(CodeWriter out) throws IOException {
+    boolean charsEmitted = false;
+    for (ClassName className : enclosingClasses()) {
+      String simpleName;
+      if (charsEmitted) {
+        // We've already emitted an enclosing class. Emit as we go.
+        out.emit(".");
+        simpleName = className.simpleName;
+
+      } else if (className.isAnnotated() || className == this) {
+        // We encountered the first enclosing class that must be emitted.
+        String qualifiedName = out.lookupName(className);
+        int dot = qualifiedName.lastIndexOf('.');
+        if (dot != -1) {
+          out.emitAndIndent(qualifiedName.substring(0, dot + 1));
+          simpleName = qualifiedName.substring(dot + 1);
+          charsEmitted = true;
+        } else {
+          simpleName = qualifiedName;
+        }
+
+      } else {
+        // Don't emit this enclosing type. Keep going so we can be more precise.
+        continue;
+      }
+
+      if (className.isAnnotated()) {
+        if (charsEmitted) out.emit(" ");
+        className.emitAnnotations(out);
+      }
+
+      out.emit(simpleName);
+      charsEmitted = true;
+    }
+
+    return out;
+  }
+
+  /** Returns all enclosing classes in this, outermost first. */
+  private List<ClassName> enclosingClasses() {
+    List<ClassName> result = new ArrayList<>();
+    for (ClassName c = this; c != null; c = c.enclosingClassName) {
+      result.add(c);
+    }
+    Collections.reverse(result);
+    return result;
+  }
+}
diff --git a/src/main/java/com/squareup/javapoet/CodeBlock.java b/src/main/java/com/squareup/javapoet/CodeBlock.java
new file mode 100644
index 0000000..33e3846
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/CodeBlock.java
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collector;
+import java.util.stream.StreamSupport;
+import javax.lang.model.element.Element;
+import javax.lang.model.type.TypeMirror;
+
+import static com.squareup.javapoet.Util.checkArgument;
+
+/**
+ * A fragment of a .java file, potentially containing declarations, statements, and documentation.
+ * Code blocks are not necessarily well-formed Java code, and are not validated. This class assumes
+ * javac will check correctness later!
+ *
+ * <p>Code blocks support placeholders like {@link java.text.Format}. Where {@link String#format}
+ * uses percent {@code %} to reference target values, this class uses dollar sign {@code $} and has
+ * its own set of permitted placeholders:
+ *
+ * <ul>
+ *   <li>{@code $L} emits a <em>literal</em> value with no escaping. Arguments for literals may be
+ *       strings, primitives, {@linkplain TypeSpec type declarations}, {@linkplain AnnotationSpec
+ *       annotations} and even other code blocks.
+ *   <li>{@code $N} emits a <em>name</em>, using name collision avoidance where necessary. Arguments
+ *       for names may be strings (actually any {@linkplain CharSequence character sequence}),
+ *       {@linkplain ParameterSpec parameters}, {@linkplain FieldSpec fields}, {@linkplain
+ *       MethodSpec methods}, and {@linkplain TypeSpec types}.
+ *   <li>{@code $S} escapes the value as a <em>string</em>, wraps it with double quotes, and emits
+ *       that. For example, {@code 6" sandwich} is emitted {@code "6\" sandwich"}.
+ *   <li>{@code $T} emits a <em>type</em> reference. Types will be imported if possible. Arguments
+ *       for types may be {@linkplain Class classes}, {@linkplain javax.lang.model.type.TypeMirror
+,*       type mirrors}, and {@linkplain javax.lang.model.element.Element elements}.
+ *   <li>{@code $$} emits a dollar sign.
+ *   <li>{@code $W} emits a space or a newline, depending on its position on the line. This prefers
+ *       to wrap lines before 100 columns.
+ *   <li>{@code $Z} acts as a zero-width space. This prefers to wrap lines before 100 columns.
+ *   <li>{@code $>} increases the indentation level.
+ *   <li>{@code $<} decreases the indentation level.
+ *   <li>{@code $[} begins a statement. For multiline statements, every line after the first line
+ *       is double-indented.
+ *   <li>{@code $]} ends a statement.
+ * </ul>
+ */
+public final class CodeBlock {
+  private static final Pattern NAMED_ARGUMENT =
+      Pattern.compile("\\$(?<argumentName>[\\w_]+):(?<typeChar>[\\w]).*");
+  private static final Pattern LOWERCASE = Pattern.compile("[a-z]+[\\w_]*");
+
+  /** A heterogeneous list containing string literals and value placeholders. */
+  final List<String> formatParts;
+  final List<Object> args;
+
+  private CodeBlock(Builder builder) {
+    this.formatParts = Util.immutableList(builder.formatParts);
+    this.args = Util.immutableList(builder.args);
+  }
+
+  public boolean isEmpty() {
+    return formatParts.isEmpty();
+  }
+
+  @Override public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null) return false;
+    if (getClass() != o.getClass()) return false;
+    return toString().equals(o.toString());
+  }
+
+  @Override public int hashCode() {
+    return toString().hashCode();
+  }
+
+  @Override public String toString() {
+    StringBuilder out = new StringBuilder();
+    try {
+      new CodeWriter(out).emit(this);
+      return out.toString();
+    } catch (IOException e) {
+      throw new AssertionError();
+    }
+  }
+
+  public static CodeBlock of(String format, Object... args) {
+    return new Builder().add(format, args).build();
+  }
+
+  /**
+   * Joins {@code codeBlocks} into a single {@link CodeBlock}, each separated by {@code separator}.
+   * For example, joining {@code String s}, {@code Object o} and {@code int i} using {@code ", "}
+   * would produce {@code String s, Object o, int i}.
+   */
+  public static CodeBlock join(Iterable<CodeBlock> codeBlocks, String separator) {
+    return StreamSupport.stream(codeBlocks.spliterator(), false).collect(joining(separator));
+  }
+
+  /**
+   * A {@link Collector} implementation that joins {@link CodeBlock} instances together into one
+   * separated by {@code separator}. For example, joining {@code String s}, {@code Object o} and
+   * {@code int i} using {@code ", "} would produce {@code String s, Object o, int i}.
+   */
+  public static Collector<CodeBlock, ?, CodeBlock> joining(String separator) {
+    return Collector.of(
+        () -> new CodeBlockJoiner(separator, builder()),
+        CodeBlockJoiner::add,
+        CodeBlockJoiner::merge,
+        CodeBlockJoiner::join);
+  }
+
+  /**
+   * A {@link Collector} implementation that joins {@link CodeBlock} instances together into one
+   * separated by {@code separator}. For example, joining {@code String s}, {@code Object o} and
+   * {@code int i} using {@code ", "} would produce {@code String s, Object o, int i}.
+   */
+  public static Collector<CodeBlock, ?, CodeBlock> joining(
+      String separator, String prefix, String suffix) {
+    Builder builder = builder().add("$N", prefix);
+    return Collector.of(
+        () -> new CodeBlockJoiner(separator, builder),
+        CodeBlockJoiner::add,
+        CodeBlockJoiner::merge,
+        joiner -> {
+            builder.add(CodeBlock.of("$N", suffix));
+            return joiner.join();
+        });
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public Builder toBuilder() {
+    Builder builder = new Builder();
+    builder.formatParts.addAll(formatParts);
+    builder.args.addAll(args);
+    return builder;
+  }
+
+  public static final class Builder {
+    final List<String> formatParts = new ArrayList<>();
+    final List<Object> args = new ArrayList<>();
+
+    private Builder() {
+    }
+
+    public boolean isEmpty() {
+      return formatParts.isEmpty();
+    }
+
+    /**
+     * Adds code using named arguments.
+     *
+     * <p>Named arguments specify their name after the '$' followed by : and the corresponding type
+     * character. Argument names consist of characters in {@code a-z, A-Z, 0-9, and _} and must
+     * start with a lowercase character.
+     *
+     * <p>For example, to refer to the type {@link java.lang.Integer} with the argument name {@code
+     * clazz} use a format string containing {@code $clazz:T} and include the key {@code clazz} with
+     * value {@code java.lang.Integer.class} in the argument map.
+     */
+    public Builder addNamed(String format, Map<String, ?> arguments) {
+      int p = 0;
+
+      for (String argument : arguments.keySet()) {
+        checkArgument(LOWERCASE.matcher(argument).matches(),
+            "argument '%s' must start with a lowercase character", argument);
+      }
+
+      while (p < format.length()) {
+        int nextP = format.indexOf("$", p);
+        if (nextP == -1) {
+          formatParts.add(format.substring(p, format.length()));
+          break;
+        }
+
+        if (p != nextP) {
+          formatParts.add(format.substring(p, nextP));
+          p = nextP;
+        }
+
+        Matcher matcher = null;
+        int colon = format.indexOf(':', p);
+        if (colon != -1) {
+          int endIndex = Math.min(colon + 2, format.length());
+          matcher = NAMED_ARGUMENT.matcher(format.substring(p, endIndex));
+        }
+        if (matcher != null && matcher.lookingAt()) {
+          String argumentName = matcher.group("argumentName");
+          checkArgument(arguments.containsKey(argumentName), "Missing named argument for $%s",
+              argumentName);
+          char formatChar = matcher.group("typeChar").charAt(0);
+          addArgument(format, formatChar, arguments.get(argumentName));
+          formatParts.add("$" + formatChar);
+          p += matcher.regionEnd();
+        } else {
+          checkArgument(p < format.length() - 1, "dangling $ at end");
+          checkArgument(isNoArgPlaceholder(format.charAt(p + 1)),
+              "unknown format $%s at %s in '%s'", format.charAt(p + 1), p + 1, format);
+          formatParts.add(format.substring(p, p + 2));
+          p += 2;
+        }
+      }
+
+      return this;
+    }
+
+    /**
+     * Add code with positional or relative arguments.
+     *
+     * <p>Relative arguments map 1:1 with the placeholders in the format string.
+     *
+     * <p>Positional arguments use an index after the placeholder to identify which argument index
+     * to use. For example, for a literal to reference the 3rd argument: "$3L" (1 based index)
+     *
+     * <p>Mixing relative and positional arguments in a call to add is invalid and will result in an
+     * error.
+     */
+    public Builder add(String format, Object... args) {
+      boolean hasRelative = false;
+      boolean hasIndexed = false;
+
+      int relativeParameterCount = 0;
+      int[] indexedParameterCount = new int[args.length];
+
+      for (int p = 0; p < format.length(); ) {
+        if (format.charAt(p) != '$') {
+          int nextP = format.indexOf('$', p + 1);
+          if (nextP == -1) nextP = format.length();
+          formatParts.add(format.substring(p, nextP));
+          p = nextP;
+          continue;
+        }
+
+        p++; // '$'.
+
+        // Consume zero or more digits, leaving 'c' as the first non-digit char after the '$'.
+        int indexStart = p;
+        char c;
+        do {
+          checkArgument(p < format.length(), "dangling format characters in '%s'", format);
+          c = format.charAt(p++);
+        } while (c >= '0' && c <= '9');
+        int indexEnd = p - 1;
+
+        // If 'c' doesn't take an argument, we're done.
+        if (isNoArgPlaceholder(c)) {
+          checkArgument(
+              indexStart == indexEnd, "$$, $>, $<, $[, $], $W, and $Z may not have an index");
+          formatParts.add("$" + c);
+          continue;
+        }
+
+        // Find either the indexed argument, or the relative argument. (0-based).
+        int index;
+        if (indexStart < indexEnd) {
+          index = Integer.parseInt(format.substring(indexStart, indexEnd)) - 1;
+          hasIndexed = true;
+          if (args.length > 0) {
+            indexedParameterCount[index % args.length]++; // modulo is needed, checked below anyway
+          }
+        } else {
+          index = relativeParameterCount;
+          hasRelative = true;
+          relativeParameterCount++;
+        }
+
+        checkArgument(index >= 0 && index < args.length,
+            "index %d for '%s' not in range (received %s arguments)",
+            index + 1, format.substring(indexStart - 1, indexEnd + 1), args.length);
+        checkArgument(!hasIndexed || !hasRelative, "cannot mix indexed and positional parameters");
+
+        addArgument(format, c, args[index]);
+
+        formatParts.add("$" + c);
+      }
+
+      if (hasRelative) {
+        checkArgument(relativeParameterCount >= args.length,
+            "unused arguments: expected %s, received %s", relativeParameterCount, args.length);
+      }
+      if (hasIndexed) {
+        List<String> unused = new ArrayList<>();
+        for (int i = 0; i < args.length; i++) {
+          if (indexedParameterCount[i] == 0) {
+            unused.add("$" + (i + 1));
+          }
+        }
+        String s = unused.size() == 1 ? "" : "s";
+        checkArgument(unused.isEmpty(), "unused argument%s: %s", s, String.join(", ", unused));
+      }
+      return this;
+    }
+
+    private boolean isNoArgPlaceholder(char c) {
+      return c == '$' || c == '>' || c == '<' || c == '[' || c == ']' || c == 'W' || c == 'Z';
+    }
+
+    private void addArgument(String format, char c, Object arg) {
+      switch (c) {
+        case 'N':
+          this.args.add(argToName(arg));
+          break;
+        case 'L':
+          this.args.add(argToLiteral(arg));
+          break;
+        case 'S':
+          this.args.add(argToString(arg));
+          break;
+        case 'T':
+          this.args.add(argToType(arg));
+          break;
+        default:
+          throw new IllegalArgumentException(
+              String.format("invalid format string: '%s'", format));
+      }
+    }
+
+    private String argToName(Object o) {
+      if (o instanceof CharSequence) return o.toString();
+      if (o instanceof ParameterSpec) return ((ParameterSpec) o).name;
+      if (o instanceof FieldSpec) return ((FieldSpec) o).name;
+      if (o instanceof MethodSpec) return ((MethodSpec) o).name;
+      if (o instanceof TypeSpec) return ((TypeSpec) o).name;
+      throw new IllegalArgumentException("expected name but was " + o);
+    }
+
+    private Object argToLiteral(Object o) {
+      return o;
+    }
+
+    private String argToString(Object o) {
+      return o != null ? String.valueOf(o) : null;
+    }
+
+    private TypeName argToType(Object o) {
+      if (o instanceof TypeName) return (TypeName) o;
+      if (o instanceof TypeMirror) return TypeName.get((TypeMirror) o);
+      if (o instanceof Element) return TypeName.get(((Element) o).asType());
+      if (o instanceof Type) return TypeName.get((Type) o);
+      throw new IllegalArgumentException("expected type but was " + o);
+    }
+
+    /**
+     * @param controlFlow the control flow construct and its code, such as "if (foo == 5)".
+     * Shouldn't contain braces or newline characters.
+     */
+    public Builder beginControlFlow(String controlFlow, Object... args) {
+      add(controlFlow + " {\n", args);
+      indent();
+      return this;
+    }
+
+    /**
+     * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
+     *     Shouldn't contain braces or newline characters.
+     */
+    public Builder nextControlFlow(String controlFlow, Object... args) {
+      unindent();
+      add("} " + controlFlow + " {\n", args);
+      indent();
+      return this;
+    }
+
+    public Builder endControlFlow() {
+      unindent();
+      add("}\n");
+      return this;
+    }
+
+    /**
+     * @param controlFlow the optional control flow construct and its code, such as
+     *     "while(foo == 20)". Only used for "do/while" control flows.
+     */
+    public Builder endControlFlow(String controlFlow, Object... args) {
+      unindent();
+      add("} " + controlFlow + ";\n", args);
+      return this;
+    }
+
+    public Builder addStatement(String format, Object... args) {
+      add("$[");
+      add(format, args);
+      add(";\n$]");
+      return this;
+    }
+
+    public Builder addStatement(CodeBlock codeBlock) {
+      return addStatement("$L", codeBlock);
+    }
+
+    public Builder add(CodeBlock codeBlock) {
+      formatParts.addAll(codeBlock.formatParts);
+      args.addAll(codeBlock.args);
+      return this;
+    }
+
+    public Builder indent() {
+      this.formatParts.add("$>");
+      return this;
+    }
+
+    public Builder unindent() {
+      this.formatParts.add("$<");
+      return this;
+    }
+
+    public CodeBlock build() {
+      return new CodeBlock(this);
+    }
+  }
+
+  private static final class CodeBlockJoiner {
+    private final String delimiter;
+    private final Builder builder;
+    private boolean first = true;
+
+    CodeBlockJoiner(String delimiter, Builder builder) {
+      this.delimiter = delimiter;
+      this.builder = builder;
+    }
+
+    CodeBlockJoiner add(CodeBlock codeBlock) {
+      if (!first) {
+        builder.add(delimiter);
+      }
+      first = false;
+
+      builder.add(codeBlock);
+      return this;
+    }
+
+    CodeBlockJoiner merge(CodeBlockJoiner other) {
+      CodeBlock otherBlock = other.builder.build();
+      if (!otherBlock.isEmpty()) {
+        add(otherBlock);
+      }
+      return this;
+    }
+
+    CodeBlock join() {
+      return builder.build();
+    }
+  }
+}
diff --git a/src/main/java/com/squareup/javapoet/CodeWriter.java b/src/main/java/com/squareup/javapoet/CodeWriter.java
new file mode 100644
index 0000000..542f434
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/CodeWriter.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Modifier;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+import static com.squareup.javapoet.Util.checkState;
+import static com.squareup.javapoet.Util.stringLiteralWithDoubleQuotes;
+import static java.lang.String.join;
+
+/**
+ * Converts a {@link JavaFile} to a string suitable to both human- and javac-consumption. This
+ * honors imports, indentation, and deferred variable names.
+ */
+final class CodeWriter {
+  /** Sentinel value that indicates that no user-provided package has been set. */
+  private static final String NO_PACKAGE = new String();
+
+  private final String indent;
+  private final LineWrapper out;
+  private int indentLevel;
+
+  private boolean javadoc = false;
+  private boolean comment = false;
+  private String packageName = NO_PACKAGE;
+  private final List<TypeSpec> typeSpecStack = new ArrayList<>();
+  private final Set<String> staticImportClassNames;
+  private final Set<String> staticImports;
+  private final Map<String, ClassName> importedTypes;
+  private final Map<String, ClassName> importableTypes = new LinkedHashMap<>();
+  private final Set<String> referencedNames = new LinkedHashSet<>();
+  private boolean trailingNewline;
+
+  /**
+   * When emitting a statement, this is the line of the statement currently being written. The first
+   * line of a statement is indented normally and subsequent wrapped lines are double-indented. This
+   * is -1 when the currently-written line isn't part of a statement.
+   */
+  int statementLine = -1;
+
+  CodeWriter(Appendable out) {
+    this(out, "  ", Collections.emptySet());
+  }
+
+  CodeWriter(Appendable out, String indent, Set<String> staticImports) {
+    this(out, indent, Collections.emptyMap(), staticImports);
+  }
+
+  CodeWriter(Appendable out, String indent, Map<String, ClassName> importedTypes,
+      Set<String> staticImports) {
+    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.staticImportClassNames = new LinkedHashSet<>();
+    for (String signature : staticImports) {
+      staticImportClassNames.add(signature.substring(0, signature.lastIndexOf('.')));
+    }
+  }
+
+  public Map<String, ClassName> importedTypes() {
+    return importedTypes;
+  }
+
+  public CodeWriter indent() {
+    return indent(1);
+  }
+
+  public CodeWriter indent(int levels) {
+    indentLevel += levels;
+    return this;
+  }
+
+  public CodeWriter unindent() {
+    return unindent(1);
+  }
+
+  public CodeWriter unindent(int levels) {
+    checkArgument(indentLevel - levels >= 0, "cannot unindent %s from %s", levels, indentLevel);
+    indentLevel -= levels;
+    return this;
+  }
+
+  public CodeWriter pushPackage(String packageName) {
+    checkState(this.packageName == NO_PACKAGE, "package already set: %s", this.packageName);
+    this.packageName = checkNotNull(packageName, "packageName == null");
+    return this;
+  }
+
+  public CodeWriter popPackage() {
+    checkState(this.packageName != NO_PACKAGE, "package not set");
+    this.packageName = NO_PACKAGE;
+    return this;
+  }
+
+  public CodeWriter pushType(TypeSpec type) {
+    this.typeSpecStack.add(type);
+    return this;
+  }
+
+  public CodeWriter popType() {
+    this.typeSpecStack.remove(typeSpecStack.size() - 1);
+    return this;
+  }
+
+  public void emitComment(CodeBlock codeBlock) throws IOException {
+    trailingNewline = true; // Force the '//' prefix for the comment.
+    comment = true;
+    try {
+      emit(codeBlock);
+      emit("\n");
+    } finally {
+      comment = false;
+    }
+  }
+
+  public void emitJavadoc(CodeBlock javadocCodeBlock) throws IOException {
+    if (javadocCodeBlock.isEmpty()) return;
+
+    emit("/**\n");
+    javadoc = true;
+    try {
+      emit(javadocCodeBlock);
+    } finally {
+      javadoc = false;
+    }
+    emit(" */\n");
+  }
+
+  public void emitAnnotations(List<AnnotationSpec> annotations, boolean inline) throws IOException {
+    for (AnnotationSpec annotationSpec : annotations) {
+      annotationSpec.emit(this, inline);
+      emit(inline ? " " : "\n");
+    }
+  }
+
+  /**
+   * Emits {@code modifiers} in the standard order. Modifiers in {@code implicitModifiers} will not
+   * be emitted.
+   */
+  public void emitModifiers(Set<Modifier> modifiers, Set<Modifier> implicitModifiers)
+      throws IOException {
+    if (modifiers.isEmpty()) return;
+    for (Modifier modifier : EnumSet.copyOf(modifiers)) {
+      if (implicitModifiers.contains(modifier)) continue;
+      emitAndIndent(modifier.name().toLowerCase(Locale.US));
+      emitAndIndent(" ");
+    }
+  }
+
+  public void emitModifiers(Set<Modifier> modifiers) throws IOException {
+    emitModifiers(modifiers, Collections.emptySet());
+  }
+
+  /**
+   * Emit type variables with their bounds. This should only be used when declaring type variables;
+   * everywhere else bounds are omitted.
+   */
+  public void emitTypeVariables(List<TypeVariableName> typeVariables) throws IOException {
+    if (typeVariables.isEmpty()) return;
+
+    emit("<");
+    boolean firstTypeVariable = true;
+    for (TypeVariableName typeVariable : typeVariables) {
+      if (!firstTypeVariable) emit(", ");
+      emitAnnotations(typeVariable.annotations, true);
+      emit("$L", typeVariable.name);
+      boolean firstBound = true;
+      for (TypeName bound : typeVariable.bounds) {
+        emit(firstBound ? " extends $T" : " & $T", bound);
+        firstBound = false;
+      }
+      firstTypeVariable = false;
+    }
+    emit(">");
+  }
+
+  public CodeWriter emit(String s) throws IOException {
+    return emitAndIndent(s);
+  }
+
+  public CodeWriter emit(String format, Object... args) throws IOException {
+    return emit(CodeBlock.of(format, args));
+  }
+
+  public CodeWriter emit(CodeBlock codeBlock) throws IOException {
+    int a = 0;
+    ClassName deferredTypeName = null; // used by "import static" logic
+    ListIterator<String> partIterator = codeBlock.formatParts.listIterator();
+    while (partIterator.hasNext()) {
+      String part = partIterator.next();
+      switch (part) {
+        case "$L":
+          emitLiteral(codeBlock.args.get(a++));
+          break;
+
+        case "$N":
+          emitAndIndent((String) codeBlock.args.get(a++));
+          break;
+
+        case "$S":
+          String string = (String) codeBlock.args.get(a++);
+          // Emit null as a literal null: no quotes.
+          emitAndIndent(string != null
+              ? stringLiteralWithDoubleQuotes(string, indent)
+              : "null");
+          break;
+
+        case "$T":
+          TypeName typeName = (TypeName) codeBlock.args.get(a++);
+          // defer "typeName.emit(this)" if next format part will be handled by the default case
+          if (typeName instanceof ClassName && partIterator.hasNext()) {
+            if (!codeBlock.formatParts.get(partIterator.nextIndex()).startsWith("$")) {
+              ClassName candidate = (ClassName) typeName;
+              if (staticImportClassNames.contains(candidate.canonicalName)) {
+                checkState(deferredTypeName == null, "pending type for static import?!");
+                deferredTypeName = candidate;
+                break;
+              }
+            }
+          }
+          typeName.emit(this);
+          break;
+
+        case "$$":
+          emitAndIndent("$");
+          break;
+
+        case "$>":
+          indent();
+          break;
+
+        case "$<":
+          unindent();
+          break;
+
+        case "$[":
+          checkState(statementLine == -1, "statement enter $[ followed by statement enter $[");
+          statementLine = 0;
+          break;
+
+        case "$]":
+          checkState(statementLine != -1, "statement exit $] has no matching statement enter $[");
+          if (statementLine > 0) {
+            unindent(2); // End a multi-line statement. Decrease the indentation level.
+          }
+          statementLine = -1;
+          break;
+
+        case "$W":
+          out.wrappingSpace(indentLevel + 2);
+          break;
+
+        case "$Z":
+          out.zeroWidthSpace(indentLevel + 2);
+          break;
+
+        default:
+          // handle deferred type
+          if (deferredTypeName != null) {
+            if (part.startsWith(".")) {
+              if (emitStaticImportMember(deferredTypeName.canonicalName, part)) {
+                // okay, static import hit and all was emitted, so clean-up and jump to next part
+                deferredTypeName = null;
+                break;
+              }
+            }
+            deferredTypeName.emit(this);
+            deferredTypeName = null;
+          }
+          emitAndIndent(part);
+          break;
+      }
+    }
+    return this;
+  }
+
+  public CodeWriter emitWrappingSpace() throws IOException {
+    out.wrappingSpace(indentLevel + 2);
+    return this;
+  }
+
+  private static String extractMemberName(String part) {
+    checkArgument(Character.isJavaIdentifierStart(part.charAt(0)), "not an identifier: %s", part);
+    for (int i = 1; i <= part.length(); i++) {
+      if (!SourceVersion.isIdentifier(part.substring(0, i))) {
+        return part.substring(0, i - 1);
+      }
+    }
+    return part;
+  }
+
+  private boolean emitStaticImportMember(String canonical, String part) throws IOException {
+    String partWithoutLeadingDot = part.substring(1);
+    if (partWithoutLeadingDot.isEmpty()) return false;
+    char first = partWithoutLeadingDot.charAt(0);
+    if (!Character.isJavaIdentifierStart(first)) return false;
+    String explicit = canonical + "." + extractMemberName(partWithoutLeadingDot);
+    String wildcard = canonical + ".*";
+    if (staticImports.contains(explicit) || staticImports.contains(wildcard)) {
+      emitAndIndent(partWithoutLeadingDot);
+      return true;
+    }
+    return false;
+  }
+
+  private void emitLiteral(Object o) throws IOException {
+    if (o instanceof TypeSpec) {
+      TypeSpec typeSpec = (TypeSpec) o;
+      typeSpec.emit(this, null, Collections.emptySet());
+    } else if (o instanceof AnnotationSpec) {
+      AnnotationSpec annotationSpec = (AnnotationSpec) o;
+      annotationSpec.emit(this, true);
+    } else if (o instanceof CodeBlock) {
+      CodeBlock codeBlock = (CodeBlock) o;
+      emit(codeBlock);
+    } else {
+      emitAndIndent(String.valueOf(o));
+    }
+  }
+
+  /**
+   * Returns the best name to identify {@code className} with in the current context. This uses the
+   * available imports and the current scope to find the shortest name available. It does not honor
+   * names visible due to inheritance.
+   */
+  String lookupName(ClassName className) {
+    // 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;
+    for (ClassName c = className; c != null; c = c.enclosingClassName()) {
+      ClassName resolved = resolve(c.simpleName());
+      nameResolved = resolved != null;
+
+      if (resolved != null && Objects.equals(resolved.canonicalName, c.canonicalName)) {
+        int suffixOffset = c.simpleNames().size() - 1;
+        return join(".", className.simpleNames().subList(
+            suffixOffset, className.simpleNames().size()));
+      }
+    }
+
+    // If the name resolved but wasn't a match, we're stuck with the fully qualified name.
+    if (nameResolved) {
+      return className.canonicalName;
+    }
+
+    // If the class is in the same package, we're done.
+    if (Objects.equals(packageName, className.packageName())) {
+      referencedNames.add(className.topLevelClassName().simpleName());
+      return join(".", className.simpleNames());
+    }
+
+    // We'll have to use the fully-qualified name. Mark the type as importable for a future pass.
+    if (!javadoc) {
+      importableType(className);
+    }
+
+    return className.canonicalName;
+  }
+
+  private void importableType(ClassName className) {
+    if (className.packageName().isEmpty()) {
+      return;
+    }
+    ClassName topLevelClassName = className.topLevelClassName();
+    String simpleName = topLevelClassName.simpleName();
+    ClassName replaced = importableTypes.put(simpleName, topLevelClassName);
+    if (replaced != null) {
+      importableTypes.put(simpleName, replaced); // On collision, prefer the first inserted.
+    }
+  }
+
+  /**
+   * Returns the class referenced by {@code simpleName}, using the current nesting context and
+   * imports.
+   */
+  // TODO(jwilson): also honor superclass members when resolving names.
+  private ClassName resolve(String simpleName) {
+    // 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);
+        }
+      }
+    }
+
+    // Match the top-level class.
+    if (typeSpecStack.size() > 0 && Objects.equals(typeSpecStack.get(0).name, simpleName)) {
+      return ClassName.get(packageName, simpleName);
+    }
+
+    // Match an imported type.
+    ClassName importedType = importedTypes.get(simpleName);
+    if (importedType != null) return importedType;
+
+    // No match.
+    return null;
+  }
+
+  /** Returns the class named {@code simpleName} when nested in the class at {@code stackDepth}. */
+  private ClassName stackClassName(int stackDepth, String simpleName) {
+    ClassName className = ClassName.get(packageName, typeSpecStack.get(0).name);
+    for (int i = 1; i <= stackDepth; i++) {
+      className = className.nestedClass(typeSpecStack.get(i).name);
+    }
+    return className.nestedClass(simpleName);
+  }
+
+  /**
+   * Emits {@code s} with indentation as required. It's important that all code that writes to
+   * {@link #out} does it through here, since we emit indentation lazily in order to avoid
+   * unnecessary trailing whitespace.
+   */
+  CodeWriter emitAndIndent(String s) throws IOException {
+    boolean first = true;
+    for (String line : s.split("\n", -1)) {
+      // Emit a newline character. Make sure blank lines in Javadoc & comments look good.
+      if (!first) {
+        if ((javadoc || comment) && trailingNewline) {
+          emitIndentation();
+          out.append(javadoc ? " *" : "//");
+        }
+        out.append("\n");
+        trailingNewline = true;
+        if (statementLine != -1) {
+          if (statementLine == 0) {
+            indent(2); // Begin multiple-line statement. Increase the indentation level.
+          }
+          statementLine++;
+        }
+      }
+
+      first = false;
+      if (line.isEmpty()) continue; // Don't indent empty lines.
+
+      // Emit indentation and comment prefix if necessary.
+      if (trailingNewline) {
+        emitIndentation();
+        if (javadoc) {
+          out.append(" * ");
+        } else if (comment) {
+          out.append("// ");
+        }
+      }
+
+      out.append(line);
+      trailingNewline = false;
+    }
+    return this;
+  }
+
+  private void emitIndentation() throws IOException {
+    for (int j = 0; j < indentLevel; j++) {
+      out.append(indent);
+    }
+  }
+
+  /**
+   * Returns the types that should have been imported for this code. If there were any simple name
+   * collisions, that type's first use is imported.
+   */
+  Map<String, ClassName> suggestedImports() {
+    Map<String, ClassName> result = new LinkedHashMap<>(importableTypes);
+    result.keySet().removeAll(referencedNames);
+    return result;
+  }
+}
diff --git a/src/main/java/com/squareup/javapoet/FieldSpec.java b/src/main/java/com/squareup/javapoet/FieldSpec.java
new file mode 100644
index 0000000..851b36d
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/FieldSpec.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Modifier;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+import static com.squareup.javapoet.Util.checkState;
+
+/** A generated field declaration. */
+public final class FieldSpec {
+  public final TypeName type;
+  public final String name;
+  public final CodeBlock javadoc;
+  public final List<AnnotationSpec> annotations;
+  public final Set<Modifier> modifiers;
+  public final CodeBlock initializer;
+
+  private FieldSpec(Builder builder) {
+    this.type = checkNotNull(builder.type, "type == null");
+    this.name = checkNotNull(builder.name, "name == null");
+    this.javadoc = builder.javadoc.build();
+    this.annotations = Util.immutableList(builder.annotations);
+    this.modifiers = Util.immutableSet(builder.modifiers);
+    this.initializer = (builder.initializer == null)
+        ? CodeBlock.builder().build()
+        : builder.initializer;
+  }
+
+  public boolean hasModifier(Modifier modifier) {
+    return modifiers.contains(modifier);
+  }
+
+  void emit(CodeWriter codeWriter, Set<Modifier> implicitModifiers) throws IOException {
+    codeWriter.emitJavadoc(javadoc);
+    codeWriter.emitAnnotations(annotations, false);
+    codeWriter.emitModifiers(modifiers, implicitModifiers);
+    codeWriter.emit("$T $L", type, name);
+    if (!initializer.isEmpty()) {
+      codeWriter.emit(" = ");
+      codeWriter.emit(initializer);
+    }
+    codeWriter.emit(";\n");
+  }
+
+  @Override public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null) return false;
+    if (getClass() != o.getClass()) return false;
+    return toString().equals(o.toString());
+  }
+
+  @Override public int hashCode() {
+    return toString().hashCode();
+  }
+
+  @Override public String toString() {
+    StringBuilder out = new StringBuilder();
+    try {
+      CodeWriter codeWriter = new CodeWriter(out);
+      emit(codeWriter, Collections.emptySet());
+      return out.toString();
+    } catch (IOException e) {
+      throw new AssertionError();
+    }
+  }
+
+  public static Builder builder(TypeName type, String name, Modifier... modifiers) {
+    checkNotNull(type, "type == null");
+    checkArgument(SourceVersion.isName(name), "not a valid name: %s", name);
+    return new Builder(type, name)
+        .addModifiers(modifiers);
+  }
+
+  public static Builder builder(Type type, String name, Modifier... modifiers) {
+    return builder(TypeName.get(type), name, modifiers);
+  }
+
+  public Builder toBuilder() {
+    Builder builder = new Builder(type, name);
+    builder.javadoc.add(javadoc);
+    builder.annotations.addAll(annotations);
+    builder.modifiers.addAll(modifiers);
+    builder.initializer = initializer.isEmpty() ? null : initializer;
+    return builder;
+  }
+
+  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<>();
+    private CodeBlock initializer = null;
+
+    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) {
+        this.annotations.add(annotationSpec);
+      }
+      return this;
+    }
+
+    public Builder addAnnotation(AnnotationSpec annotationSpec) {
+      this.annotations.add(annotationSpec);
+      return this;
+    }
+
+    public Builder addAnnotation(ClassName annotation) {
+      this.annotations.add(AnnotationSpec.builder(annotation).build());
+      return this;
+    }
+
+    public Builder addAnnotation(Class<?> annotation) {
+      return addAnnotation(ClassName.get(annotation));
+    }
+
+    public Builder addModifiers(Modifier... modifiers) {
+      Collections.addAll(this.modifiers, modifiers);
+      return this;
+    }
+
+    public Builder initializer(String format, Object... args) {
+      return initializer(CodeBlock.of(format, args));
+    }
+
+    public Builder initializer(CodeBlock codeBlock) {
+      checkState(this.initializer == null, "initializer was already set");
+      this.initializer = checkNotNull(codeBlock, "codeBlock == null");
+      return this;
+    }
+
+    public FieldSpec build() {
+      return new FieldSpec(this);
+    }
+  }
+}
diff --git a/src/main/java/com/squareup/javapoet/JavaFile.java b/src/main/java/com/squareup/javapoet/JavaFile.java
new file mode 100644
index 0000000..e7662dd
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/JavaFile.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.annotation.processing.Filer;
+import javax.lang.model.element.Element;
+import javax.tools.JavaFileObject;
+import javax.tools.JavaFileObject.Kind;
+import javax.tools.SimpleJavaFileObject;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/** A Java file containing a single top level class. */
+public final class JavaFile {
+  private static final Appendable NULL_APPENDABLE = new Appendable() {
+    @Override public Appendable append(CharSequence charSequence) {
+      return this;
+    }
+    @Override public Appendable append(CharSequence charSequence, int start, int end) {
+      return this;
+    }
+    @Override public Appendable append(char c) {
+      return this;
+    }
+  };
+
+  public final CodeBlock fileComment;
+  public final String packageName;
+  public final TypeSpec typeSpec;
+  public final boolean skipJavaLangImports;
+  private final Set<String> staticImports;
+  private final String indent;
+
+  private JavaFile(Builder builder) {
+    this.fileComment = builder.fileComment.build();
+    this.packageName = builder.packageName;
+    this.typeSpec = builder.typeSpec;
+    this.skipJavaLangImports = builder.skipJavaLangImports;
+    this.staticImports = Util.immutableSet(builder.staticImports);
+    this.indent = builder.indent;
+  }
+
+  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);
+    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);
+    emit(codeWriter);
+  }
+
+  /** Writes this to {@code directory} as UTF-8 using the standard directory structure. */
+  public void writeTo(Path directory) throws IOException {
+    checkArgument(Files.notExists(directory) || Files.isDirectory(directory),
+        "path %s exists but is not a directory.", directory);
+    Path outputDirectory = directory;
+    if (!packageName.isEmpty()) {
+      for (String packageComponent : packageName.split("\\.")) {
+        outputDirectory = outputDirectory.resolve(packageComponent);
+      }
+      Files.createDirectories(outputDirectory);
+    }
+
+    Path outputPath = outputDirectory.resolve(typeSpec.name + ".java");
+    try (Writer writer = new OutputStreamWriter(Files.newOutputStream(outputPath), UTF_8)) {
+      writeTo(writer);
+    }
+  }
+
+  /** Writes this to {@code directory} as UTF-8 using the standard directory structure. */
+  public void writeTo(File directory) throws IOException {
+    writeTo(directory.toPath());
+  }
+
+  /** Writes this to {@code filer}. */
+  public void writeTo(Filer filer) throws IOException {
+    String fileName = packageName.isEmpty()
+        ? typeSpec.name
+        : packageName + "." + typeSpec.name;
+    List<Element> originatingElements = typeSpec.originatingElements;
+    JavaFileObject filerSourceFile = filer.createSourceFile(fileName,
+        originatingElements.toArray(new Element[originatingElements.size()]));
+    try (Writer writer = filerSourceFile.openWriter()) {
+      writeTo(writer);
+    } catch (Exception e) {
+      try {
+        filerSourceFile.delete();
+      } catch (Exception ignored) {
+      }
+      throw e;
+    }
+  }
+
+  private void emit(CodeWriter codeWriter) throws IOException {
+    codeWriter.pushPackage(packageName);
+
+    if (!fileComment.isEmpty()) {
+      codeWriter.emitComment(fileComment);
+    }
+
+    if (!packageName.isEmpty()) {
+      codeWriter.emit("package $L;\n", packageName);
+      codeWriter.emit("\n");
+    }
+
+    if (!staticImports.isEmpty()) {
+      for (String signature : staticImports) {
+        codeWriter.emit("import static $L;\n", signature);
+      }
+      codeWriter.emit("\n");
+    }
+
+    int importedTypesCount = 0;
+    for (ClassName className : new TreeSet<>(codeWriter.importedTypes().values())) {
+      if (skipJavaLangImports && className.packageName().equals("java.lang")) continue;
+      codeWriter.emit("import $L;\n", className.withoutAnnotations());
+      importedTypesCount++;
+    }
+
+    if (importedTypesCount > 0) {
+      codeWriter.emit("\n");
+    }
+
+    typeSpec.emit(codeWriter, null, Collections.emptySet());
+
+    codeWriter.popPackage();
+  }
+
+  @Override public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null) return false;
+    if (getClass() != o.getClass()) return false;
+    return toString().equals(o.toString());
+  }
+
+  @Override public int hashCode() {
+    return toString().hashCode();
+  }
+
+  @Override public String toString() {
+    try {
+      StringBuilder result = new StringBuilder();
+      writeTo(result);
+      return result.toString();
+    } catch (IOException e) {
+      throw new AssertionError();
+    }
+  }
+
+  public JavaFileObject toJavaFileObject() {
+    URI uri = URI.create((packageName.isEmpty()
+        ? typeSpec.name
+        : packageName.replace('.', '/') + '/' + typeSpec.name)
+        + Kind.SOURCE.extension);
+    return new SimpleJavaFileObject(uri, Kind.SOURCE) {
+      private final long lastModified = System.currentTimeMillis();
+      @Override public String getCharContent(boolean ignoreEncodingErrors) {
+        return JavaFile.this.toString();
+      }
+      @Override public InputStream openInputStream() throws IOException {
+        return new ByteArrayInputStream(getCharContent(true).getBytes(UTF_8));
+      }
+      @Override public long getLastModified() {
+        return lastModified;
+      }
+    };
+  }
+
+  public static Builder builder(String packageName, TypeSpec typeSpec) {
+    checkNotNull(packageName, "packageName == null");
+    checkNotNull(typeSpec, "typeSpec == null");
+    return new Builder(packageName, typeSpec);
+  }
+
+  public Builder toBuilder() {
+    Builder builder = new Builder(packageName, typeSpec);
+    builder.fileComment.add(fileComment);
+    builder.skipJavaLangImports = skipJavaLangImports;
+    builder.indent = indent;
+    return builder;
+  }
+
+  public static final class Builder {
+    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 = "  ";
+
+    private Builder(String packageName, TypeSpec typeSpec) {
+      this.packageName = packageName;
+      this.typeSpec = typeSpec;
+    }
+
+    public Builder addFileComment(String format, Object... args) {
+      this.fileComment.add(format, args);
+      return this;
+    }
+
+    public Builder addStaticImport(Enum<?> constant) {
+      return addStaticImport(ClassName.get(constant.getDeclaringClass()), constant.name());
+    }
+
+    public Builder addStaticImport(Class<?> clazz, String... names) {
+      return addStaticImport(ClassName.get(clazz), names);
+    }
+
+    public Builder addStaticImport(ClassName className, String... names) {
+      checkArgument(className != null, "className == null");
+      checkArgument(names != null, "names == null");
+      checkArgument(names.length > 0, "names array is empty");
+      for (String name : names) {
+        checkArgument(name != null, "null entry in names array: %s", Arrays.toString(names));
+        staticImports.add(className.canonicalName + "." + name);
+      }
+      return this;
+    }
+
+    /**
+     * Call this to omit imports for classes in {@code java.lang}, such as {@code java.lang.String}.
+     *
+     * <p>By default, JavaPoet explicitly imports types in {@code java.lang} to defend against
+     * naming conflicts. Suppose an (ill-advised) class is named {@code com.example.String}. When
+     * {@code java.lang} imports are skipped, generated code in {@code com.example} that references
+     * {@code java.lang.String} will get {@code com.example.String} instead.
+     */
+    public Builder skipJavaLangImports(boolean skipJavaLangImports) {
+      this.skipJavaLangImports = skipJavaLangImports;
+      return this;
+    }
+
+    public Builder indent(String indent) {
+      this.indent = indent;
+      return this;
+    }
+
+    public JavaFile build() {
+      return new JavaFile(this);
+    }
+  }
+}
diff --git a/src/main/java/com/squareup/javapoet/LineWrapper.java b/src/main/java/com/squareup/javapoet/LineWrapper.java
new file mode 100644
index 0000000..6aa3131
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/LineWrapper.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+
+import static com.squareup.javapoet.Util.checkNotNull;
+
+/**
+ * Implements soft line wrapping on an appendable. To use, append characters using {@link #append}
+ * or soft-wrapping spaces using {@link #wrappingSpace}.
+ */
+final class LineWrapper {
+  private final Appendable out;
+  private final String indent;
+  private final int columnLimit;
+  private boolean closed;
+
+  /** Characters written since the last wrapping space that haven't yet been flushed. */
+  private final StringBuilder buffer = new StringBuilder();
+
+  /** The number of characters since the most recent newline. Includes both out and the buffer. */
+  private int column = 0;
+
+  /**
+   * -1 if we have no buffering; otherwise the number of {@code indent}s to write after wrapping.
+   */
+  private int indentLevel = -1;
+
+  /**
+   * Null if we have no buffering; otherwise the type to pass to the next call to {@link #flush}.
+   */
+  private FlushType nextFlush;
+
+  LineWrapper(Appendable out, String indent, int columnLimit) {
+    checkNotNull(out, "out == null");
+    this.out = out;
+    this.indent = indent;
+    this.columnLimit = columnLimit;
+  }
+
+  /** 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");
+
+    if (nextFlush != null) {
+      int nextNewline = s.indexOf('\n');
+
+      // If s doesn't cause the current line to cross the limit, buffer it and return. We'll decide
+      // whether or not we have to wrap it later.
+      if (nextNewline == -1 && column + s.length() <= columnLimit) {
+        buffer.append(s);
+        column += s.length();
+        return;
+      }
+
+      // Wrap if appending s would overflow the current line.
+      boolean wrap = nextNewline == -1 || column + nextNewline > columnLimit;
+      flush(wrap ? FlushType.WRAP : nextFlush);
+    }
+
+    out.append(s);
+    int lastNewline = s.lastIndexOf('\n');
+    column = lastNewline != -1
+        ? s.length() - lastNewline - 1
+        : column + s.length();
+  }
+
+  /** Emit either a space or a newline character. */
+  void wrappingSpace(int indentLevel) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+
+    if (this.nextFlush != null) flush(nextFlush);
+    column++; // Increment the column even though the space is deferred to next call to flush().
+    this.nextFlush = FlushType.SPACE;
+    this.indentLevel = indentLevel;
+  }
+
+  /** Emit a newline character if the line will exceed it's limit, otherwise do nothing. */
+  void zeroWidthSpace(int indentLevel) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+
+    if (column == 0) return;
+    if (this.nextFlush != null) flush(nextFlush);
+    this.nextFlush = FlushType.EMPTY;
+    this.indentLevel = indentLevel;
+  }
+
+  /** Flush any outstanding text and forbid future writes to this line wrapper. */
+  void close() throws IOException {
+    if (nextFlush != null) flush(nextFlush);
+    closed = true;
+  }
+
+  /** Write the space followed by any buffered text that follows it. */
+  private void flush(FlushType flushType) throws IOException {
+    switch (flushType) {
+      case WRAP:
+        out.append('\n');
+        for (int i = 0; i < indentLevel; i++) {
+          out.append(indent);
+        }
+        column = indentLevel * indent.length();
+        column += buffer.length();
+        break;
+      case SPACE:
+        out.append(' ');
+        break;
+      case EMPTY:
+        break;
+      default:
+        throw new IllegalArgumentException("Unknown FlushType: " + flushType);
+    }
+
+    out.append(buffer);
+    buffer.delete(0, buffer.length());
+    indentLevel = -1;
+    nextFlush = null;
+  }
+
+  private enum FlushType {
+    WRAP, SPACE, EMPTY;
+  }
+}
diff --git a/src/main/java/com/squareup/javapoet/MethodSpec.java b/src/main/java/com/squareup/javapoet/MethodSpec.java
new file mode 100644
index 0000000..a2c7c43
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/MethodSpec.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.util.Types;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+import static com.squareup.javapoet.Util.checkState;
+
+/** A generated constructor or method declaration. */
+public final class MethodSpec {
+  static final String CONSTRUCTOR = "<init>";
+
+  public final String name;
+  public final CodeBlock javadoc;
+  public final List<AnnotationSpec> annotations;
+  public final Set<Modifier> modifiers;
+  public final List<TypeVariableName> typeVariables;
+  public final TypeName returnType;
+  public final List<ParameterSpec> parameters;
+  public final boolean varargs;
+  public final List<TypeName> exceptions;
+  public final CodeBlock code;
+  public final CodeBlock defaultValue;
+
+  private MethodSpec(Builder builder) {
+    CodeBlock code = builder.code.build();
+    checkArgument(code.isEmpty() || !builder.modifiers.contains(Modifier.ABSTRACT),
+        "abstract method %s cannot have code", builder.name);
+    checkArgument(!builder.varargs || lastParameterIsArray(builder.parameters),
+        "last parameter of varargs method %s must be an array", builder.name);
+
+    this.name = checkNotNull(builder.name, "name == null");
+    this.javadoc = builder.javadoc.build();
+    this.annotations = Util.immutableList(builder.annotations);
+    this.modifiers = Util.immutableSet(builder.modifiers);
+    this.typeVariables = Util.immutableList(builder.typeVariables);
+    this.returnType = builder.returnType;
+    this.parameters = Util.immutableList(builder.parameters);
+    this.varargs = builder.varargs;
+    this.exceptions = Util.immutableList(builder.exceptions);
+    this.defaultValue = builder.defaultValue;
+    this.code = code;
+  }
+
+  private boolean lastParameterIsArray(List<ParameterSpec> parameters) {
+    return !parameters.isEmpty()
+        && TypeName.asArray((parameters.get(parameters.size() - 1).type)) != null;
+  }
+
+  void emit(CodeWriter codeWriter, String enclosingName, Set<Modifier> implicitModifiers)
+      throws IOException {
+    codeWriter.emitJavadoc(javadoc);
+    codeWriter.emitAnnotations(annotations, false);
+    codeWriter.emitModifiers(modifiers, implicitModifiers);
+
+    if (!typeVariables.isEmpty()) {
+      codeWriter.emitTypeVariables(typeVariables);
+      codeWriter.emit(" ");
+    }
+
+    if (isConstructor()) {
+      codeWriter.emit("$L($Z", enclosingName);
+    } else {
+      codeWriter.emit("$T $L($Z", returnType, name);
+    }
+
+    boolean firstParameter = true;
+    for (Iterator<ParameterSpec> i = parameters.iterator(); i.hasNext(); ) {
+      ParameterSpec parameter = i.next();
+      if (!firstParameter) codeWriter.emit(",").emitWrappingSpace();
+      parameter.emit(codeWriter, !i.hasNext() && varargs);
+      firstParameter = false;
+    }
+
+    codeWriter.emit(")");
+
+    if (defaultValue != null && !defaultValue.isEmpty()) {
+      codeWriter.emit(" default ");
+      codeWriter.emit(defaultValue);
+    }
+
+    if (!exceptions.isEmpty()) {
+      codeWriter.emitWrappingSpace().emit("throws");
+      boolean firstException = true;
+      for (TypeName exception : exceptions) {
+        if (!firstException) codeWriter.emit(",");
+        codeWriter.emitWrappingSpace().emit("$T", exception);
+        firstException = false;
+      }
+    }
+
+    if (hasModifier(Modifier.ABSTRACT)) {
+      codeWriter.emit(";\n");
+    } else if (hasModifier(Modifier.NATIVE)) {
+      // Code is allowed to support stuff like GWT JSNI.
+      codeWriter.emit(code);
+      codeWriter.emit(";\n");
+    } else {
+      codeWriter.emit(" {\n");
+
+      codeWriter.indent();
+      codeWriter.emit(code);
+      codeWriter.unindent();
+
+      codeWriter.emit("}\n");
+    }
+  }
+
+  public boolean hasModifier(Modifier modifier) {
+    return modifiers.contains(modifier);
+  }
+
+  public boolean isConstructor() {
+    return name.equals(CONSTRUCTOR);
+  }
+
+  @Override public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null) return false;
+    if (getClass() != o.getClass()) return false;
+    return toString().equals(o.toString());
+  }
+
+  @Override public int hashCode() {
+    return toString().hashCode();
+  }
+
+  @Override public String toString() {
+    StringBuilder out = new StringBuilder();
+    try {
+      CodeWriter codeWriter = new CodeWriter(out);
+      emit(codeWriter, "Constructor", Collections.emptySet());
+      return out.toString();
+    } catch (IOException e) {
+      throw new AssertionError();
+    }
+  }
+
+  public static Builder methodBuilder(String name) {
+    return new Builder(name);
+  }
+
+  public static Builder constructorBuilder() {
+    return new Builder(CONSTRUCTOR);
+  }
+
+  /**
+   * Returns a new method spec builder that overrides {@code method}.
+   *
+   * <p>This will copy its visibility modifiers, type parameters, return type, name, parameters, and
+   * throws declarations. An {@link Override} annotation will be added.
+   *
+   * <p>Note that in JavaPoet 1.2 through 1.7 this method retained annotations from the method and
+   * parameters of the overridden method. Since JavaPoet 1.8 annotations must be added separately.
+   */
+  public static Builder overriding(ExecutableElement method) {
+    checkNotNull(method, "method == null");
+
+    Element enclosingClass = method.getEnclosingElement();
+    if (enclosingClass.getModifiers().contains(Modifier.FINAL)) {
+      throw new IllegalArgumentException("Cannot override method on final class " + enclosingClass);
+    }
+
+    Set<Modifier> modifiers = method.getModifiers();
+    if (modifiers.contains(Modifier.PRIVATE)
+        || modifiers.contains(Modifier.FINAL)
+        || modifiers.contains(Modifier.STATIC)) {
+      throw new IllegalArgumentException("cannot override method with modifiers: " + modifiers);
+    }
+
+    String methodName = method.getSimpleName().toString();
+    MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(methodName);
+
+    methodBuilder.addAnnotation(Override.class);
+
+    modifiers = new LinkedHashSet<>(modifiers);
+    modifiers.remove(Modifier.ABSTRACT);
+    modifiers.remove(Modifier.DEFAULT);
+    methodBuilder.addModifiers(modifiers);
+
+    for (TypeParameterElement typeParameterElement : method.getTypeParameters()) {
+      TypeVariable var = (TypeVariable) typeParameterElement.asType();
+      methodBuilder.addTypeVariable(TypeVariableName.get(var));
+    }
+
+    methodBuilder.returns(TypeName.get(method.getReturnType()));
+    methodBuilder.addParameters(ParameterSpec.parametersOf(method));
+    methodBuilder.varargs(method.isVarArgs());
+
+    for (TypeMirror thrownType : method.getThrownTypes()) {
+      methodBuilder.addException(TypeName.get(thrownType));
+    }
+
+    return methodBuilder;
+  }
+
+  /**
+   * Returns a new method spec builder that overrides {@code method} as a member of {@code
+   * enclosing}. This will resolve type parameters: for example overriding {@link
+   * Comparable#compareTo} in a type that implements {@code Comparable<Movie>}, the {@code T}
+   * parameter will be resolved to {@code Movie}.
+   *
+   * <p>This will copy its visibility modifiers, type parameters, return type, name, parameters, and
+   * throws declarations. An {@link Override} annotation will be added.
+   *
+   * <p>Note that in JavaPoet 1.2 through 1.7 this method retained annotations from the method and
+   * parameters of the overridden method. Since JavaPoet 1.8 annotations must be added separately.
+   */
+  public static Builder overriding(
+      ExecutableElement method, DeclaredType enclosing, Types types) {
+    ExecutableType executableType = (ExecutableType) types.asMemberOf(enclosing, method);
+    List<? extends TypeMirror> resolvedParameterTypes = executableType.getParameterTypes();
+    List<? extends TypeMirror> resolvedThrownTypes = executableType.getThrownTypes();
+    TypeMirror resolvedReturnType = executableType.getReturnType();
+
+    Builder builder = overriding(method);
+    builder.returns(TypeName.get(resolvedReturnType));
+    for (int i = 0, size = builder.parameters.size(); i < size; i++) {
+      ParameterSpec parameter = builder.parameters.get(i);
+      TypeName type = TypeName.get(resolvedParameterTypes.get(i));
+      builder.parameters.set(i, parameter.toBuilder(type, parameter.name).build());
+    }
+    builder.exceptions.clear();
+    for (int i = 0, size = resolvedThrownTypes.size(); i < size; i++) {
+      builder.addException(TypeName.get(resolvedThrownTypes.get(i)));
+    }
+
+    return builder;
+  }
+
+  public Builder toBuilder() {
+    Builder builder = new Builder(name);
+    builder.javadoc.add(javadoc);
+    builder.annotations.addAll(annotations);
+    builder.modifiers.addAll(modifiers);
+    builder.typeVariables.addAll(typeVariables);
+    builder.returnType = returnType;
+    builder.parameters.addAll(parameters);
+    builder.exceptions.addAll(exceptions);
+    builder.code.add(code);
+    builder.varargs = varargs;
+    builder.defaultValue = defaultValue;
+    return builder;
+  }
+
+  public static final class Builder {
+    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 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;
+
+    private Builder(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;
+    }
+
+    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) {
+        this.annotations.add(annotationSpec);
+      }
+      return this;
+    }
+
+    public Builder addAnnotation(AnnotationSpec annotationSpec) {
+      this.annotations.add(annotationSpec);
+      return this;
+    }
+
+    public Builder addAnnotation(ClassName annotation) {
+      this.annotations.add(AnnotationSpec.builder(annotation).build());
+      return this;
+    }
+
+    public Builder addAnnotation(Class<?> annotation) {
+      return addAnnotation(ClassName.get(annotation));
+    }
+
+    public Builder addModifiers(Modifier... modifiers) {
+      checkNotNull(modifiers, "modifiers == null");
+      Collections.addAll(this.modifiers, modifiers);
+      return this;
+    }
+
+    public Builder addModifiers(Iterable<Modifier> modifiers) {
+      checkNotNull(modifiers, "modifiers == null");
+      for (Modifier modifier : modifiers) {
+        this.modifiers.add(modifier);
+      }
+      return this;
+    }
+
+    public Builder addTypeVariables(Iterable<TypeVariableName> typeVariables) {
+      checkArgument(typeVariables != null, "typeVariables == null");
+      for (TypeVariableName typeVariable : typeVariables) {
+        this.typeVariables.add(typeVariable);
+      }
+      return this;
+    }
+
+    public Builder addTypeVariable(TypeVariableName typeVariable) {
+      typeVariables.add(typeVariable);
+      return this;
+    }
+
+    public Builder returns(TypeName returnType) {
+      checkState(!name.equals(CONSTRUCTOR), "constructor cannot have return type.");
+      this.returnType = returnType;
+      return this;
+    }
+
+    public Builder returns(Type returnType) {
+      return returns(TypeName.get(returnType));
+    }
+
+    public Builder addParameters(Iterable<ParameterSpec> parameterSpecs) {
+      checkArgument(parameterSpecs != null, "parameterSpecs == null");
+      for (ParameterSpec parameterSpec : parameterSpecs) {
+        this.parameters.add(parameterSpec);
+      }
+      return this;
+    }
+
+    public Builder addParameter(ParameterSpec parameterSpec) {
+      this.parameters.add(parameterSpec);
+      return this;
+    }
+
+    public Builder addParameter(TypeName type, String name, Modifier... modifiers) {
+      return addParameter(ParameterSpec.builder(type, name, modifiers).build());
+    }
+
+    public Builder addParameter(Type type, String name, Modifier... modifiers) {
+      return addParameter(TypeName.get(type), name, modifiers);
+    }
+
+    public Builder varargs() {
+      return varargs(true);
+    }
+
+    public Builder varargs(boolean varargs) {
+      this.varargs = varargs;
+      return this;
+    }
+
+    public Builder addExceptions(Iterable<? extends TypeName> exceptions) {
+      checkArgument(exceptions != null, "exceptions == null");
+      for (TypeName exception : exceptions) {
+        this.exceptions.add(exception);
+      }
+      return this;
+    }
+
+    public Builder addException(TypeName exception) {
+      this.exceptions.add(exception);
+      return this;
+    }
+
+    public Builder addException(Type exception) {
+      return addException(TypeName.get(exception));
+    }
+
+    public Builder addCode(String format, Object... args) {
+      code.add(format, args);
+      return this;
+    }
+
+    public Builder addNamedCode(String format, Map<String, ?> args) {
+      code.addNamed(format, args);
+      return this;
+    }
+
+    public Builder addCode(CodeBlock codeBlock) {
+      code.add(codeBlock);
+      return this;
+    }
+
+    public Builder addComment(String format, Object... args) {
+      code.add("// " + format + "\n", args);
+      return this;
+    }
+
+    public Builder defaultValue(String format, Object... args) {
+      return defaultValue(CodeBlock.of(format, args));
+    }
+
+    public Builder defaultValue(CodeBlock codeBlock) {
+      checkState(this.defaultValue == null, "defaultValue was already set");
+      this.defaultValue = checkNotNull(codeBlock, "codeBlock == null");
+      return this;
+    }
+
+    /**
+     * @param controlFlow the control flow construct and its code, such as "if (foo == 5)".
+     * Shouldn't contain braces or newline characters.
+     */
+    public Builder beginControlFlow(String controlFlow, Object... args) {
+      code.beginControlFlow(controlFlow, args);
+      return this;
+    }
+
+    /**
+     * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
+     *     Shouldn't contain braces or newline characters.
+     */
+    public Builder nextControlFlow(String controlFlow, Object... args) {
+      code.nextControlFlow(controlFlow, args);
+      return this;
+    }
+
+    public Builder endControlFlow() {
+      code.endControlFlow();
+      return this;
+    }
+
+    /**
+     * @param controlFlow the optional control flow construct and its code, such as
+     *     "while(foo == 20)". Only used for "do/while" control flows.
+     */
+    public Builder endControlFlow(String controlFlow, Object... args) {
+      code.endControlFlow(controlFlow, args);
+      return this;
+    }
+
+    public Builder addStatement(String format, Object... args) {
+      code.addStatement(format, args);
+      return this;
+    }
+
+    public Builder addStatement(CodeBlock codeBlock) {
+      code.addStatement(codeBlock);
+      return this;
+    }
+
+    public MethodSpec build() {
+      return new MethodSpec(this);
+    }
+  }
+}
diff --git a/src/main/java/com/squareup/javapoet/NameAllocator.java b/src/main/java/com/squareup/javapoet/NameAllocator.java
new file mode 100644
index 0000000..8269664
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/NameAllocator.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import javax.lang.model.SourceVersion;
+
+import static com.squareup.javapoet.Util.checkNotNull;
+
+/**
+ * Assigns Java identifier names to avoid collisions, keywords, and invalid characters. To use,
+ * first create an instance and allocate all of the names that you need. Typically this is a
+ * mix of user-supplied names and constants: <pre>   {@code
+ *
+ *   NameAllocator nameAllocator = new NameAllocator();
+ *   for (MyProperty property : properties) {
+ *     nameAllocator.newName(property.name(), property);
+ *   }
+ *   nameAllocator.newName("sb", "string builder");
+ * }</pre>
+ *
+ * Pass a unique tag object to each allocation. The tag scopes the name, and can be used to look up
+ * the allocated name later. Typically the tag is the object that is being named. In the above
+ * example we use {@code property} for the user-supplied property names, and {@code "string
+ * builder"} for our constant string builder.
+ *
+ * <p>Once we've allocated names we can use them when generating code: <pre>   {@code
+ *
+ *   MethodSpec.Builder builder = MethodSpec.methodBuilder("toString")
+ *       .addAnnotation(Override.class)
+ *       .addModifiers(Modifier.PUBLIC)
+ *       .returns(String.class);
+ *
+ *   builder.addStatement("$1T $2N = new $1T()",
+ *       StringBuilder.class, nameAllocator.get("string builder"));
+ *   for (MyProperty property : properties) {
+ *     builder.addStatement("$N.append($N)",
+ *         nameAllocator.get("string builder"), nameAllocator.get(property));
+ *   }
+ *   builder.addStatement("return $N", nameAllocator.get("string builder"));
+ *   return builder.build();
+ * }</pre>
+ *
+ * The above code generates unique names if presented with conflicts. Given user-supplied properties
+ * with names {@code ab} and {@code sb} this generates the following:  <pre>   {@code
+ *
+ *   &#64;Override
+ *   public String toString() {
+ *     StringBuilder sb_ = new StringBuilder();
+ *     sb_.append(ab);
+ *     sb_.append(sb);
+ *     return sb_.toString();
+ *   }
+ * }</pre>
+ *
+ * The underscore is appended to {@code sb} to avoid conflicting with the user-supplied {@code sb}
+ * property. Underscores are also prefixed for names that start with a digit, and used to replace
+ * name-unsafe characters like space or dash.
+ *
+ * <p>When dealing with multiple independent inner scopes, use a {@link #clone()} of the
+ * NameAllocator used for the outer scope to further refine name allocation for a specific inner
+ * scope.
+ */
+public final class NameAllocator implements Cloneable {
+  private final Set<String> allocatedNames;
+  private final Map<Object, String> tagToName;
+
+  public NameAllocator() {
+    this(new LinkedHashSet<>(), new LinkedHashMap<>());
+  }
+
+  private NameAllocator(LinkedHashSet<String> allocatedNames,
+                        LinkedHashMap<Object, String> tagToName) {
+    this.allocatedNames = allocatedNames;
+    this.tagToName = tagToName;
+  }
+
+  /**
+   * Return a new name using {@code suggestion} that will not be a Java identifier or clash with
+   * other names.
+   */
+  public String newName(String suggestion) {
+    return newName(suggestion, UUID.randomUUID().toString());
+  }
+
+  /**
+   * Return a new name using {@code suggestion} that will not be a Java identifier or clash with
+   * other names. The returned value can be queried multiple times by passing {@code tag} to
+   * {@link #get(Object)}.
+   */
+  public String newName(String suggestion, Object tag) {
+    checkNotNull(suggestion, "suggestion");
+    checkNotNull(tag, "tag");
+
+    suggestion = toJavaIdentifier(suggestion);
+
+    while (SourceVersion.isKeyword(suggestion) || !allocatedNames.add(suggestion)) {
+      suggestion = suggestion + "_";
+    }
+
+    String replaced = tagToName.put(tag, suggestion);
+    if (replaced != null) {
+      tagToName.put(tag, replaced); // Put things back as they were!
+      throw new IllegalArgumentException("tag " + tag + " cannot be used for both '" + replaced
+          + "' and '" + suggestion + "'");
+    }
+
+    return suggestion;
+  }
+
+  public static String toJavaIdentifier(String suggestion) {
+    StringBuilder result = new StringBuilder();
+    for (int i = 0; i < suggestion.length(); ) {
+      int codePoint = suggestion.codePointAt(i);
+      if (i == 0
+          && !Character.isJavaIdentifierStart(codePoint)
+          && Character.isJavaIdentifierPart(codePoint)) {
+        result.append("_");
+      }
+
+      int validCodePoint = Character.isJavaIdentifierPart(codePoint) ? codePoint : '_';
+      result.appendCodePoint(validCodePoint);
+      i += Character.charCount(codePoint);
+    }
+    return result.toString();
+  }
+
+  /** Retrieve a name created with {@link #newName(String, Object)}. */
+  public String get(Object tag) {
+    String result = tagToName.get(tag);
+    if (result == null) {
+      throw new IllegalArgumentException("unknown tag: " + tag);
+    }
+    return result;
+  }
+
+  /**
+   * Create a deep copy of this NameAllocator. Useful to create multiple independent refinements
+   * of a NameAllocator to be used in the respective definition of multiples, independently-scoped,
+   * inner code blocks.
+   *
+   * @return A deep copy of this NameAllocator.
+   */
+  @Override
+  public NameAllocator clone() {
+    return new NameAllocator(
+        new LinkedHashSet<>(this.allocatedNames),
+        new LinkedHashMap<>(this.tagToName));
+  }
+
+}
diff --git a/src/main/java/com/squareup/javapoet/ParameterSpec.java b/src/main/java/com/squareup/javapoet/ParameterSpec.java
new file mode 100644
index 0000000..63da3f2
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/ParameterSpec.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.VariableElement;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+
+/** A generated parameter declaration. */
+public final class ParameterSpec {
+  public final String name;
+  public final List<AnnotationSpec> annotations;
+  public final Set<Modifier> modifiers;
+  public final TypeName type;
+
+  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");
+  }
+
+  public boolean hasModifier(Modifier modifier) {
+    return modifiers.contains(modifier);
+  }
+
+  void emit(CodeWriter codeWriter, boolean varargs) throws IOException {
+    codeWriter.emitAnnotations(annotations, true);
+    codeWriter.emitModifiers(modifiers);
+    if (varargs) {
+      TypeName.asArray(type).emit(codeWriter, true);
+    } else {
+      type.emit(codeWriter);
+    }
+    codeWriter.emit(" $L", name);
+  }
+
+  @Override public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null) return false;
+    if (getClass() != o.getClass()) return false;
+    return toString().equals(o.toString());
+  }
+
+  @Override public int hashCode() {
+    return toString().hashCode();
+  }
+
+  @Override public String toString() {
+    StringBuilder out = new StringBuilder();
+    try {
+      CodeWriter codeWriter = new CodeWriter(out);
+      emit(codeWriter, false);
+      return out.toString();
+    } catch (IOException e) {
+      throw new AssertionError();
+    }
+  }
+
+  public static ParameterSpec get(VariableElement element) {
+    TypeName type = TypeName.get(element.asType());
+    String name = element.getSimpleName().toString();
+    return ParameterSpec.builder(type, name)
+        .addModifiers(element.getModifiers())
+        .build();
+  }
+
+  static List<ParameterSpec> parametersOf(ExecutableElement method) {
+    List<ParameterSpec> result = new ArrayList<>();
+    for (VariableElement parameter : method.getParameters()) {
+      result.add(ParameterSpec.get(parameter));
+    }
+    return result;
+  }
+
+  public static Builder builder(TypeName type, String name, Modifier... modifiers) {
+    checkNotNull(type, "type == null");
+    checkArgument(SourceVersion.isName(name), "not a valid name: %s", name);
+    return new Builder(type, name)
+        .addModifiers(modifiers);
+  }
+
+  public static Builder builder(Type type, String name, Modifier... modifiers) {
+    return builder(TypeName.get(type), name, modifiers);
+  }
+
+  public Builder toBuilder() {
+    return toBuilder(type, name);
+  }
+
+  Builder toBuilder(TypeName type, String name) {
+    Builder builder = new Builder(type, name);
+    builder.annotations.addAll(annotations);
+    builder.modifiers.addAll(modifiers);
+    return builder;
+  }
+
+  public static final class Builder {
+    private final TypeName type;
+    private final String name;
+
+    private final List<AnnotationSpec> annotations = new ArrayList<>();
+    private final List<Modifier> modifiers = new ArrayList<>();
+
+    private Builder(TypeName type, String name) {
+      this.type = type;
+      this.name = name;
+    }
+
+    public Builder addAnnotations(Iterable<AnnotationSpec> annotationSpecs) {
+      checkArgument(annotationSpecs != null, "annotationSpecs == null");
+      for (AnnotationSpec annotationSpec : annotationSpecs) {
+        this.annotations.add(annotationSpec);
+      }
+      return this;
+    }
+
+    public Builder addAnnotation(AnnotationSpec annotationSpec) {
+      this.annotations.add(annotationSpec);
+      return this;
+    }
+
+    public Builder addAnnotation(ClassName annotation) {
+      this.annotations.add(AnnotationSpec.builder(annotation).build());
+      return this;
+    }
+
+    public Builder addAnnotation(Class<?> annotation) {
+      return addAnnotation(ClassName.get(annotation));
+    }
+
+    public Builder addModifiers(Modifier... modifiers) {
+      Collections.addAll(this.modifiers, modifiers);
+      return this;
+    }
+
+    public Builder addModifiers(Iterable<Modifier> modifiers) {
+      checkNotNull(modifiers, "modifiers == null");
+      for (Modifier modifier : modifiers) {
+        this.modifiers.add(modifier);
+      }
+      return this;
+    }
+
+    public ParameterSpec build() {
+      return new ParameterSpec(this);
+    }
+  }
+}
diff --git a/src/main/java/com/squareup/javapoet/ParameterizedTypeName.java b/src/main/java/com/squareup/javapoet/ParameterizedTypeName.java
new file mode 100644
index 0000000..3a8bf62
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/ParameterizedTypeName.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+
+public final class ParameterizedTypeName extends TypeName {
+  private final ParameterizedTypeName enclosingType;
+  public final ClassName rawType;
+  public final List<TypeName> typeArguments;
+
+  ParameterizedTypeName(ParameterizedTypeName enclosingType, ClassName rawType,
+      List<TypeName> typeArguments) {
+    this(enclosingType, rawType, typeArguments, new ArrayList<>());
+  }
+
+  private ParameterizedTypeName(ParameterizedTypeName enclosingType, ClassName rawType,
+      List<TypeName> typeArguments, List<AnnotationSpec> annotations) {
+    super(annotations);
+    this.rawType = checkNotNull(rawType, "rawType == null").annotated(annotations);
+    this.enclosingType = enclosingType;
+    this.typeArguments = Util.immutableList(typeArguments);
+
+    checkArgument(!this.typeArguments.isEmpty() || enclosingType != null,
+        "no type arguments: %s", rawType);
+    for (TypeName typeArgument : this.typeArguments) {
+      checkArgument(!typeArgument.isPrimitive() && typeArgument != VOID,
+          "invalid type parameter: %s", typeArgument);
+    }
+  }
+
+  @Override public ParameterizedTypeName annotated(List<AnnotationSpec> annotations) {
+    return new ParameterizedTypeName(
+        enclosingType, rawType, typeArguments, concatAnnotations(annotations));
+  }
+
+  @Override
+  public TypeName withoutAnnotations() {
+    return new ParameterizedTypeName(
+        enclosingType, rawType.withoutAnnotations(), typeArguments, new ArrayList<>());
+  }
+
+  @Override CodeWriter emit(CodeWriter out) throws IOException {
+    if (enclosingType != null) {
+      enclosingType.emit(out);
+      out.emit(".");
+      if (isAnnotated()) {
+        out.emit(" ");
+        emitAnnotations(out);
+      }
+      out.emit(rawType.simpleName());
+    } else {
+      rawType.emit(out);
+    }
+    if (!typeArguments.isEmpty()) {
+      out.emitAndIndent("<");
+      boolean firstParameter = true;
+      for (TypeName parameter : typeArguments) {
+        if (!firstParameter) out.emitAndIndent(", ");
+        parameter.emit(out);
+        firstParameter = false;
+      }
+      out.emitAndIndent(">");
+    }
+    return out;
+  }
+
+  /**
+   * Returns a new {@link ParameterizedTypeName} instance for the specified {@code name} as nested
+   * inside this class.
+   */
+  public ParameterizedTypeName nestedClass(String name) {
+    checkNotNull(name, "name == null");
+    return new ParameterizedTypeName(this, rawType.nestedClass(name), new ArrayList<>(),
+        new ArrayList<>());
+  }
+
+  /**
+   * Returns a new {@link ParameterizedTypeName} instance for the specified {@code name} as nested
+   * inside this class, with the specified {@code typeArguments}.
+   */
+  public ParameterizedTypeName nestedClass(String name, List<TypeName> typeArguments) {
+    checkNotNull(name, "name == null");
+    return new ParameterizedTypeName(this, rawType.nestedClass(name), typeArguments,
+        new ArrayList<>());
+  }
+
+  /** Returns a parameterized type, applying {@code typeArguments} to {@code rawType}. */
+  public static ParameterizedTypeName get(ClassName rawType, TypeName... typeArguments) {
+    return new ParameterizedTypeName(null, rawType, Arrays.asList(typeArguments));
+  }
+
+  /** Returns a parameterized type, applying {@code typeArguments} to {@code rawType}. */
+  public static ParameterizedTypeName get(Class<?> rawType, Type... typeArguments) {
+    return new ParameterizedTypeName(null, ClassName.get(rawType), list(typeArguments));
+  }
+
+  /** Returns a parameterized type equivalent to {@code type}. */
+  public static ParameterizedTypeName get(ParameterizedType type) {
+    return get(type, new LinkedHashMap<>());
+  }
+
+  /** Returns a parameterized type equivalent to {@code type}. */
+  static ParameterizedTypeName get(ParameterizedType type, Map<Type, TypeVariableName> map) {
+    ClassName rawType = ClassName.get((Class<?>) type.getRawType());
+    ParameterizedType ownerType = (type.getOwnerType() instanceof ParameterizedType)
+        && !Modifier.isStatic(((Class<?>) type.getRawType()).getModifiers())
+        ? (ParameterizedType) type.getOwnerType() : null;
+    List<TypeName> typeArguments = TypeName.list(type.getActualTypeArguments(), map);
+    return (ownerType != null)
+        ? get(ownerType, map).nestedClass(rawType.simpleName(), typeArguments)
+        : new ParameterizedTypeName(null, rawType, typeArguments);
+  }
+}
diff --git a/src/main/java/com/squareup/javapoet/TypeName.java b/src/main/java/com/squareup/javapoet/TypeName.java
new file mode 100644
index 0000000..38877f7
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/TypeName.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.type.NoType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.SimpleTypeVisitor8;
+
+/**
+ * Any type in Java's type system, plus {@code void}. This class is an identifier for primitive
+ * types like {@code int} and raw reference types like {@code String} and {@code List}. It also
+ * 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
+ * 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
+ * hashCode()} properly.
+ *
+ * <h3>Referencing existing types</h3>
+ *
+ * <p>Primitives and void are constants that you can reference directly: see {@link #INT}, {@link
+ * #DOUBLE}, and {@link #VOID}.
+ *
+ * <p>In an annotation processor you can get a type name instance for a type mirror by calling
+ * {@link #get(TypeMirror)}. In reflection code, you can use {@link #get(Type)}.
+ *
+ * <h3>Defining new types</h3>
+ *
+ * <p>Create new reference types like {@code com.example.HelloWorld} with {@link
+ * ClassName#get(String, String, String...)}. To build composite types like {@code char[]} and
+ * {@code Set<Long>}, use the factory methods on {@link ArrayTypeName}, {@link
+ * ParameterizedTypeName}, {@link TypeVariableName}, and {@link WildcardTypeName}.
+ */
+public class TypeName {
+  public static final TypeName VOID = new TypeName("void");
+  public static final TypeName BOOLEAN = new TypeName("boolean");
+  public static final TypeName BYTE = new TypeName("byte");
+  public static final TypeName SHORT = new TypeName("short");
+  public static final TypeName INT = new TypeName("int");
+  public static final TypeName LONG = new TypeName("long");
+  public static final TypeName CHAR = new TypeName("char");
+  public static final TypeName FLOAT = new TypeName("float");
+  public static final TypeName DOUBLE = new TypeName("double");
+  public static final ClassName OBJECT = ClassName.get("java.lang", "Object");
+
+  private static final ClassName BOXED_VOID = ClassName.get("java.lang", "Void");
+  private static final ClassName BOXED_BOOLEAN = ClassName.get("java.lang", "Boolean");
+  private static final ClassName BOXED_BYTE = ClassName.get("java.lang", "Byte");
+  private static final ClassName BOXED_SHORT = ClassName.get("java.lang", "Short");
+  private static final ClassName BOXED_INT = ClassName.get("java.lang", "Integer");
+  private static final ClassName BOXED_LONG = ClassName.get("java.lang", "Long");
+  private static final ClassName BOXED_CHAR = ClassName.get("java.lang", "Character");
+  private static final ClassName BOXED_FLOAT = ClassName.get("java.lang", "Float");
+  private static final ClassName BOXED_DOUBLE = ClassName.get("java.lang", "Double");
+
+  /** The name of this type if it is a keyword, or null. */
+  private final String keyword;
+  public final List<AnnotationSpec> annotations;
+
+  /** Lazily-initialized toString of this type name. */
+  private String cachedString;
+
+  private TypeName(String keyword) {
+    this(keyword, new ArrayList<>());
+  }
+
+  private TypeName(String keyword, List<AnnotationSpec> annotations) {
+    this.keyword = keyword;
+    this.annotations = Util.immutableList(annotations);
+  }
+
+  // Package-private constructor to prevent third-party subclasses.
+  TypeName(List<AnnotationSpec> annotations) {
+    this(null, annotations);
+  }
+
+  public final TypeName annotated(AnnotationSpec... annotations) {
+    return annotated(Arrays.asList(annotations));
+  }
+
+  public TypeName annotated(List<AnnotationSpec> annotations) {
+    Util.checkNotNull(annotations, "annotations == null");
+    return new TypeName(keyword, concatAnnotations(annotations));
+  }
+
+  public TypeName withoutAnnotations() {
+    return new TypeName(keyword);
+  }
+
+  protected final List<AnnotationSpec> concatAnnotations(List<AnnotationSpec> annotations) {
+    List<AnnotationSpec> allAnnotations = new ArrayList<>(this.annotations);
+    allAnnotations.addAll(annotations);
+    return allAnnotations;
+  }
+
+  public boolean isAnnotated() {
+    return !annotations.isEmpty();
+  }
+
+  /**
+   * Returns true if this is a primitive type like {@code int}. Returns false for all other types
+   * types including boxed primitives and {@code void}.
+   */
+  public boolean isPrimitive() {
+    return keyword != null && this != VOID;
+  }
+
+  /**
+   * Returns true if this is a boxed primitive type like {@code Integer}. Returns false for all
+   * other types types including unboxed primitives and {@code java.lang.Void}.
+   */
+  public boolean isBoxedPrimitive() {
+    return this.equals(BOXED_BOOLEAN)
+        || this.equals(BOXED_BYTE)
+        || this.equals(BOXED_SHORT)
+        || this.equals(BOXED_INT)
+        || this.equals(BOXED_LONG)
+        || this.equals(BOXED_CHAR)
+        || this.equals(BOXED_FLOAT)
+        || this.equals(BOXED_DOUBLE);
+  }
+
+  /**
+   * Returns a boxed type if this is a primitive type (like {@code Integer} for {@code int}) or
+   * {@code void}. Returns this type if boxing doesn't apply.
+   */
+  public TypeName box() {
+    if (keyword == null) return this; // Doesn't need boxing.
+    if (this == VOID) return BOXED_VOID;
+    if (this == BOOLEAN) return BOXED_BOOLEAN;
+    if (this == BYTE) return BOXED_BYTE;
+    if (this == SHORT) return BOXED_SHORT;
+    if (this == INT) return BOXED_INT;
+    if (this == LONG) return BOXED_LONG;
+    if (this == CHAR) return BOXED_CHAR;
+    if (this == FLOAT) return BOXED_FLOAT;
+    if (this == DOUBLE) return BOXED_DOUBLE;
+    throw new AssertionError(keyword);
+  }
+
+  /**
+   * Returns an unboxed type if this is a boxed primitive type (like {@code int} for {@code
+   * Integer}) or {@code Void}. Returns this type if it is already unboxed.
+   *
+   * @throws UnsupportedOperationException if this type isn't eligible for unboxing.
+   */
+  public TypeName unbox() {
+    if (keyword != null) return this; // Already unboxed.
+    if (this.equals(BOXED_VOID)) return VOID;
+    if (this.equals(BOXED_BOOLEAN)) return BOOLEAN;
+    if (this.equals(BOXED_BYTE)) return BYTE;
+    if (this.equals(BOXED_SHORT)) return SHORT;
+    if (this.equals(BOXED_INT)) return INT;
+    if (this.equals(BOXED_LONG)) return LONG;
+    if (this.equals(BOXED_CHAR)) return CHAR;
+    if (this.equals(BOXED_FLOAT)) return FLOAT;
+    if (this.equals(BOXED_DOUBLE)) return DOUBLE;
+    throw new UnsupportedOperationException("cannot unbox " + this);
+  }
+
+  @Override public final boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null) return false;
+    if (getClass() != o.getClass()) return false;
+    return toString().equals(o.toString());
+  }
+
+  @Override public final int hashCode() {
+    return toString().hashCode();
+  }
+
+  @Override public final String toString() {
+    String result = cachedString;
+    if (result == null) {
+      try {
+        StringBuilder resultBuilder = new StringBuilder();
+        CodeWriter codeWriter = new CodeWriter(resultBuilder);
+        emit(codeWriter);
+        result = resultBuilder.toString();
+        cachedString = result;
+      } catch (IOException e) {
+        throw new AssertionError();
+      }
+    }
+    return result;
+  }
+
+  CodeWriter emit(CodeWriter out) throws IOException {
+    if (keyword == null) throw new AssertionError();
+
+    if (isAnnotated()) {
+      out.emit("");
+      emitAnnotations(out);
+    }
+    return out.emitAndIndent(keyword);
+  }
+
+  CodeWriter emitAnnotations(CodeWriter out) throws IOException {
+    for (AnnotationSpec annotation : annotations) {
+      annotation.emit(out, true);
+      out.emit(" ");
+    }
+    return out;
+  }
+
+
+  /** Returns a type name equivalent to {@code mirror}. */
+  public static TypeName get(TypeMirror mirror) {
+    return get(mirror, new LinkedHashMap<>());
+  }
+
+  static TypeName get(TypeMirror mirror,
+      final Map<TypeParameterElement, TypeVariableName> typeVariables) {
+    return mirror.accept(new SimpleTypeVisitor8<TypeName, Void>() {
+      @Override public TypeName visitPrimitive(PrimitiveType t, Void p) {
+        switch (t.getKind()) {
+          case BOOLEAN:
+            return TypeName.BOOLEAN;
+          case BYTE:
+            return TypeName.BYTE;
+          case SHORT:
+            return TypeName.SHORT;
+          case INT:
+            return TypeName.INT;
+          case LONG:
+            return TypeName.LONG;
+          case CHAR:
+            return TypeName.CHAR;
+          case FLOAT:
+            return TypeName.FLOAT;
+          case DOUBLE:
+            return TypeName.DOUBLE;
+          default:
+            throw new AssertionError();
+        }
+      }
+
+      @Override public TypeName visitDeclared(DeclaredType t, Void p) {
+        ClassName rawType = ClassName.get((TypeElement) t.asElement());
+        TypeMirror enclosingType = t.getEnclosingType();
+        TypeName enclosing =
+            (enclosingType.getKind() != TypeKind.NONE)
+                    && !t.asElement().getModifiers().contains(Modifier.STATIC)
+                ? enclosingType.accept(this, null)
+                : null;
+        if (t.getTypeArguments().isEmpty() && !(enclosing instanceof ParameterizedTypeName)) {
+          return rawType;
+        }
+
+        List<TypeName> typeArgumentNames = new ArrayList<>();
+        for (TypeMirror mirror : t.getTypeArguments()) {
+          typeArgumentNames.add(get(mirror, typeVariables));
+        }
+        return enclosing instanceof ParameterizedTypeName
+            ? ((ParameterizedTypeName) enclosing).nestedClass(
+            rawType.simpleName(), typeArgumentNames)
+            : new ParameterizedTypeName(null, rawType, typeArgumentNames);
+      }
+
+      @Override public TypeName visitError(ErrorType t, Void p) {
+        return visitDeclared(t, p);
+      }
+
+      @Override public ArrayTypeName visitArray(ArrayType t, Void p) {
+        return ArrayTypeName.get(t, typeVariables);
+      }
+
+      @Override public TypeName visitTypeVariable(javax.lang.model.type.TypeVariable t, Void p) {
+        return TypeVariableName.get(t, typeVariables);
+      }
+
+      @Override public TypeName visitWildcard(javax.lang.model.type.WildcardType t, Void p) {
+        return WildcardTypeName.get(t, typeVariables);
+      }
+
+      @Override public TypeName visitNoType(NoType t, Void p) {
+        if (t.getKind() == TypeKind.VOID) return TypeName.VOID;
+        return super.visitUnknown(t, p);
+      }
+
+      @Override protected TypeName defaultAction(TypeMirror e, Void p) {
+        throw new IllegalArgumentException("Unexpected type mirror: " + e);
+      }
+    }, null);
+  }
+
+  /** Returns a type name equivalent to {@code type}. */
+  public static TypeName get(Type type) {
+    return get(type, new LinkedHashMap<>());
+  }
+
+  static TypeName get(Type type, Map<Type, TypeVariableName> map) {
+    if (type instanceof Class<?>) {
+      Class<?> classType = (Class<?>) type;
+      if (type == void.class) return VOID;
+      if (type == boolean.class) return BOOLEAN;
+      if (type == byte.class) return BYTE;
+      if (type == short.class) return SHORT;
+      if (type == int.class) return INT;
+      if (type == long.class) return LONG;
+      if (type == char.class) return CHAR;
+      if (type == float.class) return FLOAT;
+      if (type == double.class) return DOUBLE;
+      if (classType.isArray()) return ArrayTypeName.of(get(classType.getComponentType(), map));
+      return ClassName.get(classType);
+
+    } else if (type instanceof ParameterizedType) {
+      return ParameterizedTypeName.get((ParameterizedType) type, map);
+
+    } else if (type instanceof WildcardType) {
+      return WildcardTypeName.get((WildcardType) type, map);
+
+    } else if (type instanceof TypeVariable<?>) {
+      return TypeVariableName.get((TypeVariable<?>) type, map);
+
+    } else if (type instanceof GenericArrayType) {
+      return ArrayTypeName.get((GenericArrayType) type, map);
+
+    } else {
+      throw new IllegalArgumentException("unexpected type: " + type);
+    }
+  }
+
+  /** Converts an array of types to a list of type names. */
+  static List<TypeName> list(Type[] types) {
+    return list(types, new LinkedHashMap<>());
+  }
+
+  static List<TypeName> list(Type[] types, Map<Type, TypeVariableName> map) {
+    List<TypeName> result = new ArrayList<>(types.length);
+    for (Type type : types) {
+      result.add(get(type, map));
+    }
+    return result;
+  }
+
+  /** Returns the array component of {@code type}, or null if {@code type} is not an array. */
+  static TypeName arrayComponent(TypeName type) {
+    return type instanceof ArrayTypeName
+        ? ((ArrayTypeName) type).componentType
+        : null;
+  }
+
+  /** Returns {@code type} as an array, or null if {@code type} is not an array. */
+  static ArrayTypeName asArray(TypeName type) {
+    return type instanceof ArrayTypeName
+        ? ((ArrayTypeName) type)
+        : null;
+  }
+
+}
diff --git a/src/main/java/com/squareup/javapoet/TypeSpec.java b/src/main/java/com/squareup/javapoet/TypeSpec.java
new file mode 100644
index 0000000..46de3a5
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/TypeSpec.java
@@ -0,0 +1,632 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Modifier;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+import static com.squareup.javapoet.Util.checkState;
+import static com.squareup.javapoet.Util.requireExactlyOneOf;
+
+/** A generated class, interface, or enum declaration. */
+public final class TypeSpec {
+  public final Kind kind;
+  public final String name;
+  public final CodeBlock anonymousTypeArguments;
+  public final CodeBlock javadoc;
+  public final List<AnnotationSpec> annotations;
+  public final Set<Modifier> modifiers;
+  public final List<TypeVariableName> typeVariables;
+  public final TypeName superclass;
+  public final List<TypeName> superinterfaces;
+  public final Map<String, TypeSpec> enumConstants;
+  public final List<FieldSpec> fieldSpecs;
+  public final CodeBlock staticBlock;
+  public final CodeBlock initializerBlock;
+  public final List<MethodSpec> methodSpecs;
+  public final List<TypeSpec> typeSpecs;
+  public final List<Element> originatingElements;
+
+  private TypeSpec(Builder builder) {
+    this.kind = builder.kind;
+    this.name = builder.name;
+    this.anonymousTypeArguments = builder.anonymousTypeArguments;
+    this.javadoc = builder.javadoc.build();
+    this.annotations = Util.immutableList(builder.annotations);
+    this.modifiers = Util.immutableSet(builder.modifiers);
+    this.typeVariables = Util.immutableList(builder.typeVariables);
+    this.superclass = builder.superclass;
+    this.superinterfaces = Util.immutableList(builder.superinterfaces);
+    this.enumConstants = Util.immutableMap(builder.enumConstants);
+    this.fieldSpecs = Util.immutableList(builder.fieldSpecs);
+    this.staticBlock = builder.staticBlock.build();
+    this.initializerBlock = builder.initializerBlock.build();
+    this.methodSpecs = Util.immutableList(builder.methodSpecs);
+    this.typeSpecs = Util.immutableList(builder.typeSpecs);
+
+    List<Element> originatingElementsMutable = new ArrayList<>();
+    originatingElementsMutable.addAll(builder.originatingElements);
+    for (TypeSpec typeSpec : builder.typeSpecs) {
+      originatingElementsMutable.addAll(typeSpec.originatingElements);
+    }
+    this.originatingElements = Util.immutableList(originatingElementsMutable);
+  }
+
+  /**
+   * Creates a dummy type spec for type-resolution only (in CodeWriter)
+   * while emitting the type declaration but before entering the type body.
+   */
+  private TypeSpec(TypeSpec type) {
+    assert type.anonymousTypeArguments == null;
+    this.kind = type.kind;
+    this.name = type.name;
+    this.anonymousTypeArguments = null;
+    this.javadoc = type.javadoc;
+    this.annotations = Collections.emptyList();
+    this.modifiers = Collections.emptySet();
+    this.typeVariables = Collections.emptyList();
+    this.superclass = null;
+    this.superinterfaces = Collections.emptyList();
+    this.enumConstants = Collections.emptyMap();
+    this.fieldSpecs = Collections.emptyList();
+    this.staticBlock = type.staticBlock;
+    this.initializerBlock = type.initializerBlock;
+    this.methodSpecs = Collections.emptyList();
+    this.typeSpecs = Collections.emptyList();
+    this.originatingElements = Collections.emptyList();
+  }
+
+  public boolean hasModifier(Modifier modifier) {
+    return modifiers.contains(modifier);
+  }
+
+  public static Builder classBuilder(String name) {
+    return new Builder(Kind.CLASS, checkNotNull(name, "name == null"), null);
+  }
+
+  public static Builder classBuilder(ClassName className) {
+    return classBuilder(checkNotNull(className, "className == null").simpleName());
+  }
+
+  public static Builder interfaceBuilder(String name) {
+    return new Builder(Kind.INTERFACE, checkNotNull(name, "name == null"), null);
+  }
+
+  public static Builder interfaceBuilder(ClassName className) {
+    return interfaceBuilder(checkNotNull(className, "className == null").simpleName());
+  }
+
+  public static Builder enumBuilder(String name) {
+    return new Builder(Kind.ENUM, checkNotNull(name, "name == null"), null);
+  }
+
+  public static Builder enumBuilder(ClassName className) {
+    return enumBuilder(checkNotNull(className, "className == null").simpleName());
+  }
+
+  public static Builder anonymousClassBuilder(String typeArgumentsFormat, Object... args) {
+    return anonymousClassBuilder(CodeBlock.builder()
+        .add(typeArgumentsFormat, args)
+        .build());
+  }
+
+  public static Builder anonymousClassBuilder(CodeBlock typeArguments) {
+    return new Builder(Kind.CLASS, null, typeArguments);
+  }
+
+  public static Builder annotationBuilder(String name) {
+    return new Builder(Kind.ANNOTATION, checkNotNull(name, "name == null"), null);
+  }
+
+  public static Builder annotationBuilder(ClassName className) {
+    return annotationBuilder(checkNotNull(className, "className == null").simpleName());
+  }
+
+  public Builder toBuilder() {
+    Builder builder = new Builder(kind, name, anonymousTypeArguments);
+    builder.javadoc.add(javadoc);
+    builder.annotations.addAll(annotations);
+    builder.modifiers.addAll(modifiers);
+    builder.typeVariables.addAll(typeVariables);
+    builder.superclass = superclass;
+    builder.superinterfaces.addAll(superinterfaces);
+    builder.enumConstants.putAll(enumConstants);
+    builder.fieldSpecs.addAll(fieldSpecs);
+    builder.methodSpecs.addAll(methodSpecs);
+    builder.typeSpecs.addAll(typeSpecs);
+    builder.initializerBlock.add(initializerBlock);
+    builder.staticBlock.add(staticBlock);
+    return builder;
+  }
+
+  void emit(CodeWriter codeWriter, String enumName, Set<Modifier> implicitModifiers)
+      throws IOException {
+    // Nested classes interrupt wrapped line indentation. Stash the current wrapping state and put
+    // it back afterwards when this type is complete.
+    int previousStatementLine = codeWriter.statementLine;
+    codeWriter.statementLine = -1;
+
+    try {
+      if (enumName != null) {
+        codeWriter.emitJavadoc(javadoc);
+        codeWriter.emitAnnotations(annotations, false);
+        codeWriter.emit("$L", enumName);
+        if (!anonymousTypeArguments.formatParts.isEmpty()) {
+          codeWriter.emit("(");
+          codeWriter.emit(anonymousTypeArguments);
+          codeWriter.emit(")");
+        }
+        if (fieldSpecs.isEmpty() && methodSpecs.isEmpty() && typeSpecs.isEmpty()) {
+          return; // Avoid unnecessary braces "{}".
+        }
+        codeWriter.emit(" {\n");
+      } else if (anonymousTypeArguments != null) {
+        TypeName supertype = !superinterfaces.isEmpty() ? superinterfaces.get(0) : superclass;
+        codeWriter.emit("new $T(", supertype);
+        codeWriter.emit(anonymousTypeArguments);
+        codeWriter.emit(") {\n");
+      } else {
+        // Push an empty type (specifically without nested types) for type-resolution.
+        codeWriter.pushType(new TypeSpec(this));
+
+        codeWriter.emitJavadoc(javadoc);
+        codeWriter.emitAnnotations(annotations, false);
+        codeWriter.emitModifiers(modifiers, Util.union(implicitModifiers, kind.asMemberModifiers));
+        if (kind == Kind.ANNOTATION) {
+          codeWriter.emit("$L $L", "@interface", name);
+        } else {
+          codeWriter.emit("$L $L", kind.name().toLowerCase(Locale.US), name);
+        }
+        codeWriter.emitTypeVariables(typeVariables);
+
+        List<TypeName> extendsTypes;
+        List<TypeName> implementsTypes;
+        if (kind == Kind.INTERFACE) {
+          extendsTypes = superinterfaces;
+          implementsTypes = Collections.emptyList();
+        } else {
+          extendsTypes = superclass.equals(ClassName.OBJECT)
+              ? Collections.emptyList()
+              : Collections.singletonList(superclass);
+          implementsTypes = superinterfaces;
+        }
+
+        if (!extendsTypes.isEmpty()) {
+          codeWriter.emit(" extends");
+          boolean firstType = true;
+          for (TypeName type : extendsTypes) {
+            if (!firstType) codeWriter.emit(",");
+            codeWriter.emit(" $T", type);
+            firstType = false;
+          }
+        }
+
+        if (!implementsTypes.isEmpty()) {
+          codeWriter.emit(" implements");
+          boolean firstType = true;
+          for (TypeName type : implementsTypes) {
+            if (!firstType) codeWriter.emit(",");
+            codeWriter.emit(" $T", type);
+            firstType = false;
+          }
+        }
+
+        codeWriter.popType();
+
+        codeWriter.emit(" {\n");
+      }
+
+      codeWriter.pushType(this);
+      codeWriter.indent();
+      boolean firstMember = true;
+      for (Iterator<Map.Entry<String, TypeSpec>> i = enumConstants.entrySet().iterator();
+          i.hasNext(); ) {
+        Map.Entry<String, TypeSpec> enumConstant = i.next();
+        if (!firstMember) codeWriter.emit("\n");
+        enumConstant.getValue().emit(codeWriter, enumConstant.getKey(), Collections.emptySet());
+        firstMember = false;
+        if (i.hasNext()) {
+          codeWriter.emit(",\n");
+        } else if (!fieldSpecs.isEmpty() || !methodSpecs.isEmpty() || !typeSpecs.isEmpty()) {
+          codeWriter.emit(";\n");
+        } else {
+          codeWriter.emit("\n");
+        }
+      }
+
+      // Static fields.
+      for (FieldSpec fieldSpec : fieldSpecs) {
+        if (!fieldSpec.hasModifier(Modifier.STATIC)) continue;
+        if (!firstMember) codeWriter.emit("\n");
+        fieldSpec.emit(codeWriter, kind.implicitFieldModifiers);
+        firstMember = false;
+      }
+
+      if (!staticBlock.isEmpty()) {
+        if (!firstMember) codeWriter.emit("\n");
+        codeWriter.emit(staticBlock);
+        firstMember = false;
+      }
+
+      // Non-static fields.
+      for (FieldSpec fieldSpec : fieldSpecs) {
+        if (fieldSpec.hasModifier(Modifier.STATIC)) continue;
+        if (!firstMember) codeWriter.emit("\n");
+        fieldSpec.emit(codeWriter, kind.implicitFieldModifiers);
+        firstMember = false;
+      }
+
+      // Initializer block.
+      if (!initializerBlock.isEmpty()) {
+        if (!firstMember) codeWriter.emit("\n");
+        codeWriter.emit(initializerBlock);
+        firstMember = false;
+      }
+
+      // Constructors.
+      for (MethodSpec methodSpec : methodSpecs) {
+        if (!methodSpec.isConstructor()) continue;
+        if (!firstMember) codeWriter.emit("\n");
+        methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers);
+        firstMember = false;
+      }
+
+      // Methods (static and non-static).
+      for (MethodSpec methodSpec : methodSpecs) {
+        if (methodSpec.isConstructor()) continue;
+        if (!firstMember) codeWriter.emit("\n");
+        methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers);
+        firstMember = false;
+      }
+
+      // Types.
+      for (TypeSpec typeSpec : typeSpecs) {
+        if (!firstMember) codeWriter.emit("\n");
+        typeSpec.emit(codeWriter, null, kind.implicitTypeModifiers);
+        firstMember = false;
+      }
+
+      codeWriter.unindent();
+      codeWriter.popType();
+
+      codeWriter.emit("}");
+      if (enumName == null && anonymousTypeArguments == null) {
+        codeWriter.emit("\n"); // If this type isn't also a value, include a trailing newline.
+      }
+    } finally {
+      codeWriter.statementLine = previousStatementLine;
+    }
+  }
+
+  @Override public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null) return false;
+    if (getClass() != o.getClass()) return false;
+    return toString().equals(o.toString());
+  }
+
+  @Override public int hashCode() {
+    return toString().hashCode();
+  }
+
+  @Override public String toString() {
+    StringBuilder out = new StringBuilder();
+    try {
+      CodeWriter codeWriter = new CodeWriter(out);
+      emit(codeWriter, null, Collections.emptySet());
+      return out.toString();
+    } catch (IOException e) {
+      throw new AssertionError();
+    }
+  }
+
+  public enum Kind {
+    CLASS(
+        Collections.emptySet(),
+        Collections.emptySet(),
+        Collections.emptySet(),
+        Collections.emptySet()),
+
+    INTERFACE(
+        Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)),
+        Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.ABSTRACT)),
+        Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC)),
+        Util.immutableSet(Collections.singletonList(Modifier.STATIC))),
+
+    ENUM(
+        Collections.emptySet(),
+        Collections.emptySet(),
+        Collections.emptySet(),
+        Collections.singleton(Modifier.STATIC)),
+
+    ANNOTATION(
+        Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)),
+        Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.ABSTRACT)),
+        Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC)),
+        Util.immutableSet(Collections.singletonList(Modifier.STATIC)));
+
+    private final Set<Modifier> implicitFieldModifiers;
+    private final Set<Modifier> implicitMethodModifiers;
+    private final Set<Modifier> implicitTypeModifiers;
+    private final Set<Modifier> asMemberModifiers;
+
+    Kind(Set<Modifier> implicitFieldModifiers,
+        Set<Modifier> implicitMethodModifiers,
+        Set<Modifier> implicitTypeModifiers,
+        Set<Modifier> asMemberModifiers) {
+      this.implicitFieldModifiers = implicitFieldModifiers;
+      this.implicitMethodModifiers = implicitMethodModifiers;
+      this.implicitTypeModifiers = implicitTypeModifiers;
+      this.asMemberModifiers = asMemberModifiers;
+    }
+  }
+
+  public static final class Builder {
+    private final Kind kind;
+    private final String name;
+    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<>();
+
+    private Builder(Kind kind, String name,
+        CodeBlock anonymousTypeArguments) {
+      checkArgument(name == null || SourceVersion.isName(name), "not a valid name: %s", name);
+      this.kind = kind;
+      this.name = name;
+      this.anonymousTypeArguments = anonymousTypeArguments;
+    }
+
+    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) {
+        this.annotations.add(annotationSpec);
+      }
+      return this;
+    }
+
+    public Builder addAnnotation(AnnotationSpec annotationSpec) {
+      checkNotNull(annotationSpec, "annotationSpec == null");
+      this.annotations.add(annotationSpec);
+      return this;
+    }
+
+    public Builder addAnnotation(ClassName annotation) {
+      return addAnnotation(AnnotationSpec.builder(annotation).build());
+    }
+
+    public Builder addAnnotation(Class<?> annotation) {
+      return addAnnotation(ClassName.get(annotation));
+    }
+
+    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);
+      }
+      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);
+      }
+      return this;
+    }
+
+    public Builder addTypeVariable(TypeVariableName typeVariable) {
+      checkState(anonymousTypeArguments == null, "forbidden on anonymous types.");
+      typeVariables.add(typeVariable);
+      return this;
+    }
+
+    public Builder superclass(TypeName superclass) {
+      checkState(this.kind == Kind.CLASS, "only classes have super classes, not " + this.kind);
+      checkState(this.superclass == ClassName.OBJECT,
+          "superclass already set to " + this.superclass);
+      checkArgument(!superclass.isPrimitive(), "superclass may not be a primitive");
+      this.superclass = superclass;
+      return this;
+    }
+
+    public Builder superclass(Type superclass) {
+      return superclass(TypeName.get(superclass));
+    }
+
+    public Builder addSuperinterfaces(Iterable<? extends TypeName> superinterfaces) {
+      checkArgument(superinterfaces != null, "superinterfaces == null");
+      for (TypeName superinterface : superinterfaces) {
+        addSuperinterface(superinterface);
+      }
+      return this;
+    }
+
+    public Builder addSuperinterface(TypeName superinterface) {
+      checkArgument(superinterface != null, "superinterface == null");
+      this.superinterfaces.add(superinterface);
+      return this;
+    }
+
+    public Builder addSuperinterface(Type superinterface) {
+      return addSuperinterface(TypeName.get(superinterface));
+    }
+
+    public Builder addEnumConstant(String name) {
+      return addEnumConstant(name, anonymousClassBuilder("").build());
+    }
+
+    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;
+    }
+
+    public Builder addFields(Iterable<FieldSpec> fieldSpecs) {
+      checkArgument(fieldSpecs != null, "fieldSpecs == null");
+      for (FieldSpec fieldSpec : fieldSpecs) {
+        addField(fieldSpec);
+      }
+      return this;
+    }
+
+    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;
+    }
+
+    public Builder addField(TypeName type, String name, Modifier... modifiers) {
+      return addField(FieldSpec.builder(type, name, modifiers).build());
+    }
+
+    public Builder addField(Type type, String name, Modifier... modifiers) {
+      return addField(TypeName.get(type), name, modifiers);
+    }
+
+    public Builder addStaticBlock(CodeBlock block) {
+      staticBlock.beginControlFlow("static").add(block).endControlFlow();
+      return this;
+    }
+
+    public Builder addInitializerBlock(CodeBlock block) {
+      if ((kind != Kind.CLASS && kind != Kind.ENUM)) {
+        throw new UnsupportedOperationException(kind + " can't have initializer blocks");
+      }
+      initializerBlock.add("{\n")
+          .indent()
+          .add(block)
+          .unindent()
+          .add("}\n");
+      return this;
+    }
+
+    public Builder addMethods(Iterable<MethodSpec> methodSpecs) {
+      checkArgument(methodSpecs != null, "methodSpecs == null");
+      for (MethodSpec methodSpec : methodSpecs) {
+        addMethod(methodSpec);
+      }
+      return this;
+    }
+
+    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;
+    }
+
+    public Builder addTypes(Iterable<TypeSpec> typeSpecs) {
+      checkArgument(typeSpecs != null, "typeSpecs == null");
+      for (TypeSpec typeSpec : typeSpecs) {
+        addType(typeSpec);
+      }
+      return this;
+    }
+
+    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;
+    }
+
+    public Builder addOriginatingElement(Element originatingElement) {
+      originatingElements.add(originatingElement);
+      return this;
+    }
+
+    public TypeSpec build() {
+      checkArgument(kind != Kind.ENUM || !enumConstants.isEmpty(),
+          "at least one enum constant is required for %s", name);
+
+      boolean isAbstract = modifiers.contains(Modifier.ABSTRACT) || kind != Kind.CLASS;
+      for (MethodSpec methodSpec : methodSpecs) {
+        checkArgument(isAbstract || !methodSpec.hasModifier(Modifier.ABSTRACT),
+            "non-abstract type %s cannot declare abstract method %s", name, methodSpec.name);
+      }
+
+      boolean superclassIsObject = superclass.equals(ClassName.OBJECT);
+      int interestingSupertypeCount = (superclassIsObject ? 0 : 1) + superinterfaces.size();
+      checkArgument(anonymousTypeArguments == null || interestingSupertypeCount <= 1,
+          "anonymous type has too many supertypes");
+
+      return new TypeSpec(this);
+    }
+  }
+}
diff --git a/src/main/java/com/squareup/javapoet/TypeVariableName.java b/src/main/java/com/squareup/javapoet/TypeVariableName.java
new file mode 100644
index 0000000..54c2fa5
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/TypeVariableName.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+
+public final class TypeVariableName extends TypeName {
+  public final String name;
+  public final List<TypeName> bounds;
+
+  private TypeVariableName(String name, List<TypeName> bounds) {
+    this(name, bounds, new ArrayList<>());
+  }
+
+  private TypeVariableName(String name, List<TypeName> bounds, List<AnnotationSpec> annotations) {
+    super(annotations);
+    this.name = checkNotNull(name, "name == null");
+    this.bounds = bounds;
+
+    for (TypeName bound : this.bounds) {
+      checkArgument(!bound.isPrimitive() && bound != VOID, "invalid bound: %s", bound);
+    }
+  }
+
+  @Override public TypeVariableName annotated(List<AnnotationSpec> annotations) {
+    return new TypeVariableName(name, bounds, annotations);
+  }
+
+  @Override public TypeName withoutAnnotations() {
+    return new TypeVariableName(name, bounds);
+  }
+
+  public TypeVariableName withBounds(Type... bounds) {
+    return withBounds(TypeName.list(bounds));
+  }
+
+  public TypeVariableName withBounds(TypeName... bounds) {
+    return withBounds(Arrays.asList(bounds));
+  }
+
+  public TypeVariableName withBounds(List<? extends TypeName> bounds) {
+    ArrayList<TypeName> newBounds = new ArrayList<>();
+    newBounds.addAll(this.bounds);
+    newBounds.addAll(bounds);
+    return new TypeVariableName(name, newBounds, annotations);
+  }
+
+  private static TypeVariableName of(String name, List<TypeName> bounds) {
+    // Strip java.lang.Object from bounds if it is present.
+    List<TypeName> boundsNoObject = new ArrayList<>(bounds);
+    boundsNoObject.remove(OBJECT);
+    return new TypeVariableName(name, Collections.unmodifiableList(boundsNoObject));
+  }
+
+  @Override CodeWriter emit(CodeWriter out) throws IOException {
+    emitAnnotations(out);
+    return out.emitAndIndent(name);
+  }
+
+  /** Returns type variable named {@code name} without bounds. */
+  public static TypeVariableName get(String name) {
+    return TypeVariableName.of(name, Collections.emptyList());
+  }
+
+  /** Returns type variable named {@code name} with {@code bounds}. */
+  public static TypeVariableName get(String name, TypeName... bounds) {
+    return TypeVariableName.of(name, Arrays.asList(bounds));
+  }
+
+  /** Returns type variable named {@code name} with {@code bounds}. */
+  public static TypeVariableName get(String name, Type... bounds) {
+    return TypeVariableName.of(name, TypeName.list(bounds));
+  }
+
+  /** Returns type variable equivalent to {@code mirror}. */
+  public static TypeVariableName get(TypeVariable mirror) {
+    return get((TypeParameterElement) mirror.asElement());
+  }
+
+  /**
+   * Make a TypeVariableName for the given TypeMirror. This form is used internally to avoid
+   * infinite recursion in cases like {@code Enum<E extends Enum<E>>}. When we encounter such a
+   * thing, we will make a TypeVariableName without bounds and add that to the {@code typeVariables}
+   * map before looking up the bounds. Then if we encounter this TypeVariable again while
+   * constructing the bounds, we can just return it from the map. And, the code that put the entry
+   * in {@code variables} will make sure that the bounds are filled in before returning.
+   */
+  static TypeVariableName get(
+      TypeVariable mirror, Map<TypeParameterElement, TypeVariableName> typeVariables) {
+    TypeParameterElement element = (TypeParameterElement) mirror.asElement();
+    TypeVariableName typeVariableName = typeVariables.get(element);
+    if (typeVariableName == null) {
+      // Since the bounds field is public, we need to make it an unmodifiableList. But we control
+      // the List that that wraps, which means we can change it before returning.
+      List<TypeName> bounds = new ArrayList<>();
+      List<TypeName> visibleBounds = Collections.unmodifiableList(bounds);
+      typeVariableName = new TypeVariableName(element.getSimpleName().toString(), visibleBounds);
+      typeVariables.put(element, typeVariableName);
+      for (TypeMirror typeMirror : element.getBounds()) {
+        bounds.add(TypeName.get(typeMirror, typeVariables));
+      }
+      bounds.remove(OBJECT);
+    }
+    return typeVariableName;
+  }
+
+  /** Returns type variable equivalent to {@code element}. */
+  public static TypeVariableName get(TypeParameterElement element) {
+    String name = element.getSimpleName().toString();
+    List<? extends TypeMirror> boundsMirrors = element.getBounds();
+
+    List<TypeName> boundsTypeNames = new ArrayList<>();
+    for (TypeMirror typeMirror : boundsMirrors) {
+      boundsTypeNames.add(TypeName.get(typeMirror));
+    }
+
+    return TypeVariableName.of(name, boundsTypeNames);
+  }
+
+  /** Returns type variable equivalent to {@code type}. */
+  public static TypeVariableName get(java.lang.reflect.TypeVariable<?> type) {
+    return get(type, new LinkedHashMap<>());
+  }
+
+  /** @see #get(java.lang.reflect.TypeVariable, Map) */
+  static TypeVariableName get(java.lang.reflect.TypeVariable<?> type,
+      Map<Type, TypeVariableName> map) {
+    TypeVariableName result = map.get(type);
+    if (result == null) {
+      List<TypeName> bounds = new ArrayList<>();
+      List<TypeName> visibleBounds = Collections.unmodifiableList(bounds);
+      result = new TypeVariableName(type.getName(), visibleBounds);
+      map.put(type, result);
+      for (Type bound : type.getBounds()) {
+        bounds.add(TypeName.get(bound, map));
+      }
+      bounds.remove(OBJECT);
+    }
+    return result;
+  }
+}
diff --git a/src/main/java/com/squareup/javapoet/Util.java b/src/main/java/com/squareup/javapoet/Util.java
new file mode 100644
index 0000000..e0eabad
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/Util.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.Modifier;
+
+import static java.lang.Character.isISOControl;
+
+/**
+ * Like Guava, but worse and standalone. This makes it easier to mix JavaPoet with libraries that
+ * bring their own version of Guava.
+ */
+final class Util {
+  private Util() {
+  }
+
+  static <K, V> Map<K, List<V>> immutableMultimap(Map<K, List<V>> multimap) {
+    LinkedHashMap<K, List<V>> result = new LinkedHashMap<>();
+    for (Map.Entry<K, List<V>> entry : multimap.entrySet()) {
+      if (entry.getValue().isEmpty()) continue;
+      result.put(entry.getKey(), immutableList(entry.getValue()));
+    }
+    return Collections.unmodifiableMap(result);
+  }
+
+  static <K, V> Map<K, V> immutableMap(Map<K, V> map) {
+    return Collections.unmodifiableMap(new LinkedHashMap<>(map));
+  }
+
+  static void checkArgument(boolean condition, String format, Object... args) {
+    if (!condition) throw new IllegalArgumentException(String.format(format, args));
+  }
+
+  static <T> T checkNotNull(T reference, String format, Object... args) {
+    if (reference == null) throw new NullPointerException(String.format(format, args));
+    return reference;
+  }
+
+  static void checkState(boolean condition, String format, Object... args) {
+    if (!condition) throw new IllegalStateException(String.format(format, args));
+  }
+
+  static <T> List<T> immutableList(Collection<T> collection) {
+    return Collections.unmodifiableList(new ArrayList<>(collection));
+  }
+
+  static <T> Set<T> immutableSet(Collection<T> set) {
+    return Collections.unmodifiableSet(new LinkedHashSet<>(set));
+  }
+
+  static <T> Set<T> union(Set<T> a, Set<T> b) {
+    Set<T> result = new LinkedHashSet<>();
+    result.addAll(a);
+    result.addAll(b);
+    return result;
+  }
+
+  static void requireExactlyOneOf(Set<Modifier> modifiers, Modifier... mutuallyExclusive) {
+    int count = 0;
+    for (Modifier modifier : mutuallyExclusive) {
+      if (modifiers.contains(modifier)) count++;
+    }
+    checkArgument(count == 1, "modifiers %s must contain one of %s",
+        modifiers, Arrays.toString(mutuallyExclusive));
+  }
+
+  static String characterLiteralWithoutSingleQuotes(char c) {
+    // see https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6
+    switch (c) {
+      case '\b': return "\\b"; /* \u0008: backspace (BS) */
+      case '\t': return "\\t"; /* \u0009: horizontal tab (HT) */
+      case '\n': return "\\n"; /* \u000a: linefeed (LF) */
+      case '\f': return "\\f"; /* \u000c: form feed (FF) */
+      case '\r': return "\\r"; /* \u000d: carriage return (CR) */
+      case '\"': return "\"";  /* \u0022: double quote (") */
+      case '\'': return "\\'"; /* \u0027: single quote (') */
+      case '\\': return "\\\\";  /* \u005c: backslash (\) */
+      default:
+        return isISOControl(c) ? String.format("\\u%04x", (int) c) : Character.toString(c);
+    }
+  }
+
+  /** Returns the string literal representing {@code value}, including wrapping double quotes. */
+  static String stringLiteralWithDoubleQuotes(String value, String indent) {
+    StringBuilder result = new StringBuilder(value.length() + 2);
+    result.append('"');
+    for (int i = 0; i < value.length(); i++) {
+      char c = value.charAt(i);
+      // trivial case: single quote must not be escaped
+      if (c == '\'') {
+        result.append("'");
+        continue;
+      }
+      // trivial case: double quotes must be escaped
+      if (c == '\"') {
+        result.append("\\\"");
+        continue;
+      }
+      // default case: just let character literal do its work
+      result.append(characterLiteralWithoutSingleQuotes(c));
+      // need to append indent after linefeed?
+      if (c == '\n' && i + 1 < value.length()) {
+        result.append("\"\n").append(indent).append(indent).append("+ \"");
+      }
+    }
+    result.append('"');
+    return result.toString();
+  }
+}
diff --git a/src/main/java/com/squareup/javapoet/WildcardTypeName.java b/src/main/java/com/squareup/javapoet/WildcardTypeName.java
new file mode 100644
index 0000000..17cb73f
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/WildcardTypeName.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.TypeMirror;
+
+import static com.squareup.javapoet.Util.checkArgument;
+
+public final class WildcardTypeName extends TypeName {
+  public final List<TypeName> upperBounds;
+  public final List<TypeName> lowerBounds;
+
+  private WildcardTypeName(List<TypeName> upperBounds, List<TypeName> lowerBounds) {
+    this(upperBounds, lowerBounds, new ArrayList<>());
+  }
+
+  private WildcardTypeName(List<TypeName> upperBounds, List<TypeName> lowerBounds,
+      List<AnnotationSpec> annotations) {
+    super(annotations);
+    this.upperBounds = Util.immutableList(upperBounds);
+    this.lowerBounds = Util.immutableList(lowerBounds);
+
+    checkArgument(this.upperBounds.size() == 1, "unexpected extends bounds: %s", upperBounds);
+    for (TypeName upperBound : this.upperBounds) {
+      checkArgument(!upperBound.isPrimitive() && upperBound != VOID,
+          "invalid upper bound: %s", upperBound);
+    }
+    for (TypeName lowerBound : this.lowerBounds) {
+      checkArgument(!lowerBound.isPrimitive() && lowerBound != VOID,
+          "invalid lower bound: %s", lowerBound);
+    }
+  }
+
+  @Override public WildcardTypeName annotated(List<AnnotationSpec> annotations) {
+    return new WildcardTypeName(upperBounds, lowerBounds, concatAnnotations(annotations));
+  }
+
+  @Override public TypeName withoutAnnotations() {
+    return new WildcardTypeName(upperBounds, lowerBounds);
+  }
+
+  @Override CodeWriter emit(CodeWriter out) throws IOException {
+    if (lowerBounds.size() == 1) {
+      return out.emit("? super $T", lowerBounds.get(0));
+    }
+    return upperBounds.get(0).equals(TypeName.OBJECT)
+        ? out.emit("?")
+        : out.emit("? extends $T", upperBounds.get(0));
+  }
+
+  /**
+   * Returns a type that represents an unknown type that extends {@code bound}. For example, if
+   * {@code bound} is {@code CharSequence.class}, this returns {@code ? extends CharSequence}. If
+   * {@code bound} is {@code Object.class}, this returns {@code ?}, which is shorthand for {@code
+   * ? extends Object}.
+   */
+  public static WildcardTypeName subtypeOf(TypeName upperBound) {
+    return new WildcardTypeName(Collections.singletonList(upperBound), Collections.emptyList());
+  }
+
+  public static WildcardTypeName subtypeOf(Type upperBound) {
+    return subtypeOf(TypeName.get(upperBound));
+  }
+
+  /**
+   * Returns a type that represents an unknown supertype of {@code bound}. For example, if {@code
+   * bound} is {@code String.class}, this returns {@code ? super String}.
+   */
+  public static WildcardTypeName supertypeOf(TypeName lowerBound) {
+    return new WildcardTypeName(Collections.singletonList(OBJECT),
+        Collections.singletonList(lowerBound));
+  }
+
+  public static WildcardTypeName supertypeOf(Type lowerBound) {
+    return supertypeOf(TypeName.get(lowerBound));
+  }
+
+  public static TypeName get(javax.lang.model.type.WildcardType mirror) {
+    return get(mirror, new LinkedHashMap<>());
+  }
+
+  static TypeName get(
+      javax.lang.model.type.WildcardType mirror,
+      Map<TypeParameterElement, TypeVariableName> typeVariables) {
+    TypeMirror extendsBound = mirror.getExtendsBound();
+    if (extendsBound == null) {
+      TypeMirror superBound = mirror.getSuperBound();
+      if (superBound == null) {
+        return subtypeOf(Object.class);
+      } else {
+        return supertypeOf(TypeName.get(superBound, typeVariables));
+      }
+    } else {
+      return subtypeOf(TypeName.get(extendsBound, typeVariables));
+    }
+  }
+
+  public static TypeName get(WildcardType wildcardName) {
+    return get(wildcardName, new LinkedHashMap<>());
+  }
+
+  static TypeName get(WildcardType wildcardName, Map<Type, TypeVariableName> map) {
+    return new WildcardTypeName(
+        list(wildcardName.getUpperBounds(), map),
+        list(wildcardName.getLowerBounds(), map));
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/AbstractTypesTest.java b/src/test/java/com/squareup/javapoet/AbstractTypesTest.java
new file mode 100644
index 0000000..86d9cbc
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/AbstractTypesTest.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+import static javax.lang.model.util.ElementFilter.fieldsIn;
+import static org.junit.Assert.*;
+
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVisitor;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.JavaFileObject;
+
+import org.junit.Test;
+
+public abstract class AbstractTypesTest {
+  protected abstract Elements getElements();
+  protected abstract Types getTypes();
+
+  private TypeElement getElement(Class<?> clazz) {
+    return getElements().getTypeElement(clazz.getCanonicalName());
+  }
+
+  private TypeMirror getMirror(Class<?> clazz) {
+    return getElement(clazz).asType();
+  }
+
+  @Test public void getBasicTypeMirror() {
+    assertThat(TypeName.get(getMirror(Object.class)))
+        .isEqualTo(ClassName.get(Object.class));
+    assertThat(TypeName.get(getMirror(Charset.class)))
+        .isEqualTo(ClassName.get(Charset.class));
+    assertThat(TypeName.get(getMirror(AbstractTypesTest.class)))
+        .isEqualTo(ClassName.get(AbstractTypesTest.class));
+  }
+
+  @Test public void getParameterizedTypeMirror() {
+    DeclaredType setType =
+        getTypes().getDeclaredType(getElement(Set.class), getMirror(Object.class));
+    assertThat(TypeName.get(setType))
+        .isEqualTo(ParameterizedTypeName.get(ClassName.get(Set.class), ClassName.OBJECT));
+  }
+
+  @Test public void errorTypes() {
+    JavaFileObject hasErrorTypes =
+        JavaFileObjects.forSourceLines(
+            "com.squareup.tacos.ErrorTypes",
+            "package com.squareup.tacos;",
+            "",
+            "@SuppressWarnings(\"hook-into-compiler\")",
+            "class ErrorTypes {",
+            "  Tacos tacos;",
+            "  Ingredients.Guacamole guacamole;",
+            "}");
+    Compilation compilation = javac().withProcessors(new AbstractProcessor() {
+      @Override
+      public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
+        TypeElement classFile =
+            processingEnv.getElementUtils().getTypeElement("com.squareup.tacos.ErrorTypes");
+        List<VariableElement> fields = fieldsIn(classFile.getEnclosedElements());
+        ErrorType topLevel = (ErrorType) fields.get(0).asType();
+        ErrorType member = (ErrorType) fields.get(1).asType();
+
+        assertThat(TypeName.get(topLevel)).isEqualTo(ClassName.get("", "Tacos"));
+        assertThat(TypeName.get(member)).isEqualTo(ClassName.get("Ingredients", "Guacamole"));
+        return false;
+      }
+
+      @Override
+      public Set<String> getSupportedAnnotationTypes() {
+        return Collections.singleton("*");
+      }
+    }).compile(hasErrorTypes);
+
+    assertThat(compilation).failed();
+  }
+
+  static class Parameterized<
+      Simple,
+      ExtendsClass extends Number,
+      ExtendsInterface extends Runnable,
+      ExtendsTypeVariable extends Simple,
+      Intersection extends Number & Runnable,
+      IntersectionOfInterfaces extends Runnable & Serializable> {}
+
+  @Test public void getTypeVariableTypeMirror() {
+    List<? extends TypeParameterElement> typeVariables =
+        getElement(Parameterized.class).getTypeParameters();
+
+    // Members of converted types use ClassName and not Class<?>.
+    ClassName number = ClassName.get(Number.class);
+    ClassName runnable = ClassName.get(Runnable.class);
+    ClassName serializable = ClassName.get(Serializable.class);
+
+    assertThat(TypeName.get(typeVariables.get(0).asType()))
+        .isEqualTo(TypeVariableName.get("Simple"));
+    assertThat(TypeName.get(typeVariables.get(1).asType()))
+        .isEqualTo(TypeVariableName.get("ExtendsClass", number));
+    assertThat(TypeName.get(typeVariables.get(2).asType()))
+        .isEqualTo(TypeVariableName.get("ExtendsInterface", runnable));
+    assertThat(TypeName.get(typeVariables.get(3).asType()))
+        .isEqualTo(TypeVariableName.get("ExtendsTypeVariable", TypeVariableName.get("Simple")));
+    assertThat(TypeName.get(typeVariables.get(4).asType()))
+        .isEqualTo(TypeVariableName.get("Intersection", number, runnable));
+    assertThat(TypeName.get(typeVariables.get(5).asType()))
+        .isEqualTo(TypeVariableName.get("IntersectionOfInterfaces", runnable, serializable));
+    assertThat(((TypeVariableName) TypeName.get(typeVariables.get(4).asType())).bounds)
+        .containsExactly(number, runnable);
+  }
+
+  static class Recursive<T extends Map<List<T>, Set<T[]>>> {}
+
+  @Test
+  public void getTypeVariableTypeMirrorRecursive() {
+    TypeMirror typeMirror = getElement(Recursive.class).asType();
+    ParameterizedTypeName typeName = (ParameterizedTypeName) TypeName.get(typeMirror);
+    String className = Recursive.class.getCanonicalName();
+    assertThat(typeName.toString()).isEqualTo(className + "<T>");
+
+    TypeVariableName typeVariableName = (TypeVariableName) typeName.typeArguments.get(0);
+
+    try {
+      typeVariableName.bounds.set(0, null);
+      fail("Expected UnsupportedOperationException");
+    } catch (UnsupportedOperationException expected) {
+    }
+
+    assertThat(typeVariableName.toString()).isEqualTo("T");
+    assertThat(typeVariableName.bounds.toString())
+        .isEqualTo("[java.util.Map<java.util.List<T>, java.util.Set<T[]>>]");
+  }
+
+  @Test public void getPrimitiveTypeMirror() {
+    assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.BOOLEAN)))
+        .isEqualTo(TypeName.BOOLEAN);
+    assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.BYTE)))
+        .isEqualTo(TypeName.BYTE);
+    assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.SHORT)))
+        .isEqualTo(TypeName.SHORT);
+    assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.INT)))
+        .isEqualTo(TypeName.INT);
+    assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.LONG)))
+        .isEqualTo(TypeName.LONG);
+    assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.CHAR)))
+        .isEqualTo(TypeName.CHAR);
+    assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.FLOAT)))
+        .isEqualTo(TypeName.FLOAT);
+    assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.DOUBLE)))
+        .isEqualTo(TypeName.DOUBLE);
+  }
+
+  @Test public void getArrayTypeMirror() {
+    assertThat(TypeName.get(getTypes().getArrayType(getMirror(Object.class))))
+        .isEqualTo(ArrayTypeName.of(ClassName.OBJECT));
+  }
+
+  @Test public void getVoidTypeMirror() {
+    assertThat(TypeName.get(getTypes().getNoType(TypeKind.VOID)))
+        .isEqualTo(TypeName.VOID);
+  }
+
+  @Test public void getNullTypeMirror() {
+    try {
+      TypeName.get(getTypes().getNullType());
+      fail();
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  @Test public void parameterizedType() throws Exception {
+    ParameterizedTypeName type = ParameterizedTypeName.get(Map.class, String.class, Long.class);
+    assertThat(type.toString()).isEqualTo("java.util.Map<java.lang.String, java.lang.Long>");
+  }
+
+  @Test public void arrayType() throws Exception {
+    ArrayTypeName type = ArrayTypeName.of(String.class);
+    assertThat(type.toString()).isEqualTo("java.lang.String[]");
+  }
+
+  @Test public void wildcardExtendsType() throws Exception {
+    WildcardTypeName type = WildcardTypeName.subtypeOf(CharSequence.class);
+    assertThat(type.toString()).isEqualTo("? extends java.lang.CharSequence");
+  }
+
+  @Test public void wildcardExtendsObject() throws Exception {
+    WildcardTypeName type = WildcardTypeName.subtypeOf(Object.class);
+    assertThat(type.toString()).isEqualTo("?");
+  }
+
+  @Test public void wildcardSuperType() throws Exception {
+    WildcardTypeName type = WildcardTypeName.supertypeOf(String.class);
+    assertThat(type.toString()).isEqualTo("? super java.lang.String");
+  }
+
+  @Test public void wildcardMirrorNoBounds() throws Exception {
+    WildcardType wildcard = getTypes().getWildcardType(null, null);
+    TypeName type = TypeName.get(wildcard);
+    assertThat(type.toString()).isEqualTo("?");
+  }
+
+  @Test public void wildcardMirrorExtendsType() throws Exception {
+    Types types = getTypes();
+    Elements elements = getElements();
+    TypeMirror charSequence = elements.getTypeElement(CharSequence.class.getName()).asType();
+    WildcardType wildcard = types.getWildcardType(charSequence, null);
+    TypeName type = TypeName.get(wildcard);
+    assertThat(type.toString()).isEqualTo("? extends java.lang.CharSequence");
+  }
+
+  @Test public void wildcardMirrorSuperType() throws Exception {
+    Types types = getTypes();
+    Elements elements = getElements();
+    TypeMirror string = elements.getTypeElement(String.class.getName()).asType();
+    WildcardType wildcard = types.getWildcardType(null, string);
+    TypeName type = TypeName.get(wildcard);
+    assertThat(type.toString()).isEqualTo("? super java.lang.String");
+  }
+
+  @Test public void typeVariable() throws Exception {
+    TypeVariableName type = TypeVariableName.get("T", CharSequence.class);
+    assertThat(type.toString()).isEqualTo("T"); // (Bounds are only emitted in declaration.)
+  }
+
+  @Test public void box() throws Exception {
+    assertThat(TypeName.INT.box()).isEqualTo(ClassName.get(Integer.class));
+    assertThat(TypeName.VOID.box()).isEqualTo(ClassName.get(Void.class));
+    assertThat(ClassName.get(Integer.class).box()).isEqualTo(ClassName.get(Integer.class));
+    assertThat(ClassName.get(Void.class).box()).isEqualTo(ClassName.get(Void.class));
+    assertThat(TypeName.OBJECT.box()).isEqualTo(TypeName.OBJECT);
+    assertThat(ClassName.get(String.class).box()).isEqualTo(ClassName.get(String.class));
+  }
+
+  @Test public void unbox() throws Exception {
+    assertThat(TypeName.INT).isEqualTo(TypeName.INT.unbox());
+    assertThat(TypeName.VOID).isEqualTo(TypeName.VOID.unbox());
+    assertThat(ClassName.get(Integer.class).unbox()).isEqualTo(TypeName.INT.unbox());
+    assertThat(ClassName.get(Void.class).unbox()).isEqualTo(TypeName.VOID.unbox());
+    try {
+      TypeName.OBJECT.unbox();
+      fail();
+    } catch (UnsupportedOperationException expected) {
+    }
+    try {
+      ClassName.get(String.class).unbox();
+      fail();
+    } catch (UnsupportedOperationException expected) {
+    }
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/AnnotatedTypeNameTest.java b/src/test/java/com/squareup/javapoet/AnnotatedTypeNameTest.java
new file mode 100644
index 0000000..42734ff
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/AnnotatedTypeNameTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+public class AnnotatedTypeNameTest {
+
+  private final static String NN = NeverNull.class.getCanonicalName();
+  private final AnnotationSpec NEVER_NULL = AnnotationSpec.builder(NeverNull.class).build();
+  private final static String TUA = TypeUseAnnotation.class.getCanonicalName();
+  private final AnnotationSpec TYPE_USE_ANNOTATION =
+      AnnotationSpec.builder(TypeUseAnnotation.class).build();
+
+  @Target(ElementType.TYPE_USE)
+  public @interface NeverNull {}
+
+  @Target(ElementType.TYPE_USE)
+  public @interface TypeUseAnnotation {}
+
+
+  @Test(expected=NullPointerException.class) public void nullAnnotationArray() {
+    TypeName.BOOLEAN.annotated((AnnotationSpec[]) null);
+  }
+
+  @Test(expected=NullPointerException.class) public void nullAnnotationList() {
+    TypeName.DOUBLE.annotated((List<AnnotationSpec>) null);
+  }
+
+  @Test public void annotated() {
+    TypeName simpleString = TypeName.get(String.class);
+    assertFalse(simpleString.isAnnotated());
+    assertEquals(simpleString, TypeName.get(String.class));
+
+    TypeName annotated = simpleString.annotated(NEVER_NULL);
+    assertTrue(annotated.isAnnotated());
+    assertEquals(annotated, annotated.annotated());
+  }
+
+  @Test public void annotatedType() {
+    TypeName type = TypeName.get(String.class);
+    TypeName actual = type.annotated(TYPE_USE_ANNOTATION);
+    assertThat(actual.toString()).isEqualTo("java.lang. @" + TUA + " String");
+  }
+
+  @Test public void annotatedTwice() {
+    TypeName type = TypeName.get(String.class);
+    TypeName actual =
+        type.annotated(NEVER_NULL)
+            .annotated(TYPE_USE_ANNOTATION);
+    assertThat(actual.toString())
+        .isEqualTo("java.lang. @" + NN + " @" + TUA + " String");
+  }
+
+  @Test public void annotatedParameterizedType() {
+    TypeName type = ParameterizedTypeName.get(List.class, String.class);
+    TypeName actual = type.annotated(TYPE_USE_ANNOTATION);
+    assertThat(actual.toString()).isEqualTo("java.util. @" + TUA + " List<java.lang.String>");
+  }
+
+  @Test public void annotatedArgumentOfParameterizedType() {
+    TypeName type = TypeName.get(String.class).annotated(TYPE_USE_ANNOTATION);
+    TypeName actual = ParameterizedTypeName.get(ClassName.get(List.class), type);
+    assertThat(actual.toString()).isEqualTo("java.util.List<java.lang. @" + TUA + " String>");
+  }
+
+  @Test public void annotatedWildcardTypeNameWithSuper() {
+    TypeName type = TypeName.get(String.class).annotated(TYPE_USE_ANNOTATION);
+    TypeName actual = WildcardTypeName.supertypeOf(type);
+    assertThat(actual.toString()).isEqualTo("? super java.lang. @" + TUA + " String");
+  }
+
+  @Test public void annotatedWildcardTypeNameWithExtends() {
+    TypeName type = TypeName.get(String.class).annotated(TYPE_USE_ANNOTATION);
+    TypeName actual = WildcardTypeName.subtypeOf(type);
+    assertThat(actual.toString()).isEqualTo("? extends java.lang. @" + TUA + " String");
+  }
+
+  @Test public void annotatedEquivalence() {
+    annotatedEquivalence(TypeName.VOID);
+    annotatedEquivalence(ArrayTypeName.get(Object[].class));
+    annotatedEquivalence(ClassName.get(Object.class));
+    annotatedEquivalence(ParameterizedTypeName.get(List.class, Object.class));
+    annotatedEquivalence(TypeVariableName.get(Object.class));
+    annotatedEquivalence(WildcardTypeName.get(Object.class));
+  }
+
+  private void annotatedEquivalence(TypeName type) {
+    assertFalse(type.isAnnotated());
+    assertEquals(type, type);
+    assertEquals(type.annotated(TYPE_USE_ANNOTATION), type.annotated(TYPE_USE_ANNOTATION));
+    assertNotEquals(type, type.annotated(TYPE_USE_ANNOTATION));
+    assertEquals(type.hashCode(), type.hashCode());
+    assertEquals(type.annotated(TYPE_USE_ANNOTATION).hashCode(),
+        type.annotated(TYPE_USE_ANNOTATION).hashCode());
+    assertNotEquals(type.hashCode(), type.annotated(TYPE_USE_ANNOTATION).hashCode());
+  }
+
+  // https://github.com/square/javapoet/issues/431
+  @Test public void annotatedNestedType() {
+    TypeName type = TypeName.get(Map.Entry.class).annotated(TYPE_USE_ANNOTATION);
+    assertThat(type.toString()).isEqualTo("java.util.Map. @" + TUA + " Entry");
+  }
+
+  @Test public void annotatedEnclosingAndNestedType() {
+    TypeName type = ((ClassName) TypeName.get(Map.class).annotated(TYPE_USE_ANNOTATION))
+        .nestedClass("Entry").annotated(TYPE_USE_ANNOTATION);
+    assertThat(type.toString()).isEqualTo("java.util. @" + TUA + " Map. @" + TUA + " Entry");
+  }
+
+  // https://github.com/square/javapoet/issues/431
+  @Test public void annotatedNestedParameterizedType() {
+    TypeName type = ParameterizedTypeName.get(Map.Entry.class, Byte.class, Byte.class)
+        .annotated(TYPE_USE_ANNOTATION);
+    assertThat(type.toString())
+        .isEqualTo("java.util.Map. @" + TUA + " Entry<java.lang.Byte, java.lang.Byte>");
+  }
+
+  @Test public void withoutAnnotationsOnAnnotatedEnclosingAndNestedType() {
+    TypeName type = ((ClassName) TypeName.get(Map.class).annotated(TYPE_USE_ANNOTATION))
+        .nestedClass("Entry").annotated(TYPE_USE_ANNOTATION);
+    assertThat(type.isAnnotated()).isTrue();
+    assertThat(type.withoutAnnotations()).isEqualTo(TypeName.get(Map.Entry.class));
+  }
+
+  @Test public void withoutAnnotationsOnAnnotatedEnclosingType() {
+    TypeName type = ((ClassName) TypeName.get(Map.class).annotated(TYPE_USE_ANNOTATION))
+        .nestedClass("Entry");
+    assertThat(type.isAnnotated()).isTrue();
+    assertThat(type.withoutAnnotations()).isEqualTo(TypeName.get(Map.Entry.class));
+  }
+
+  @Test public void withoutAnnotationsOnAnnotatedNestedType() {
+    TypeName type = ((ClassName) TypeName.get(Map.class))
+        .nestedClass("Entry").annotated(TYPE_USE_ANNOTATION);
+    assertThat(type.isAnnotated()).isTrue();
+    assertThat(type.withoutAnnotations()).isEqualTo(TypeName.get(Map.Entry.class));
+  }
+
+  // https://github.com/square/javapoet/issues/614
+   @Test public void annotatedArrayType() {
+    TypeName type = ArrayTypeName.of(ClassName.get(Object.class)).annotated(TYPE_USE_ANNOTATION);
+    assertThat(type.toString()).isEqualTo("java.lang.Object @" + TUA + " []");
+  }
+
+  @Test public void annotatedArrayElementType() {
+    TypeName type = ArrayTypeName.of(ClassName.get(Object.class).annotated(TYPE_USE_ANNOTATION));
+    assertThat(type.toString()).isEqualTo("java.lang. @" + TUA + " Object[]");
+  }
+
+  // https://github.com/square/javapoet/issues/614
+  @Test public void annotatedOuterMultidimensionalArrayType() {
+    TypeName type = ArrayTypeName.of(ArrayTypeName.of(ClassName.get(Object.class)))
+        .annotated(TYPE_USE_ANNOTATION);
+    assertThat(type.toString()).isEqualTo("java.lang.Object @" + TUA + " [][]");
+  }
+
+  // https://github.com/square/javapoet/issues/614
+  @Test public void annotatedInnerMultidimensionalArrayType() {
+    TypeName type = ArrayTypeName.of(ArrayTypeName.of(ClassName.get(Object.class))
+        .annotated(TYPE_USE_ANNOTATION));
+    assertThat(type.toString()).isEqualTo("java.lang.Object[] @" + TUA + " []");
+  }
+
+  // https://github.com/square/javapoet/issues/614
+  @Test public void annotatedArrayTypeVarargsParameter() {
+    TypeName type = ArrayTypeName.of(ArrayTypeName.of(ClassName.get(Object.class)))
+        .annotated(TYPE_USE_ANNOTATION);
+    MethodSpec varargsMethod = MethodSpec.methodBuilder("m")
+        .addParameter(
+            ParameterSpec.builder(type, "p")
+                .build())
+        .varargs()
+        .build();
+    assertThat(varargsMethod.toString()).isEqualTo(""
+        + "void m(java.lang.Object @" + TUA + " []... p) {\n"
+        + "}\n");
+  }
+
+  // https://github.com/square/javapoet/issues/614
+  @Test public void annotatedArrayTypeInVarargsParameter() {
+    TypeName type = ArrayTypeName.of(ArrayTypeName.of(ClassName.get(Object.class))
+        .annotated(TYPE_USE_ANNOTATION));
+    MethodSpec varargsMethod = MethodSpec.methodBuilder("m")
+        .addParameter(
+            ParameterSpec.builder(type, "p")
+                .build())
+        .varargs()
+        .build();
+    assertThat(varargsMethod.toString()).isEqualTo(""
+        + "void m(java.lang.Object[] @" + TUA + " ... p) {\n"
+        + "}\n");
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/AnnotationSpecTest.java b/src/test/java/com/squareup/javapoet/AnnotationSpecTest.java
new file mode 100644
index 0000000..49606c7
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/AnnotationSpecTest.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import com.google.testing.compile.CompilationRule;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import javax.lang.model.element.TypeElement;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+public final class AnnotationSpecTest {
+
+  @Retention(RetentionPolicy.RUNTIME)
+  public @interface AnnotationA {
+  }
+
+  @Inherited
+  @Retention(RetentionPolicy.RUNTIME)
+  public @interface AnnotationB {
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  public @interface AnnotationC {
+    String value();
+  }
+
+  public enum Breakfast {
+    WAFFLES, PANCAKES;
+    public String toString() { return name() + " with cherries!"; };
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  public @interface HasDefaultsAnnotation {
+
+    byte a() default 5;
+
+    short b() default 6;
+
+    int c() default 7;
+
+    long d() default 8;
+
+    float e() default 9.0f;
+
+    double f() default 10.0;
+
+    char[] g() default {0, 0xCAFE, 'z', '€', 'ℕ', '"', '\'', '\t', '\n'};
+
+    boolean h() default true;
+
+    Breakfast i() default Breakfast.WAFFLES;
+
+    AnnotationA j() default @AnnotationA();
+
+    String k() default "maple";
+
+    Class<? extends Annotation> l() default AnnotationB.class;
+
+    int[] m() default {1, 2, 3};
+
+    Breakfast[] n() default {Breakfast.WAFFLES, Breakfast.PANCAKES};
+
+    Breakfast o();
+
+    int p();
+
+    AnnotationC q() default @AnnotationC("foo");
+
+    Class<? extends Number>[] r() default {Byte.class, Short.class, Integer.class, Long.class};
+
+  }
+
+  @HasDefaultsAnnotation(
+      o = Breakfast.PANCAKES,
+      p = 1701,
+      f = 11.1,
+      m = {9, 8, 1},
+      l = Override.class,
+      j = @AnnotationA,
+      q = @AnnotationC("bar"),
+      r = {Float.class, Double.class})
+  public class IsAnnotated {
+    // empty
+  }
+
+  @Rule public final CompilationRule compilation = new CompilationRule();
+
+  @Test public void equalsAndHashCode() {
+    AnnotationSpec a = AnnotationSpec.builder(AnnotationC.class).build();
+    AnnotationSpec b = AnnotationSpec.builder(AnnotationC.class).build();
+    assertThat(a.equals(b)).isTrue();
+    assertThat(a.hashCode()).isEqualTo(b.hashCode());
+    a = AnnotationSpec.builder(AnnotationC.class).addMember("value", "$S", "123").build();
+    b = AnnotationSpec.builder(AnnotationC.class).addMember("value", "$S", "123").build();
+    assertThat(a.equals(b)).isTrue();
+    assertThat(a.hashCode()).isEqualTo(b.hashCode());
+  }
+
+  @Test public void defaultAnnotation() {
+    String name = IsAnnotated.class.getCanonicalName();
+    TypeElement element = compilation.getElements().getTypeElement(name);
+    AnnotationSpec annotation = AnnotationSpec.get(element.getAnnotationMirrors().get(0));
+
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addAnnotation(annotation)
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import com.squareup.javapoet.AnnotationSpecTest;\n"
+        + "import java.lang.Double;\n"
+        + "import java.lang.Float;\n"
+        + "import java.lang.Override;\n"
+        + "\n"
+        + "@AnnotationSpecTest.HasDefaultsAnnotation(\n"
+        + "    o = AnnotationSpecTest.Breakfast.PANCAKES,\n"
+        + "    p = 1701,\n"
+        + "    f = 11.1,\n"
+        + "    m = {\n"
+        + "        9,\n"
+        + "        8,\n"
+        + "        1\n"
+        + "    },\n"
+        + "    l = Override.class,\n"
+        + "    j = @AnnotationSpecTest.AnnotationA,\n"
+        + "    q = @AnnotationSpecTest.AnnotationC(\"bar\"),\n"
+        + "    r = {\n"
+        + "        Float.class,\n"
+        + "        Double.class\n"
+        + "    }\n"
+        + ")\n"
+        + "class Taco {\n"
+        + "}\n");
+  }
+
+  @Test public void defaultAnnotationWithImport() {
+    String name = IsAnnotated.class.getCanonicalName();
+    TypeElement element = compilation.getElements().getTypeElement(name);
+    AnnotationSpec annotation = AnnotationSpec.get(element.getAnnotationMirrors().get(0));
+    TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(IsAnnotated.class.getSimpleName());
+    typeBuilder.addAnnotation(annotation);
+    JavaFile file = JavaFile.builder("com.squareup.javapoet", typeBuilder.build()).build();
+    assertThat(file.toString()).isEqualTo(
+        "package com.squareup.javapoet;\n"
+            + "\n"
+            + "import java.lang.Double;\n"
+            + "import java.lang.Float;\n"
+            + "import java.lang.Override;\n"
+            + "\n"
+            + "@AnnotationSpecTest.HasDefaultsAnnotation(\n"
+            + "    o = AnnotationSpecTest.Breakfast.PANCAKES,\n"
+            + "    p = 1701,\n"
+            + "    f = 11.1,\n"
+            + "    m = {\n"
+            + "        9,\n"
+            + "        8,\n"
+            + "        1\n"
+            + "    },\n"
+            + "    l = Override.class,\n"
+            + "    j = @AnnotationSpecTest.AnnotationA,\n"
+            + "    q = @AnnotationSpecTest.AnnotationC(\"bar\"),\n"
+            + "    r = {\n"
+            + "        Float.class,\n"
+            + "        Double.class\n"
+            + "    }\n"
+            + ")\n"
+            + "class IsAnnotated {\n"
+            + "}\n"
+    );
+  }
+
+  @Test public void emptyArray() {
+    AnnotationSpec.Builder builder = AnnotationSpec.builder(HasDefaultsAnnotation.class);
+    builder.addMember("n", "$L", "{}");
+    assertThat(builder.build().toString()).isEqualTo(
+        "@com.squareup.javapoet.AnnotationSpecTest.HasDefaultsAnnotation(" + "n = {}" + ")");
+    builder.addMember("m", "$L", "{}");
+    assertThat(builder.build().toString())
+        .isEqualTo(
+            "@com.squareup.javapoet.AnnotationSpecTest.HasDefaultsAnnotation("
+                + "n = {}, m = {}"
+                + ")");
+  }
+
+  @Test public void dynamicArrayOfEnumConstants() {
+    AnnotationSpec.Builder builder = AnnotationSpec.builder(HasDefaultsAnnotation.class);
+    builder.addMember("n", "$T.$L", Breakfast.class, Breakfast.PANCAKES.name());
+    assertThat(builder.build().toString()).isEqualTo(
+        "@com.squareup.javapoet.AnnotationSpecTest.HasDefaultsAnnotation("
+            + "n = com.squareup.javapoet.AnnotationSpecTest.Breakfast.PANCAKES"
+            + ")");
+
+    // builder = AnnotationSpec.builder(HasDefaultsAnnotation.class);
+    builder.addMember("n", "$T.$L", Breakfast.class, Breakfast.WAFFLES.name());
+    builder.addMember("n", "$T.$L", Breakfast.class, Breakfast.PANCAKES.name());
+    assertThat(builder.build().toString()).isEqualTo(
+        "@com.squareup.javapoet.AnnotationSpecTest.HasDefaultsAnnotation("
+            + "n = {"
+            + "com.squareup.javapoet.AnnotationSpecTest.Breakfast.PANCAKES"
+            + ", com.squareup.javapoet.AnnotationSpecTest.Breakfast.WAFFLES"
+            + ", com.squareup.javapoet.AnnotationSpecTest.Breakfast.PANCAKES"
+            + "})");
+
+    builder = builder.build().toBuilder(); // idempotent
+    assertThat(builder.build().toString()).isEqualTo(
+        "@com.squareup.javapoet.AnnotationSpecTest.HasDefaultsAnnotation("
+            + "n = {"
+            + "com.squareup.javapoet.AnnotationSpecTest.Breakfast.PANCAKES"
+            + ", com.squareup.javapoet.AnnotationSpecTest.Breakfast.WAFFLES"
+            + ", com.squareup.javapoet.AnnotationSpecTest.Breakfast.PANCAKES"
+            + "})");
+
+    builder.addMember("n", "$T.$L", Breakfast.class, Breakfast.WAFFLES.name());
+    assertThat(builder.build().toString()).isEqualTo(
+        "@com.squareup.javapoet.AnnotationSpecTest.HasDefaultsAnnotation("
+            + "n = {"
+            + "com.squareup.javapoet.AnnotationSpecTest.Breakfast.PANCAKES"
+            + ", com.squareup.javapoet.AnnotationSpecTest.Breakfast.WAFFLES"
+            + ", com.squareup.javapoet.AnnotationSpecTest.Breakfast.PANCAKES"
+            + ", com.squareup.javapoet.AnnotationSpecTest.Breakfast.WAFFLES"
+            + "})");
+  }
+
+  @Test public void defaultAnnotationToBuilder() {
+    String name = IsAnnotated.class.getCanonicalName();
+    TypeElement element = compilation.getElements().getTypeElement(name);
+    AnnotationSpec.Builder builder = AnnotationSpec.get(element.getAnnotationMirrors().get(0))
+        .toBuilder();
+    builder.addMember("m", "$L", 123);
+    assertThat(builder.build().toString()).isEqualTo(
+        "@com.squareup.javapoet.AnnotationSpecTest.HasDefaultsAnnotation("
+            + "o = com.squareup.javapoet.AnnotationSpecTest.Breakfast.PANCAKES"
+            + ", p = 1701"
+            + ", f = 11.1"
+            + ", m = {9, 8, 1, 123}"
+            + ", l = java.lang.Override.class"
+            + ", j = @com.squareup.javapoet.AnnotationSpecTest.AnnotationA"
+            + ", q = @com.squareup.javapoet.AnnotationSpecTest.AnnotationC(\"bar\")"
+            + ", r = {java.lang.Float.class, java.lang.Double.class}"
+            + ")");
+  }
+
+  @Test public void reflectAnnotation() {
+    HasDefaultsAnnotation annotation = IsAnnotated.class.getAnnotation(HasDefaultsAnnotation.class);
+    AnnotationSpec spec = AnnotationSpec.get(annotation);
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addAnnotation(spec)
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import com.squareup.javapoet.AnnotationSpecTest;\n"
+        + "import java.lang.Double;\n"
+        + "import java.lang.Float;\n"
+        + "import java.lang.Override;\n"
+        + "\n"
+        + "@AnnotationSpecTest.HasDefaultsAnnotation(\n"
+        + "    f = 11.1,\n"
+        + "    l = Override.class,\n"
+        + "    m = {\n"
+        + "        9,\n"
+        + "        8,\n"
+        + "        1\n"
+        + "    },\n"
+        + "    o = AnnotationSpecTest.Breakfast.PANCAKES,\n"
+        + "    p = 1701,\n"
+        + "    q = @AnnotationSpecTest.AnnotationC(\"bar\"),\n"
+        + "    r = {\n"
+        + "        Float.class,\n"
+        + "        Double.class\n"
+        + "    }\n"
+        + ")\n"
+        + "class Taco {\n"
+        + "}\n");
+  }
+
+  @Test public void reflectAnnotationWithDefaults() {
+    HasDefaultsAnnotation annotation = IsAnnotated.class.getAnnotation(HasDefaultsAnnotation.class);
+    AnnotationSpec spec = AnnotationSpec.get(annotation, true);
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addAnnotation(spec)
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import com.squareup.javapoet.AnnotationSpecTest;\n"
+        + "import java.lang.Double;\n"
+        + "import java.lang.Float;\n"
+        + "import java.lang.Override;\n"
+        + "\n"
+        + "@AnnotationSpecTest.HasDefaultsAnnotation(\n"
+        + "    a = 5,\n"
+        + "    b = 6,\n"
+        + "    c = 7,\n"
+        + "    d = 8,\n"
+        + "    e = 9.0f,\n"
+        + "    f = 11.1,\n"
+        + "    g = {\n"
+        + "        '\\u0000',\n"
+        + "        '쫾',\n"
+        + "        'z',\n"
+        + "        '€',\n"
+        + "        'ℕ',\n"
+        + "        '\"',\n"
+        + "        '\\'',\n"
+        + "        '\\t',\n"
+        + "        '\\n'\n"
+        + "    },\n"
+        + "    h = true,\n"
+        + "    i = AnnotationSpecTest.Breakfast.WAFFLES,\n"
+        + "    j = @AnnotationSpecTest.AnnotationA,\n"
+        + "    k = \"maple\",\n"
+        + "    l = Override.class,\n"
+        + "    m = {\n"
+        + "        9,\n"
+        + "        8,\n"
+        + "        1\n"
+        + "    },\n"
+        + "    n = {\n"
+        + "        AnnotationSpecTest.Breakfast.WAFFLES,\n"
+        + "        AnnotationSpecTest.Breakfast.PANCAKES\n"
+        + "    },\n"
+        + "    o = AnnotationSpecTest.Breakfast.PANCAKES,\n"
+        + "    p = 1701,\n"
+        + "    q = @AnnotationSpecTest.AnnotationC(\"bar\"),\n"
+        + "    r = {\n"
+        + "        Float.class,\n"
+        + "        Double.class\n"
+        + "    }\n"
+        + ")\n"
+        + "class Taco {\n"
+        + "}\n");
+  }
+
+  @Test public void disallowsNullMemberName() {
+    AnnotationSpec.Builder builder = AnnotationSpec.builder(HasDefaultsAnnotation.class);
+    try {
+      AnnotationSpec.Builder $L = builder.addMember(null, "$L", "");
+      fail($L.build().toString());
+    } catch (NullPointerException e) {
+      assertThat(e).hasMessageThat().isEqualTo("name == null");
+    }
+  }
+
+  @Test public void requiresValidMemberName() {
+    AnnotationSpec.Builder builder = AnnotationSpec.builder(HasDefaultsAnnotation.class);
+    try {
+      AnnotationSpec.Builder $L = builder.addMember("@", "$L", "");
+      fail($L.build().toString());
+    } catch (IllegalArgumentException e) {
+      assertThat(e).hasMessageThat().isEqualTo("not a valid name: @");
+    }
+  }
+
+  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
new file mode 100644
index 0000000..e2cc55e
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/ClassNameTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import com.google.testing.compile.CompilationRule;
+import java.util.Map;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
+
+@RunWith(JUnit4.class)
+public final class ClassNameTest {
+  @Rule public CompilationRule compilationRule = new CompilationRule();
+
+  @Test public void bestGuessForString_simpleClass() {
+    assertThat(ClassName.bestGuess(String.class.getName()))
+        .isEqualTo(ClassName.get("java.lang", "String"));
+  }
+
+  @Test public void bestGuessNonAscii() {
+    ClassName className = ClassName.bestGuess(
+        "com.\ud835\udc1andro\ud835\udc22d.\ud835\udc00ctiv\ud835\udc22ty");
+    assertEquals("com.\ud835\udc1andro\ud835\udc22d", className.packageName());
+    assertEquals("\ud835\udc00ctiv\ud835\udc22ty", className.simpleName());
+  }
+
+  static class OuterClass {
+    static class InnerClass {}
+  }
+
+  @Test public void bestGuessForString_nestedClass() {
+    assertThat(ClassName.bestGuess(Map.Entry.class.getCanonicalName()))
+        .isEqualTo(ClassName.get("java.util", "Map", "Entry"));
+    assertThat(ClassName.bestGuess(OuterClass.InnerClass.class.getCanonicalName()))
+        .isEqualTo(ClassName.get("com.squareup.javapoet",
+            "ClassNameTest", "OuterClass", "InnerClass"));
+  }
+
+  @Test public void bestGuessForString_defaultPackage() {
+    assertThat(ClassName.bestGuess("SomeClass"))
+        .isEqualTo(ClassName.get("", "SomeClass"));
+    assertThat(ClassName.bestGuess("SomeClass.Nested"))
+        .isEqualTo(ClassName.get("", "SomeClass", "Nested"));
+    assertThat(ClassName.bestGuess("SomeClass.Nested.EvenMore"))
+        .isEqualTo(ClassName.get("", "SomeClass", "Nested", "EvenMore"));
+  }
+
+  @Test public void bestGuessForString_confusingInput() {
+    assertBestGuessThrows("");
+    assertBestGuessThrows(".");
+    assertBestGuessThrows(".Map");
+    assertBestGuessThrows("java");
+    assertBestGuessThrows("java.util");
+    assertBestGuessThrows("java.util.");
+    assertBestGuessThrows("java..util.Map.Entry");
+    assertBestGuessThrows("java.util..Map.Entry");
+    assertBestGuessThrows("java.util.Map..Entry");
+    assertBestGuessThrows("com.test.$");
+    assertBestGuessThrows("com.test.LooksLikeAClass.pkg");
+    assertBestGuessThrows("!@#$gibberish%^&*");
+  }
+
+  private void assertBestGuessThrows(String s) {
+    try {
+      ClassName.bestGuess(s);
+      fail();
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  @Test public void createNestedClass() {
+    ClassName foo = ClassName.get("com.example", "Foo");
+    ClassName bar = foo.nestedClass("Bar");
+    assertThat(bar).isEqualTo(ClassName.get("com.example", "Foo", "Bar"));
+    ClassName baz = bar.nestedClass("Baz");
+    assertThat(baz).isEqualTo(ClassName.get("com.example", "Foo", "Bar", "Baz"));
+  }
+
+  static class $Outer {
+    static class $Inner {}
+  }
+
+  @Test public void classNameFromTypeElement() {
+    Elements elements = compilationRule.getElements();
+    TypeElement object = elements.getTypeElement(Object.class.getCanonicalName());
+    assertThat(ClassName.get(object).toString()).isEqualTo("java.lang.Object");
+    TypeElement outer = elements.getTypeElement($Outer.class.getCanonicalName());
+    assertThat(ClassName.get(outer).toString()).isEqualTo("com.squareup.javapoet.ClassNameTest.$Outer");
+    TypeElement inner = elements.getTypeElement($Outer.$Inner.class.getCanonicalName());
+    assertThat(ClassName.get(inner).toString()).isEqualTo("com.squareup.javapoet.ClassNameTest.$Outer.$Inner");
+  }
+
+  /**
+   * Buck builds with "source-based ABI generation" and those builds don't support
+   * {@link TypeElement#getKind()}. Test to confirm that we don't use that API.
+   */
+  @Test public void classNameFromTypeElementDoesntUseGetKind() {
+    Elements elements = compilationRule.getElements();
+    TypeElement object = elements.getTypeElement(Object.class.getCanonicalName());
+    assertThat(ClassName.get(preventGetKind(object)).toString())
+        .isEqualTo("java.lang.Object");
+    TypeElement outer = elements.getTypeElement($Outer.class.getCanonicalName());
+    assertThat(ClassName.get(preventGetKind(outer)).toString())
+        .isEqualTo("com.squareup.javapoet.ClassNameTest.$Outer");
+    TypeElement inner = elements.getTypeElement($Outer.$Inner.class.getCanonicalName());
+    assertThat(ClassName.get(preventGetKind(inner)).toString())
+        .isEqualTo("com.squareup.javapoet.ClassNameTest.$Outer.$Inner");
+  }
+
+  /** Returns a new instance like {@code object} that throws on {@code getKind()}. */
+  private TypeElement preventGetKind(TypeElement object) {
+    TypeElement spy = Mockito.spy(object);
+    when(spy.getKind()).thenThrow(new AssertionError());
+    when(spy.getEnclosingElement()).thenAnswer(invocation -> {
+      Object enclosingElement = invocation.callRealMethod();
+      return enclosingElement instanceof TypeElement
+          ? preventGetKind((TypeElement) enclosingElement)
+          : enclosingElement;
+    });
+    return spy;
+  }
+
+  @Test public void classNameFromClass() {
+    assertThat(ClassName.get(Object.class).toString())
+        .isEqualTo("java.lang.Object");
+    assertThat(ClassName.get(OuterClass.InnerClass.class).toString())
+        .isEqualTo("com.squareup.javapoet.ClassNameTest.OuterClass.InnerClass");
+    assertThat((ClassName.get(new Object() {}.getClass())).toString())
+        .isEqualTo("com.squareup.javapoet.ClassNameTest$1");
+    assertThat((ClassName.get(new Object() { Object inner = new Object() {}; }.inner.getClass())).toString())
+        .isEqualTo("com.squareup.javapoet.ClassNameTest$2$1");
+    assertThat((ClassName.get($Outer.class)).toString())
+        .isEqualTo("com.squareup.javapoet.ClassNameTest.$Outer");
+    assertThat((ClassName.get($Outer.$Inner.class)).toString())
+        .isEqualTo("com.squareup.javapoet.ClassNameTest.$Outer.$Inner");
+  }
+
+  @Test public void peerClass() {
+    assertThat(ClassName.get(Double.class).peerClass("Short"))
+        .isEqualTo(ClassName.get(Short.class));
+    assertThat(ClassName.get("", "Double").peerClass("Short"))
+        .isEqualTo(ClassName.get("", "Short"));
+    assertThat(ClassName.get("a.b", "Combo", "Taco").peerClass("Burrito"))
+        .isEqualTo(ClassName.get("a.b", "Combo", "Burrito"));
+  }
+
+  @Test public void fromClassRejectionTypes() {
+    try {
+      ClassName.get(int.class);
+      fail();
+    } catch (IllegalArgumentException ignored) {
+    }
+    try {
+      ClassName.get(void.class);
+      fail();
+    } catch (IllegalArgumentException ignored) {
+    }
+    try {
+      ClassName.get(Object[].class);
+      fail();
+    } catch (IllegalArgumentException ignored) {
+    }
+  }
+
+  @Test
+  public void reflectionName() {
+    assertEquals("java.lang.Object", TypeName.OBJECT.reflectionName());
+    assertEquals("java.lang.Thread$State", ClassName.get(Thread.State.class).reflectionName());
+    assertEquals("java.util.Map$Entry", ClassName.get(Map.Entry.class).reflectionName());
+    assertEquals("Foo", ClassName.get("", "Foo").reflectionName());
+    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());
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/CodeBlockTest.java b/src/test/java/com/squareup/javapoet/CodeBlockTest.java
new file mode 100644
index 0000000..2862809
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/CodeBlockTest.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public final class CodeBlockTest {
+  @Test public void equalsAndHashCode() {
+    CodeBlock a = CodeBlock.builder().build();
+    CodeBlock b = CodeBlock.builder().build();
+    assertThat(a.equals(b)).isTrue();
+    assertThat(a.hashCode()).isEqualTo(b.hashCode());
+    a = CodeBlock.builder().add("$L", "taco").build();
+    b = CodeBlock.builder().add("$L", "taco").build();
+    assertThat(a.equals(b)).isTrue();
+    assertThat(a.hashCode()).isEqualTo(b.hashCode());
+  }
+
+  @Test public void of() {
+    CodeBlock a = CodeBlock.of("$L taco", "delicious");
+    assertThat(a.toString()).isEqualTo("delicious taco");
+  }
+
+  @Test public void isEmpty() {
+    assertTrue(CodeBlock.builder().isEmpty());
+    assertTrue(CodeBlock.builder().add("").isEmpty());
+    assertFalse(CodeBlock.builder().add(" ").isEmpty());
+  }
+
+  @Test public void indentCannotBeIndexed() {
+    try {
+      CodeBlock.builder().add("$1>", "taco").build();
+      fail();
+    } catch (IllegalArgumentException exp) {
+      assertThat(exp)
+          .hasMessageThat()
+          .isEqualTo("$$, $>, $<, $[, $], $W, and $Z may not have an index");
+    }
+  }
+
+  @Test public void deindentCannotBeIndexed() {
+    try {
+      CodeBlock.builder().add("$1<", "taco").build();
+      fail();
+    } catch (IllegalArgumentException exp) {
+      assertThat(exp)
+          .hasMessageThat()
+          .isEqualTo("$$, $>, $<, $[, $], $W, and $Z may not have an index");
+    }
+  }
+
+  @Test public void dollarSignEscapeCannotBeIndexed() {
+    try {
+      CodeBlock.builder().add("$1$", "taco").build();
+      fail();
+    } catch (IllegalArgumentException exp) {
+      assertThat(exp)
+          .hasMessageThat()
+          .isEqualTo("$$, $>, $<, $[, $], $W, and $Z may not have an index");
+    }
+  }
+
+  @Test public void statementBeginningCannotBeIndexed() {
+    try {
+      CodeBlock.builder().add("$1[", "taco").build();
+      fail();
+    } catch (IllegalArgumentException exp) {
+      assertThat(exp)
+          .hasMessageThat()
+          .isEqualTo("$$, $>, $<, $[, $], $W, and $Z may not have an index");
+    }
+  }
+
+  @Test public void statementEndingCannotBeIndexed() {
+    try {
+      CodeBlock.builder().add("$1]", "taco").build();
+      fail();
+    } catch (IllegalArgumentException exp) {
+      assertThat(exp)
+          .hasMessageThat()
+          .isEqualTo("$$, $>, $<, $[, $], $W, and $Z may not have an index");
+    }
+  }
+
+  @Test public void nameFormatCanBeIndexed() {
+    CodeBlock block = CodeBlock.builder().add("$1N", "taco").build();
+    assertThat(block.toString()).isEqualTo("taco");
+  }
+
+  @Test public void literalFormatCanBeIndexed() {
+    CodeBlock block = CodeBlock.builder().add("$1L", "taco").build();
+    assertThat(block.toString()).isEqualTo("taco");
+  }
+
+  @Test public void stringFormatCanBeIndexed() {
+    CodeBlock block = CodeBlock.builder().add("$1S", "taco").build();
+    assertThat(block.toString()).isEqualTo("\"taco\"");
+  }
+
+  @Test public void typeFormatCanBeIndexed() {
+    CodeBlock block = CodeBlock.builder().add("$1T", String.class).build();
+    assertThat(block.toString()).isEqualTo("java.lang.String");
+  }
+
+  @Test public void simpleNamedArgument() {
+    Map<String, Object> map = new LinkedHashMap<>();
+    map.put("text", "taco");
+    CodeBlock block = CodeBlock.builder().addNamed("$text:S", map).build();
+    assertThat(block.toString()).isEqualTo("\"taco\"");
+  }
+
+  @Test public void repeatedNamedArgument() {
+    Map<String, Object> map = new LinkedHashMap<>();
+    map.put("text", "tacos");
+    CodeBlock block = CodeBlock.builder()
+        .addNamed("\"I like \" + $text:S + \". Do you like \" + $text:S + \"?\"", map)
+        .build();
+    assertThat(block.toString()).isEqualTo(
+        "\"I like \" + \"tacos\" + \". Do you like \" + \"tacos\" + \"?\"");
+  }
+
+  @Test public void namedAndNoArgFormat() {
+    Map<String, Object> map = new LinkedHashMap<>();
+    map.put("text", "tacos");
+    CodeBlock block = CodeBlock.builder()
+        .addNamed("$>\n$text:L for $$3.50", map).build();
+    assertThat(block.toString()).isEqualTo("\n  tacos for $3.50");
+  }
+
+  @Test public void missingNamedArgument() {
+    try {
+      Map<String, Object> map = new LinkedHashMap<>();
+      CodeBlock.builder().addNamed("$text:S", map).build();
+      fail();
+    } catch(IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("Missing named argument for $text");
+    }
+  }
+
+  @Test public void lowerCaseNamed() {
+    try {
+      Map<String, Object> map = new LinkedHashMap<>();
+      map.put("Text", "tacos");
+      CodeBlock block = CodeBlock.builder().addNamed("$Text:S", map).build();
+      fail();
+    } catch(IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("argument 'Text' must start with a lowercase character");
+    }
+  }
+
+  @Test public void multipleNamedArguments() {
+    Map<String, Object> map = new LinkedHashMap<>();
+    map.put("pipe", System.class);
+    map.put("text", "tacos");
+
+    CodeBlock block = CodeBlock.builder()
+        .addNamed("$pipe:T.out.println(\"Let's eat some $text:L\");", map)
+        .build();
+
+    assertThat(block.toString()).isEqualTo(
+        "java.lang.System.out.println(\"Let's eat some tacos\");");
+  }
+
+  @Test public void namedNewline() {
+    Map<String, Object> map = new LinkedHashMap<>();
+    map.put("clazz", Integer.class);
+    CodeBlock block = CodeBlock.builder().addNamed("$clazz:T\n", map).build();
+    assertThat(block.toString()).isEqualTo("java.lang.Integer\n");
+  }
+
+  @Test public void danglingNamed() {
+    Map<String, Object> map = new LinkedHashMap<>();
+    map.put("clazz", Integer.class);
+    try {
+      CodeBlock.builder().addNamed("$clazz:T$", map).build();
+      fail();
+    } catch(IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("dangling $ at end");
+    }
+  }
+
+  @Test public void indexTooHigh() {
+    try {
+      CodeBlock.builder().add("$2T", String.class).build();
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("index 2 for '$2T' not in range (received 1 arguments)");
+    }
+  }
+
+  @Test public void indexIsZero() {
+    try {
+      CodeBlock.builder().add("$0T", String.class).build();
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("index 0 for '$0T' not in range (received 1 arguments)");
+    }
+  }
+
+  @Test public void indexIsNegative() {
+    try {
+      CodeBlock.builder().add("$-1T", String.class).build();
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("invalid format string: '$-1T'");
+    }
+  }
+
+  @Test public void indexWithoutFormatType() {
+    try {
+      CodeBlock.builder().add("$1", String.class).build();
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("dangling format characters in '$1'");
+    }
+  }
+
+  @Test public void indexWithoutFormatTypeNotAtStringEnd() {
+    try {
+      CodeBlock.builder().add("$1 taco", String.class).build();
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("invalid format string: '$1 taco'");
+    }
+  }
+
+  @Test public void indexButNoArguments() {
+    try {
+      CodeBlock.builder().add("$1T").build();
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("index 1 for '$1T' not in range (received 0 arguments)");
+    }
+  }
+
+  @Test public void formatIndicatorAlone() {
+    try {
+      CodeBlock.builder().add("$", String.class).build();
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("dangling format characters in '$'");
+    }
+  }
+
+  @Test public void formatIndicatorWithoutIndexOrFormatType() {
+    try {
+      CodeBlock.builder().add("$ tacoString", String.class).build();
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("invalid format string: '$ tacoString'");
+    }
+  }
+
+  @Test public void sameIndexCanBeUsedWithDifferentFormats() {
+    CodeBlock block = CodeBlock.builder()
+        .add("$1T.out.println($1S)", ClassName.get(System.class))
+        .build();
+    assertThat(block.toString()).isEqualTo("java.lang.System.out.println(\"java.lang.System\")");
+  }
+
+  @Test public void tooManyStatementEnters() {
+    CodeBlock codeBlock = CodeBlock.builder().add("$[$[").build();
+    try {
+      // We can't report this error until rendering type because code blocks might be composed.
+      codeBlock.toString();
+      fail();
+    } catch (IllegalStateException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("statement enter $[ followed by statement enter $[");
+    }
+  }
+
+  @Test public void statementExitWithoutStatementEnter() {
+    CodeBlock codeBlock = CodeBlock.builder().add("$]").build();
+    try {
+      // We can't report this error until rendering type because code blocks might be composed.
+      codeBlock.toString();
+      fail();
+    } catch (IllegalStateException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("statement exit $] has no matching statement enter $[");
+    }
+  }
+
+  @Test public void join() {
+    List<CodeBlock> codeBlocks = new ArrayList<>();
+    codeBlocks.add(CodeBlock.of("$S", "hello"));
+    codeBlocks.add(CodeBlock.of("$T", ClassName.get("world", "World")));
+    codeBlocks.add(CodeBlock.of("need tacos"));
+
+    CodeBlock joined = CodeBlock.join(codeBlocks, " || ");
+    assertThat(joined.toString()).isEqualTo("\"hello\" || world.World || need tacos");
+  }
+
+  @Test public void joining() {
+    List<CodeBlock> codeBlocks = new ArrayList<>();
+    codeBlocks.add(CodeBlock.of("$S", "hello"));
+    codeBlocks.add(CodeBlock.of("$T", ClassName.get("world", "World")));
+    codeBlocks.add(CodeBlock.of("need tacos"));
+
+    CodeBlock joined = codeBlocks.stream().collect(CodeBlock.joining(" || "));
+    assertThat(joined.toString()).isEqualTo("\"hello\" || world.World || need tacos");
+  }
+
+  @Test public void joiningSingle() {
+    List<CodeBlock> codeBlocks = new ArrayList<>();
+    codeBlocks.add(CodeBlock.of("$S", "hello"));
+
+    CodeBlock joined = codeBlocks.stream().collect(CodeBlock.joining(" || "));
+    assertThat(joined.toString()).isEqualTo("\"hello\"");
+  }
+
+  @Test public void joiningWithPrefixAndSuffix() {
+    List<CodeBlock> codeBlocks = new ArrayList<>();
+    codeBlocks.add(CodeBlock.of("$S", "hello"));
+    codeBlocks.add(CodeBlock.of("$T", ClassName.get("world", "World")));
+    codeBlocks.add(CodeBlock.of("need tacos"));
+
+    CodeBlock joined = codeBlocks.stream().collect(CodeBlock.joining(" || ", "start {", "} end"));
+    assertThat(joined.toString()).isEqualTo("start {\"hello\" || world.World || need tacos} end");
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/FieldSpecTest.java b/src/test/java/com/squareup/javapoet/FieldSpecTest.java
new file mode 100644
index 0000000..63f7aa8
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/FieldSpecTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import javax.lang.model.element.Modifier;
+
+public class FieldSpecTest {
+  @Test public void equalsAndHashCode() {
+    FieldSpec a = FieldSpec.builder(int.class, "foo").build();
+    FieldSpec b = FieldSpec.builder(int.class, "foo").build();
+    assertThat(a.equals(b)).isTrue();
+    assertThat(a.hashCode()).isEqualTo(b.hashCode());
+    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());
+  }
+
+  @Test public void nullAnnotationsAddition() {
+    try {
+      FieldSpec.builder(int.class, "foo").addAnnotations(null);
+      fail();
+    }
+    catch (IllegalArgumentException expected) {
+      assertThat(expected.getMessage())
+          .isEqualTo("annotationSpecs == null");
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/squareup/javapoet/FileReadingTest.java b/src/test/java/com/squareup/javapoet/FileReadingTest.java
new file mode 100644
index 0000000..eb19de0
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/FileReadingTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.concurrent.Callable;
+import javax.lang.model.element.Modifier;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaCompiler.CompilationTask;
+import javax.tools.JavaFileObject;
+import javax.tools.JavaFileObject.Kind;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+@RunWith(JUnit4.class)
+public class FileReadingTest {
+  
+  // Used for storing compilation output.
+  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  @Test public void javaFileObjectUri() {
+    TypeSpec type = TypeSpec.classBuilder("Test").build();
+    assertThat(JavaFile.builder("", type).build().toJavaFileObject().toUri())
+        .isEqualTo(URI.create("Test.java"));
+    assertThat(JavaFile.builder("foo", type).build().toJavaFileObject().toUri())
+        .isEqualTo(URI.create("foo/Test.java"));
+    assertThat(JavaFile.builder("com.example", type).build().toJavaFileObject().toUri())
+        .isEqualTo(URI.create("com/example/Test.java"));
+  }
+  
+  @Test public void javaFileObjectKind() {
+    JavaFile javaFile = JavaFile.builder("", TypeSpec.classBuilder("Test").build()).build();
+    assertThat(javaFile.toJavaFileObject().getKind()).isEqualTo(Kind.SOURCE);
+  }
+  
+  @Test public void javaFileObjectCharacterContent() throws IOException {
+    TypeSpec type = TypeSpec.classBuilder("Test")
+        .addJavadoc("Pi\u00f1ata\u00a1")
+        .addMethod(MethodSpec.methodBuilder("fooBar").build())
+        .build();
+    JavaFile javaFile = JavaFile.builder("foo", type).build();
+    JavaFileObject javaFileObject = javaFile.toJavaFileObject();
+    
+    // We can never have encoding issues (everything is in process)
+    assertThat(javaFileObject.getCharContent(true)).isEqualTo(javaFile.toString());
+    assertThat(javaFileObject.getCharContent(false)).isEqualTo(javaFile.toString());
+  }
+  
+  @Test public void javaFileObjectInputStreamIsUtf8() throws IOException {
+    JavaFile javaFile = JavaFile.builder("foo", TypeSpec.classBuilder("Test").build())
+        .addFileComment("Pi\u00f1ata\u00a1")
+        .build();
+    byte[] bytes = ByteStreams.toByteArray(javaFile.toJavaFileObject().openInputStream());
+    
+    // JavaPoet always uses UTF-8.
+    assertThat(bytes).isEqualTo(javaFile.toString().getBytes(UTF_8));
+  }
+  
+  @Test public void compileJavaFile() throws Exception {
+    final String value = "Hello World!";
+    TypeSpec type = TypeSpec.classBuilder("Test")
+        .addModifiers(Modifier.PUBLIC)
+        .addSuperinterface(ParameterizedTypeName.get(Callable.class, String.class))
+        .addMethod(MethodSpec.methodBuilder("call")
+            .returns(String.class)
+            .addModifiers(Modifier.PUBLIC)
+            .addStatement("return $S", value)
+            .build())
+        .build();
+    JavaFile javaFile = JavaFile.builder("foo", type).build();
+
+    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+    DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
+    StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnosticCollector, 
+        Locale.getDefault(), UTF_8);
+    fileManager.setLocation(StandardLocation.CLASS_OUTPUT,
+        Collections.singleton(temporaryFolder.newFolder()));
+    CompilationTask task = compiler.getTask(null, 
+        fileManager,
+        diagnosticCollector,
+        Collections.emptySet(),
+        Collections.emptySet(),
+        Collections.singleton(javaFile.toJavaFileObject()));
+    
+    assertThat(task.call()).isTrue();
+    assertThat(diagnosticCollector.getDiagnostics()).isEmpty();
+
+    ClassLoader loader = fileManager.getClassLoader(StandardLocation.CLASS_OUTPUT);
+    Callable<?> test = Class.forName("foo.Test", true, loader)
+            .asSubclass(Callable.class)
+            .getDeclaredConstructor()
+            .newInstance();
+    assertThat(Callable.class.getMethod("call").invoke(test)).isEqualTo(value);
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/FileWritingTest.java b/src/test/java/com/squareup/javapoet/FileWritingTest.java
new file mode 100644
index 0000000..f817ddb
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/FileWritingTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+package com.squareup.javapoet;
+
+import com.google.common.jimfs.Configuration;
+import com.google.common.jimfs.Jimfs;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Date;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Modifier;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.fail;
+
+@RunWith(JUnit4.class)
+public final class FileWritingTest {
+  // Used for testing java.io File behavior.
+  @Rule public final TemporaryFolder tmp = new TemporaryFolder();
+
+  // Used for testing java.nio.file Path behavior.
+  private final FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
+  private final Path fsRoot = fs.getRootDirectories().iterator().next();
+
+  // Used for testing annotation processor Filer behavior.
+  private final TestFiler filer = new TestFiler(fs, fsRoot);
+
+  @Test public void pathNotDirectory() throws IOException {
+    TypeSpec type = TypeSpec.classBuilder("Test").build();
+    JavaFile javaFile = JavaFile.builder("example", type).build();
+    Path path = fs.getPath("/foo/bar");
+    Files.createDirectories(path.getParent());
+    Files.createFile(path);
+    try {
+      javaFile.writeTo(path);
+      fail();
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage()).isEqualTo("path /foo/bar exists but is not a directory.");
+    }
+  }
+
+  @Test public void fileNotDirectory() throws IOException {
+    TypeSpec type = TypeSpec.classBuilder("Test").build();
+    JavaFile javaFile = JavaFile.builder("example", type).build();
+    File file = new File(tmp.newFolder("foo"), "bar");
+    file.createNewFile();
+    try {
+      javaFile.writeTo(file);
+      fail();
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage()).isEqualTo(
+          "path " + file.getPath() + " exists but is not a directory.");
+    }
+  }
+
+  @Test public void pathDefaultPackage() throws IOException {
+    TypeSpec type = TypeSpec.classBuilder("Test").build();
+    JavaFile.builder("", type).build().writeTo(fsRoot);
+
+    Path testPath = fsRoot.resolve("Test.java");
+    assertThat(Files.exists(testPath)).isTrue();
+  }
+
+  @Test public void fileDefaultPackage() throws IOException {
+    TypeSpec type = TypeSpec.classBuilder("Test").build();
+    JavaFile.builder("", type).build().writeTo(tmp.getRoot());
+
+    File testFile = new File(tmp.getRoot(), "Test.java");
+    assertThat(testFile.exists()).isTrue();
+  }
+
+  @Test public void filerDefaultPackage() throws IOException {
+    TypeSpec type = TypeSpec.classBuilder("Test").build();
+    JavaFile.builder("", type).build().writeTo(filer);
+
+    Path testPath = fsRoot.resolve("Test.java");
+    assertThat(Files.exists(testPath)).isTrue();
+  }
+
+  @Test public void pathNestedClasses() throws IOException {
+    TypeSpec type = TypeSpec.classBuilder("Test").build();
+    JavaFile.builder("foo", type).build().writeTo(fsRoot);
+    JavaFile.builder("foo.bar", type).build().writeTo(fsRoot);
+    JavaFile.builder("foo.bar.baz", type).build().writeTo(fsRoot);
+
+    Path fooPath = fsRoot.resolve(fs.getPath("foo", "Test.java"));
+    Path barPath = fsRoot.resolve(fs.getPath("foo", "bar", "Test.java"));
+    Path bazPath = fsRoot.resolve(fs.getPath("foo", "bar", "baz", "Test.java"));
+    assertThat(Files.exists(fooPath)).isTrue();
+    assertThat(Files.exists(barPath)).isTrue();
+    assertThat(Files.exists(bazPath)).isTrue();
+  }
+
+  @Test public void fileNestedClasses() throws IOException {
+    TypeSpec type = TypeSpec.classBuilder("Test").build();
+    JavaFile.builder("foo", type).build().writeTo(tmp.getRoot());
+    JavaFile.builder("foo.bar", type).build().writeTo(tmp.getRoot());
+    JavaFile.builder("foo.bar.baz", type).build().writeTo(tmp.getRoot());
+
+    File fooDir = new File(tmp.getRoot(), "foo");
+    File fooFile = new File(fooDir, "Test.java");
+    File barDir = new File(fooDir, "bar");
+    File barFile = new File(barDir, "Test.java");
+    File bazDir = new File(barDir, "baz");
+    File bazFile = new File(bazDir, "Test.java");
+    assertThat(fooFile.exists()).isTrue();
+    assertThat(barFile.exists()).isTrue();
+    assertThat(bazFile.exists()).isTrue();
+  }
+
+  @Test public void filerNestedClasses() throws IOException {
+    TypeSpec type = TypeSpec.classBuilder("Test").build();
+    JavaFile.builder("foo", type).build().writeTo(filer);
+    JavaFile.builder("foo.bar", type).build().writeTo(filer);
+    JavaFile.builder("foo.bar.baz", type).build().writeTo(filer);
+
+    Path fooPath = fsRoot.resolve(fs.getPath("foo", "Test.java"));
+    Path barPath = fsRoot.resolve(fs.getPath("foo", "bar", "Test.java"));
+    Path bazPath = fsRoot.resolve(fs.getPath("foo", "bar", "baz", "Test.java"));
+    assertThat(Files.exists(fooPath)).isTrue();
+    assertThat(Files.exists(barPath)).isTrue();
+    assertThat(Files.exists(bazPath)).isTrue();
+  }
+
+  @Test public void filerPassesOriginatingElements() throws IOException {
+    Element element1_1 = Mockito.mock(Element.class);
+    TypeSpec test1 = TypeSpec.classBuilder("Test1")
+        .addOriginatingElement(element1_1)
+        .build();
+
+    Element element2_1 = Mockito.mock(Element.class);
+    Element element2_2 = Mockito.mock(Element.class);
+    TypeSpec test2 = TypeSpec.classBuilder("Test2")
+        .addOriginatingElement(element2_1)
+        .addOriginatingElement(element2_2)
+        .build();
+
+    JavaFile.builder("example", test1).build().writeTo(filer);
+    JavaFile.builder("example", test2).build().writeTo(filer);
+
+    Path testPath1 = fsRoot.resolve(fs.getPath("example", "Test1.java"));
+    assertThat(filer.getOriginatingElements(testPath1)).containsExactly(element1_1);
+    Path testPath2 = fsRoot.resolve(fs.getPath("example", "Test2.java"));
+    assertThat(filer.getOriginatingElements(testPath2)).containsExactly(element2_1, element2_2);
+  }
+
+  @Test public void filerClassesWithTabIndent() throws IOException {
+    TypeSpec test = TypeSpec.classBuilder("Test")
+        .addField(Date.class, "madeFreshDate")
+        .addMethod(MethodSpec.methodBuilder("main")
+            .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+            .addParameter(String[].class, "args")
+            .addCode("$T.out.println($S);\n", System.class, "Hello World!")
+            .build())
+        .build();
+    JavaFile.builder("foo", test).indent("\t").build().writeTo(filer);
+
+    Path fooPath = fsRoot.resolve(fs.getPath("foo", "Test.java"));
+    assertThat(Files.exists(fooPath)).isTrue();
+    String source = new String(Files.readAllBytes(fooPath));
+
+    assertThat(source).isEqualTo(""
+        + "package foo;\n"
+        + "\n"
+        + "import java.lang.String;\n"
+        + "import java.lang.System;\n"
+        + "import java.util.Date;\n"
+        + "\n"
+        + "class Test {\n"
+        + "\tDate madeFreshDate;\n"
+        + "\n"
+        + "\tpublic static void main(String[] args) {\n"
+        + "\t\tSystem.out.println(\"Hello World!\");\n"
+        + "\t}\n"
+        + "}\n");
+  }
+
+  /**
+   * This test confirms that JavaPoet ignores the host charset and always uses UTF-8. The host
+   * charset is customized with {@code -Dfile.encoding=ISO-8859-1}.
+   */
+  @Test public void fileIsUtf8() throws IOException {
+    JavaFile javaFile = JavaFile.builder("foo", TypeSpec.classBuilder("Taco").build())
+        .addFileComment("Pi\u00f1ata\u00a1")
+        .build();
+    javaFile.writeTo(fsRoot);
+
+    Path fooPath = fsRoot.resolve(fs.getPath("foo", "Taco.java"));
+    assertThat(new String(Files.readAllBytes(fooPath), UTF_8)).isEqualTo(""
+        + "// Pi\u00f1ata\u00a1\n"
+        + "package foo;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "}\n");
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/JavaFileTest.java b/src/test/java/com/squareup/javapoet/JavaFileTest.java
new file mode 100644
index 0000000..e056116
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/JavaFileTest.java
@@ -0,0 +1,692 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import javax.lang.model.element.Modifier;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(JUnit4.class)
+public final class JavaFileTest {
+  @Test public void importStaticReadmeExample() {
+    ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
+    ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards");
+    ClassName list = ClassName.get("java.util", "List");
+    ClassName arrayList = ClassName.get("java.util", "ArrayList");
+    TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);
+    MethodSpec beyond = MethodSpec.methodBuilder("beyond")
+        .returns(listOfHoverboards)
+        .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
+        .addStatement("result.add($T.createNimbus(2000))", hoverboard)
+        .addStatement("result.add($T.createNimbus(\"2001\"))", hoverboard)
+        .addStatement("result.add($T.createNimbus($T.THUNDERBOLT))", hoverboard, namedBoards)
+        .addStatement("$T.sort(result)", Collections.class)
+        .addStatement("return result.isEmpty() ? $T.emptyList() : result", Collections.class)
+        .build();
+    TypeSpec hello = TypeSpec.classBuilder("HelloWorld")
+        .addMethod(beyond)
+        .build();
+    JavaFile example = JavaFile.builder("com.example.helloworld", hello)
+        .addStaticImport(hoverboard, "createNimbus")
+        .addStaticImport(namedBoards, "*")
+        .addStaticImport(Collections.class, "*")
+        .build();
+    assertThat(example.toString()).isEqualTo(""
+        + "package com.example.helloworld;\n"
+        + "\n"
+        + "import static com.mattel.Hoverboard.Boards.*;\n"
+        + "import static com.mattel.Hoverboard.createNimbus;\n"
+        + "import static java.util.Collections.*;\n"
+        + "\n"
+        + "import com.mattel.Hoverboard;\n"
+        + "import java.util.ArrayList;\n"
+        + "import java.util.List;\n"
+        + "\n"
+        + "class HelloWorld {\n"
+        + "  List<Hoverboard> beyond() {\n"
+        + "    List<Hoverboard> result = new ArrayList<>();\n"
+        + "    result.add(createNimbus(2000));\n"
+        + "    result.add(createNimbus(\"2001\"));\n"
+        + "    result.add(createNimbus(THUNDERBOLT));\n"
+        + "    sort(result);\n"
+        + "    return result.isEmpty() ? emptyList() : result;\n"
+        + "  }\n"
+        + "}\n");
+  }
+  @Test public void importStaticForCrazyFormatsWorks() {
+    MethodSpec method = MethodSpec.methodBuilder("method").build();
+    JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .addStaticBlock(CodeBlock.builder()
+                .addStatement("$T", Runtime.class)
+                .addStatement("$T.a()", Runtime.class)
+                .addStatement("$T.X", Runtime.class)
+                .addStatement("$T$T", Runtime.class, Runtime.class)
+                .addStatement("$T.$T", Runtime.class, Runtime.class)
+                .addStatement("$1T$1T", Runtime.class)
+                .addStatement("$1T$2L$1T", Runtime.class, "?")
+                .addStatement("$1T$2L$2S$1T", Runtime.class, "?")
+                .addStatement("$1T$2L$2S$1T$3N$1T", Runtime.class, "?", method)
+                .addStatement("$T$L", Runtime.class, "?")
+                .addStatement("$T$S", Runtime.class, "?")
+                .addStatement("$T$N", Runtime.class, method)
+                .build())
+            .build())
+        .addStaticImport(Runtime.class, "*")
+        .build()
+        .toString(); // don't look at the generated code...
+  }
+
+  @Test public void importStaticMixed() {
+    JavaFile source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .addStaticBlock(CodeBlock.builder()
+                .addStatement("assert $1T.valueOf(\"BLOCKED\") == $1T.BLOCKED", Thread.State.class)
+                .addStatement("$T.gc()", System.class)
+                .addStatement("$1T.out.println($1T.nanoTime())", System.class)
+                .build())
+            .addMethod(MethodSpec.constructorBuilder()
+                .addParameter(Thread.State[].class, "states")
+                .varargs(true)
+                .build())
+            .build())
+        .addStaticImport(Thread.State.BLOCKED)
+        .addStaticImport(System.class, "*")
+        .addStaticImport(Thread.State.class, "valueOf")
+        .build();
+    assertThat(source.toString()).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import static java.lang.System.*;\n"
+        + "import static java.lang.Thread.State.BLOCKED;\n"
+        + "import static java.lang.Thread.State.valueOf;\n"
+        + "\n"
+        + "import java.lang.Thread;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  static {\n"
+        + "    assert valueOf(\"BLOCKED\") == BLOCKED;\n"
+        + "    gc();\n"
+        + "    out.println(nanoTime());\n"
+        + "  }\n"
+        + "\n"
+        + "  Taco(Thread.State... states) {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Ignore("addStaticImport doesn't support members with $L")
+  @Test public void importStaticDynamic() {
+    JavaFile source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .addMethod(MethodSpec.methodBuilder("main")
+                .addStatement("$T.$L.println($S)", System.class, "out", "hello")
+                .build())
+            .build())
+        .addStaticImport(System.class, "out")
+        .build();
+    assertThat(source.toString()).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import static java.lang.System.out;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  void main() {\n"
+        + "    out.println(\"hello\");\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void importStaticNone() {
+    assertThat(JavaFile.builder("readme", importStaticTypeSpec("Util"))
+        .build().toString()).isEqualTo(""
+        + "package readme;\n"
+        + "\n"
+        + "import java.lang.System;\n"
+        + "import java.util.concurrent.TimeUnit;\n"
+        + "\n"
+        + "class Util {\n"
+        + "  public static long minutesToSeconds(long minutes) {\n"
+        + "    System.gc();\n"
+        + "    return TimeUnit.SECONDS.convert(minutes, TimeUnit.MINUTES);\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void importStaticOnce() {
+    assertThat(JavaFile.builder("readme", importStaticTypeSpec("Util"))
+        .addStaticImport(TimeUnit.SECONDS)
+        .build().toString()).isEqualTo(""
+        + "package readme;\n"
+        + "\n"
+        + "import static java.util.concurrent.TimeUnit.SECONDS;\n"
+        + "\n"
+        + "import java.lang.System;\n"
+        + "import java.util.concurrent.TimeUnit;\n"
+        + "\n"
+        + "class Util {\n"
+        + "  public static long minutesToSeconds(long minutes) {\n"
+        + "    System.gc();\n"
+        + "    return SECONDS.convert(minutes, TimeUnit.MINUTES);\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void importStaticTwice() {
+    assertThat(JavaFile.builder("readme", importStaticTypeSpec("Util"))
+        .addStaticImport(TimeUnit.SECONDS)
+        .addStaticImport(TimeUnit.MINUTES)
+        .build().toString()).isEqualTo(""
+            + "package readme;\n"
+            + "\n"
+            + "import static java.util.concurrent.TimeUnit.MINUTES;\n"
+            + "import static java.util.concurrent.TimeUnit.SECONDS;\n"
+            + "\n"
+            + "import java.lang.System;\n"
+            + "\n"
+            + "class Util {\n"
+            + "  public static long minutesToSeconds(long minutes) {\n"
+            + "    System.gc();\n"
+            + "    return SECONDS.convert(minutes, MINUTES);\n"
+            + "  }\n"
+            + "}\n");
+  }
+
+  @Test public void importStaticUsingWildcards() {
+    assertThat(JavaFile.builder("readme", importStaticTypeSpec("Util"))
+        .addStaticImport(TimeUnit.class, "*")
+        .addStaticImport(System.class, "*")
+        .build().toString()).isEqualTo(""
+            + "package readme;\n"
+            + "\n"
+            + "import static java.lang.System.*;\n"
+            + "import static java.util.concurrent.TimeUnit.*;\n"
+            + "\n"
+            + "class Util {\n"
+            + "  public static long minutesToSeconds(long minutes) {\n"
+            + "    gc();\n"
+            + "    return SECONDS.convert(minutes, MINUTES);\n"
+            + "  }\n"
+            + "}\n");
+  }
+
+  private TypeSpec importStaticTypeSpec(String name) {
+    MethodSpec method = MethodSpec.methodBuilder("minutesToSeconds")
+        .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+        .returns(long.class)
+        .addParameter(long.class, "minutes")
+        .addStatement("$T.gc()", System.class)
+        .addStatement("return $1T.SECONDS.convert(minutes, $1T.MINUTES)", TimeUnit.class)
+        .build();
+    return TypeSpec.classBuilder(name).addMethod(method).build();
+
+  }
+  @Test public void noImports() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco").build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "}\n");
+  }
+
+  @Test public void singleImport() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .addField(Date.class, "madeFreshDate")
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.util.Date;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  Date madeFreshDate;\n"
+        + "}\n");
+  }
+
+  @Test public void conflictingImports() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .addField(Date.class, "madeFreshDate")
+            .addField(ClassName.get("java.sql", "Date"), "madeFreshDatabaseDate")
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.util.Date;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  Date madeFreshDate;\n"
+        + "\n"
+        + "  java.sql.Date madeFreshDatabaseDate;\n"
+        + "}\n");
+  }
+
+  @Test public void annotatedTypeParam() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .addField(ParameterizedTypeName.get(ClassName.get(List.class),
+                ClassName.get("com.squareup.meat", "Chorizo")
+                    .annotated(AnnotationSpec.builder(ClassName.get("com.squareup.tacos", "Spicy"))
+                        .build())), "chorizo")
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import com.squareup.meat.Chorizo;\n"
+        + "import java.util.List;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  List<@Spicy Chorizo> chorizo;\n"
+        + "}\n");
+  }
+
+  @Test public void skipJavaLangImportsWithConflictingClassLast() throws Exception {
+    // Whatever is used first wins! In this case the Float in java.lang is imported.
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .addField(ClassName.get("java.lang", "Float"), "litres")
+            .addField(ClassName.get("com.squareup.soda", "Float"), "beverage")
+            .build())
+        .skipJavaLangImports(true)
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  Float litres;\n"
+        + "\n"
+        + "  com.squareup.soda.Float beverage;\n" // Second 'Float' is fully qualified.
+        + "}\n");
+  }
+
+  @Test public void skipJavaLangImportsWithConflictingClassFirst() throws Exception {
+    // Whatever is used first wins! In this case the Float in com.squareup.soda is imported.
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .addField(ClassName.get("com.squareup.soda", "Float"), "beverage")
+            .addField(ClassName.get("java.lang", "Float"), "litres")
+            .build())
+        .skipJavaLangImports(true)
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import com.squareup.soda.Float;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  Float beverage;\n"
+        + "\n"
+        + "  java.lang.Float litres;\n" // Second 'Float' is fully qualified.
+        + "}\n");
+  }
+
+  @Test public void conflictingParentName() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("A")
+            .addType(TypeSpec.classBuilder("B")
+                .addType(TypeSpec.classBuilder("Twin").build())
+                .addType(TypeSpec.classBuilder("C")
+                    .addField(ClassName.get("com.squareup.tacos", "A", "Twin", "D"), "d")
+                    .build())
+                .build())
+            .addType(TypeSpec.classBuilder("Twin")
+                .addType(TypeSpec.classBuilder("D")
+                    .build())
+                .build())
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class A {\n"
+        + "  class B {\n"
+        + "    class Twin {\n"
+        + "    }\n"
+        + "\n"
+        + "    class C {\n"
+        + "      A.Twin.D d;\n"
+        + "    }\n"
+        + "  }\n"
+        + "\n"
+        + "  class Twin {\n"
+        + "    class D {\n"
+        + "    }\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void conflictingChildName() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("A")
+            .addType(TypeSpec.classBuilder("B")
+                .addType(TypeSpec.classBuilder("C")
+                    .addField(ClassName.get("com.squareup.tacos", "A", "Twin", "D"), "d")
+                    .addType(TypeSpec.classBuilder("Twin").build())
+                    .build())
+                .build())
+            .addType(TypeSpec.classBuilder("Twin")
+                .addType(TypeSpec.classBuilder("D")
+                    .build())
+                .build())
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class A {\n"
+        + "  class B {\n"
+        + "    class C {\n"
+        + "      A.Twin.D d;\n"
+        + "\n"
+        + "      class Twin {\n"
+        + "      }\n"
+        + "    }\n"
+        + "  }\n"
+        + "\n"
+        + "  class Twin {\n"
+        + "    class D {\n"
+        + "    }\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void conflictingNameOutOfScope() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("A")
+            .addType(TypeSpec.classBuilder("B")
+                .addType(TypeSpec.classBuilder("C")
+                    .addField(ClassName.get("com.squareup.tacos", "A", "Twin", "D"), "d")
+                    .addType(TypeSpec.classBuilder("Nested")
+                        .addType(TypeSpec.classBuilder("Twin").build())
+                        .build())
+                    .build())
+                .build())
+            .addType(TypeSpec.classBuilder("Twin")
+                .addType(TypeSpec.classBuilder("D")
+                    .build())
+                .build())
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class A {\n"
+        + "  class B {\n"
+        + "    class C {\n"
+        + "      Twin.D d;\n"
+        + "\n"
+        + "      class Nested {\n"
+        + "        class Twin {\n"
+        + "        }\n"
+        + "      }\n"
+        + "    }\n"
+        + "  }\n"
+        + "\n"
+        + "  class Twin {\n"
+        + "    class D {\n"
+        + "    }\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void nestedClassAndSuperclassShareName() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .superclass(ClassName.get("com.squareup.wire", "Message"))
+            .addType(TypeSpec.classBuilder("Builder")
+                .superclass(ClassName.get("com.squareup.wire", "Message", "Builder"))
+                .build())
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import com.squareup.wire.Message;\n"
+        + "\n"
+        + "class Taco extends Message {\n"
+        + "  class Builder extends Message.Builder {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void classAndSuperclassShareName() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .superclass(ClassName.get("com.taco.bell", "Taco"))
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco extends com.taco.bell.Taco {\n"
+        + "}\n");
+  }
+
+  @Test public void conflictingAnnotation() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .addAnnotation(ClassName.get("com.taco.bell", "Taco"))
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "@com.taco.bell.Taco\n"
+        + "class Taco {\n"
+        + "}\n");
+  }
+
+  @Test public void conflictingAnnotationReferencedClass() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .addAnnotation(AnnotationSpec.builder(ClassName.get("com.squareup.tacos", "MyAnno"))
+                .addMember("value", "$T.class", ClassName.get("com.taco.bell", "Taco"))
+                .build())
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "@MyAnno(com.taco.bell.Taco.class)\n"
+        + "class Taco {\n"
+        + "}\n");
+  }
+
+  @Test public void conflictingTypeVariableBound() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .addTypeVariable(
+                TypeVariableName.get("T", ClassName.get("com.taco.bell", "Taco")))
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco<T extends com.taco.bell.Taco> {\n"
+        + "}\n");
+  }
+
+  @Test public void superclassReferencesSelf() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .superclass(ParameterizedTypeName.get(
+                ClassName.get(Comparable.class), ClassName.get("com.squareup.tacos", "Taco")))
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Comparable;\n"
+        + "\n"
+        + "class Taco extends Comparable<Taco> {\n"
+        + "}\n");
+  }
+
+  /** https://github.com/square/javapoet/issues/366 */
+  @Test public void annotationIsNestedClass() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("TestComponent")
+            .addAnnotation(ClassName.get("dagger", "Component"))
+            .addType(TypeSpec.classBuilder("Builder")
+                .addAnnotation(ClassName.get("dagger", "Component", "Builder"))
+                .build())
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import dagger.Component;\n"
+        + "\n"
+        + "@Component\n"
+        + "class TestComponent {\n"
+        + "  @Component.Builder\n"
+        + "  class Builder {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void defaultPackage() throws Exception {
+    String source = JavaFile.builder("",
+        TypeSpec.classBuilder("HelloWorld")
+            .addMethod(MethodSpec.methodBuilder("main")
+                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+                .addParameter(String[].class, "args")
+                .addCode("$T.out.println($S);\n", System.class, "Hello World!")
+                .build())
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "import java.lang.String;\n"
+        + "import java.lang.System;\n"
+        + "\n"
+        + "class HelloWorld {\n"
+        + "  public static void main(String[] args) {\n"
+        + "    System.out.println(\"Hello World!\");\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void defaultPackageTypesAreNotImported() throws Exception {
+    String source = JavaFile.builder("hello",
+          TypeSpec.classBuilder("World").addSuperinterface(ClassName.get("", "Test")).build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package hello;\n"
+        + "\n"
+        + "class World implements Test {\n"
+        + "}\n");
+  }
+
+  @Test public void topOfFileComment() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco").build())
+        .addFileComment("Generated $L by JavaPoet. DO NOT EDIT!", "2015-01-13")
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "// Generated 2015-01-13 by JavaPoet. DO NOT EDIT!\n"
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "}\n");
+  }
+
+  @Test public void emptyLinesInTopOfFileComment() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco").build())
+        .addFileComment("\nGENERATED FILE:\n\nDO NOT EDIT!\n")
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "//\n"
+        + "// GENERATED FILE:\n"
+        + "//\n"
+        + "// DO NOT EDIT!\n"
+        + "//\n"
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "}\n");
+  }
+
+  @Test public void packageClassConflictsWithNestedClass() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .addField(ClassName.get("com.squareup.tacos", "A"), "a")
+            .addType(TypeSpec.classBuilder("A").build())
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  com.squareup.tacos.A a;\n"
+        + "\n"
+        + "  class A {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void packageClassConflictsWithSuperlass() throws Exception {
+    String source = JavaFile.builder("com.squareup.tacos",
+        TypeSpec.classBuilder("Taco")
+            .superclass(ClassName.get("com.taco.bell", "A"))
+            .addField(ClassName.get("com.squareup.tacos", "A"), "a")
+            .build())
+        .build()
+        .toString();
+    assertThat(source).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco extends com.taco.bell.A {\n"
+        + "  A a;\n"
+        + "}\n");
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/LineWrapperTest.java b/src/test/java/com/squareup/javapoet/LineWrapperTest.java
new file mode 100644
index 0000000..ba8472c
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/LineWrapperTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+package com.squareup.javapoet;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(JUnit4.class)
+public final class LineWrapperTest {
+  @Test public void wrap() throws Exception {
+    StringBuffer out = new StringBuffer();
+    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
+    lineWrapper.append("abcde");
+    lineWrapper.wrappingSpace(2);
+    lineWrapper.append("fghij");
+    lineWrapper.close();
+    assertThat(out.toString()).isEqualTo("abcde\n    fghij");
+  }
+
+  @Test public void noWrap() throws Exception {
+    StringBuffer out = new StringBuffer();
+    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
+    lineWrapper.append("abcde");
+    lineWrapper.wrappingSpace(2);
+    lineWrapper.append("fghi");
+    lineWrapper.close();
+    assertThat(out.toString()).isEqualTo("abcde fghi");
+  }
+
+  @Test public void zeroWidthNoWrap() throws Exception {
+    StringBuffer out = new StringBuffer();
+    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
+    lineWrapper.append("abcde");
+    lineWrapper.zeroWidthSpace(2);
+    lineWrapper.append("fghij");
+    lineWrapper.close();
+    assertThat(out.toString()).isEqualTo("abcdefghij");
+  }
+
+  @Test public void nospaceWrapMax() throws Exception {
+    StringBuffer out = new StringBuffer();
+    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
+    lineWrapper.append("abcde");
+    lineWrapper.zeroWidthSpace(2);
+    lineWrapper.append("fghijk");
+    lineWrapper.close();
+    assertThat(out.toString()).isEqualTo("abcde\n    fghijk");
+  }
+
+  @Test public void multipleWrite() throws Exception {
+    StringBuffer out = new StringBuffer();
+    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
+    lineWrapper.append("ab");
+    lineWrapper.wrappingSpace(1);
+    lineWrapper.append("cd");
+    lineWrapper.wrappingSpace(1);
+    lineWrapper.append("ef");
+    lineWrapper.wrappingSpace(1);
+    lineWrapper.append("gh");
+    lineWrapper.wrappingSpace(1);
+    lineWrapper.append("ij");
+    lineWrapper.wrappingSpace(1);
+    lineWrapper.append("kl");
+    lineWrapper.wrappingSpace(1);
+    lineWrapper.append("mn");
+    lineWrapper.wrappingSpace(1);
+    lineWrapper.append("op");
+    lineWrapper.wrappingSpace(1);
+    lineWrapper.append("qr");
+    lineWrapper.close();
+    assertThat(out.toString()).isEqualTo("ab cd ef\n  gh ij kl\n  mn op qr");
+  }
+
+  @Test public void fencepost() throws Exception {
+    StringBuffer out = new StringBuffer();
+    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
+    lineWrapper.append("abcde");
+    lineWrapper.append("fghij");
+    lineWrapper.wrappingSpace(2);
+    lineWrapper.append("k");
+    lineWrapper.append("lmnop");
+    lineWrapper.close();
+    assertThat(out.toString()).isEqualTo("abcdefghij\n    klmnop");
+  }
+
+  @Test public void fencepostZeroWidth() throws Exception {
+    StringBuffer out = new StringBuffer();
+    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
+    lineWrapper.append("abcde");
+    lineWrapper.append("fghij");
+    lineWrapper.zeroWidthSpace(2);
+    lineWrapper.append("k");
+    lineWrapper.append("lmnop");
+    lineWrapper.close();
+    assertThat(out.toString()).isEqualTo("abcdefghij\n    klmnop");
+  }
+
+  @Test public void overlyLongLinesWithoutLeadingSpace() throws Exception {
+    StringBuffer out = new StringBuffer();
+    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
+    lineWrapper.append("abcdefghijkl");
+    lineWrapper.close();
+    assertThat(out.toString()).isEqualTo("abcdefghijkl");
+  }
+
+  @Test public void overlyLongLinesWithLeadingSpace() throws Exception {
+    StringBuffer out = new StringBuffer();
+    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
+    lineWrapper.wrappingSpace(2);
+    lineWrapper.append("abcdefghijkl");
+    lineWrapper.close();
+    assertThat(out.toString()).isEqualTo("\n    abcdefghijkl");
+  }
+
+  @Test public void overlyLongLinesWithLeadingZeroWidth() throws Exception {
+    StringBuffer out = new StringBuffer();
+    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
+    lineWrapper.zeroWidthSpace(2);
+    lineWrapper.append("abcdefghijkl");
+    lineWrapper.close();
+    assertThat(out.toString()).isEqualTo("abcdefghijkl");
+  }
+
+  @Test public void noWrapEmbeddedNewlines() throws Exception {
+    StringBuffer out = new StringBuffer();
+    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
+    lineWrapper.append("abcde");
+    lineWrapper.wrappingSpace(2);
+    lineWrapper.append("fghi\njklmn");
+    lineWrapper.append("opqrstuvwxy");
+    lineWrapper.close();
+    assertThat(out.toString()).isEqualTo("abcde fghi\njklmnopqrstuvwxy");
+  }
+
+  @Test public void wrapEmbeddedNewlines() throws Exception {
+    StringBuffer out = new StringBuffer();
+    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
+    lineWrapper.append("abcde");
+    lineWrapper.wrappingSpace(2);
+    lineWrapper.append("fghij\nklmn");
+    lineWrapper.append("opqrstuvwxy");
+    lineWrapper.close();
+    assertThat(out.toString()).isEqualTo("abcde\n    fghij\nklmnopqrstuvwxy");
+  }
+
+  @Test public void noWrapEmbeddedNewlines_ZeroWidth() throws Exception {
+    StringBuffer out = new StringBuffer();
+    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
+    lineWrapper.append("abcde");
+    lineWrapper.zeroWidthSpace(2);
+    lineWrapper.append("fghij\nklmn");
+    lineWrapper.append("opqrstuvwxyz");
+    lineWrapper.close();
+    assertThat(out.toString()).isEqualTo("abcdefghij\nklmnopqrstuvwxyz");
+  }
+
+  @Test public void wrapEmbeddedNewlines_ZeroWidth() throws Exception {
+    StringBuffer out = new StringBuffer();
+    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
+    lineWrapper.append("abcde");
+    lineWrapper.zeroWidthSpace(2);
+    lineWrapper.append("fghijk\nlmn");
+    lineWrapper.append("opqrstuvwxy");
+    lineWrapper.close();
+    assertThat(out.toString()).isEqualTo("abcde\n    fghijk\nlmnopqrstuvwxy");
+  }
+
+  @Test public void noWrapMultipleNewlines() throws Exception {
+    StringBuffer out = new StringBuffer();
+    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
+    lineWrapper.append("abcde");
+    lineWrapper.wrappingSpace(2);
+    lineWrapper.append("fghi\nklmnopq\nr");
+    lineWrapper.wrappingSpace(2);
+    lineWrapper.append("stuvwxyz");
+    lineWrapper.close();
+    assertThat(out.toString()).isEqualTo("abcde fghi\nklmnopq\nr stuvwxyz");
+  }
+
+  @Test public void wrapMultipleNewlines() throws Exception {
+    StringBuffer out = new StringBuffer();
+    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
+    lineWrapper.append("abcde");
+    lineWrapper.wrappingSpace(2);
+    lineWrapper.append("fghi\nklmnopq\nrs");
+    lineWrapper.wrappingSpace(2);
+    lineWrapper.append("tuvwxyz1");
+    lineWrapper.close();
+    assertThat(out.toString()).isEqualTo("abcde fghi\nklmnopq\nrs\n    tuvwxyz1");
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/MethodSpecTest.java b/src/test/java/com/squareup/javapoet/MethodSpecTest.java
new file mode 100644
index 0000000..5dfabaa
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/MethodSpecTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import com.google.testing.compile.CompilationRule;
+import java.io.Closeable;
+import java.io.IOException;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeoutException;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.truth.Truth.assertThat;
+import static javax.lang.model.util.ElementFilter.methodsIn;
+import static org.junit.Assert.fail;
+
+public final class MethodSpecTest {
+  @Rule public final CompilationRule compilation = new CompilationRule();
+
+  private Elements elements;
+  private Types types;
+
+  @Before public void setUp() {
+    elements = compilation.getElements();
+    types = compilation.getTypes();
+  }
+
+  private TypeElement getElement(Class<?> clazz) {
+    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);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("annotationSpecs == null");
+    }
+  }
+
+  @Test public void nullTypeVariablesAddition() {
+    try {
+      MethodSpec.methodBuilder("doSomething").addTypeVariables(null);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("typeVariables == null");
+    }
+  }
+
+  @Test public void nullParametersAddition() {
+    try {
+      MethodSpec.methodBuilder("doSomething").addParameters(null);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("parameterSpecs == null");
+    }
+  }
+
+  @Test public void nullExceptionsAddition() {
+    try {
+      MethodSpec.methodBuilder("doSomething").addExceptions(null);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("exceptions == null");
+    }
+  }
+
+  @Target(ElementType.PARAMETER)
+  @interface Nullable {
+  }
+
+  abstract static class Everything {
+    @Deprecated protected abstract <T extends Runnable & Closeable> Runnable everything(
+        @Nullable String thing, List<? extends T> things) throws IOException, SecurityException;
+  }
+
+  abstract static class Generics {
+    <T, R, V extends Throwable> T run(R param) throws V {
+      return null;
+    }
+  }
+
+  abstract static class HasAnnotation {
+    @Override public abstract String toString();
+  }
+
+  interface Throws<R extends RuntimeException> {
+    void fail() throws R;
+  }
+
+  interface ExtendsOthers extends Callable<Integer>, Comparable<ExtendsOthers>,
+      Throws<IllegalStateException> {
+  }
+
+  interface ExtendsIterableWithDefaultMethods extends Iterable<Object> {
+  }
+
+  final class FinalClass {
+    void method() {
+    }
+  }
+
+  abstract static class InvalidOverrideMethods {
+    final void finalMethod() {
+    }
+
+    private void privateMethod() {
+    }
+
+    static void staticMethod() {
+    }
+  }
+
+  @Test public void overrideEverything() {
+    TypeElement classElement = getElement(Everything.class);
+    ExecutableElement methodElement = getOnlyElement(methodsIn(classElement.getEnclosedElements()));
+    MethodSpec method = MethodSpec.overriding(methodElement).build();
+    assertThat(method.toString()).isEqualTo(""
+        + "@java.lang.Override\n"
+        + "protected <T extends java.lang.Runnable & java.io.Closeable> java.lang.Runnable "
+        + "everything(\n"
+        + "    java.lang.String arg0, java.util.List<? extends T> arg1) throws java.io.IOException,\n"
+        + "    java.lang.SecurityException {\n"
+        + "}\n");
+  }
+
+  @Test public void overrideGenerics() {
+    TypeElement classElement = getElement(Generics.class);
+    ExecutableElement methodElement = getOnlyElement(methodsIn(classElement.getEnclosedElements()));
+    MethodSpec method = MethodSpec.overriding(methodElement)
+        .addStatement("return null")
+        .build();
+    assertThat(method.toString()).isEqualTo(""
+        + "@java.lang.Override\n"
+        + "<T, R, V extends java.lang.Throwable> T run(R param) throws V {\n"
+        + "  return null;\n"
+        + "}\n");
+  }
+
+  @Test public void overrideDoesNotCopyOverrideAnnotation() {
+    TypeElement classElement = getElement(HasAnnotation.class);
+    ExecutableElement exec = getOnlyElement(methodsIn(classElement.getEnclosedElements()));
+    MethodSpec method = MethodSpec.overriding(exec).build();
+    assertThat(method.toString()).isEqualTo(""
+        + "@java.lang.Override\n"
+        + "public java.lang.String toString() {\n"
+        + "}\n");
+  }
+
+  @Test public void overrideDoesNotCopyDefaultModifier() {
+    TypeElement classElement = getElement(ExtendsIterableWithDefaultMethods.class);
+    DeclaredType classType = (DeclaredType) classElement.asType();
+    List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
+    ExecutableElement exec = findFirst(methods, "spliterator");
+    MethodSpec method = MethodSpec.overriding(exec, classType, types).build();
+    assertThat(method.toString()).isEqualTo(""
+        + "@java.lang.Override\n"
+        + "public java.util.Spliterator<java.lang.Object> spliterator() {\n"
+        + "}\n");
+  }
+
+  @Test public void overrideExtendsOthersWorksWithActualTypeParameters() {
+    TypeElement classElement = getElement(ExtendsOthers.class);
+    DeclaredType classType = (DeclaredType) classElement.asType();
+    List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
+    ExecutableElement exec = findFirst(methods, "call");
+    MethodSpec method = MethodSpec.overriding(exec, classType, types).build();
+    assertThat(method.toString()).isEqualTo(""
+        + "@java.lang.Override\n"
+        + "public java.lang.Integer call() throws java.lang.Exception {\n"
+        + "}\n");
+    exec = findFirst(methods, "compareTo");
+    method = MethodSpec.overriding(exec, classType, types).build();
+    assertThat(method.toString()).isEqualTo(""
+        + "@java.lang.Override\n"
+        + "public int compareTo(" + ExtendsOthers.class.getCanonicalName() + " arg0) {\n"
+        + "}\n");
+    exec = findFirst(methods, "fail");
+    method = MethodSpec.overriding(exec, classType, types).build();
+    assertThat(method.toString()).isEqualTo(""
+        + "@java.lang.Override\n"
+        + "public void fail() throws java.lang.IllegalStateException {\n"
+        + "}\n");
+  }
+
+  @Test public void overrideFinalClassMethod() {
+    TypeElement classElement = getElement(FinalClass.class);
+    List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
+    try {
+      MethodSpec.overriding(findFirst(methods, "method"));
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo(
+          "Cannot override method on final class com.squareup.javapoet.MethodSpecTest.FinalClass");
+    }
+  }
+
+  @Test public void overrideInvalidModifiers() {
+    TypeElement classElement = getElement(InvalidOverrideMethods.class);
+    List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
+    try {
+      MethodSpec.overriding(findFirst(methods, "finalMethod"));
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("cannot override method with modifiers: [final]");
+    }
+    try {
+      MethodSpec.overriding(findFirst(methods, "privateMethod"));
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("cannot override method with modifiers: [private]");
+    }
+    try {
+      MethodSpec.overriding(findFirst(methods, "staticMethod"));
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("cannot override method with modifiers: [static]");
+    }
+  }
+
+  @Test public void equalsAndHashCode() {
+    MethodSpec a = MethodSpec.constructorBuilder().build();
+    MethodSpec b = MethodSpec.constructorBuilder().build();
+    assertThat(a.equals(b)).isTrue();
+    assertThat(a.hashCode()).isEqualTo(b.hashCode());
+    a = MethodSpec.methodBuilder("taco").build();
+    b = MethodSpec.methodBuilder("taco").build();
+    assertThat(a.equals(b)).isTrue();
+    assertThat(a.hashCode()).isEqualTo(b.hashCode());
+    TypeElement classElement = getElement(Everything.class);
+    ExecutableElement methodElement = getOnlyElement(methodsIn(classElement.getEnclosedElements()));
+    a = MethodSpec.overriding(methodElement).build();
+    b = MethodSpec.overriding(methodElement).build();
+    assertThat(a.equals(b)).isTrue();
+    assertThat(a.hashCode()).isEqualTo(b.hashCode());
+  }
+
+  @Test public void duplicateExceptionsIgnored() {
+    ClassName ioException = ClassName.get(IOException.class);
+    ClassName timeoutException = ClassName.get(TimeoutException.class);
+    MethodSpec methodSpec = MethodSpec.methodBuilder("duplicateExceptions")
+      .addException(ioException)
+      .addException(timeoutException)
+      .addException(timeoutException)
+      .addException(ioException)
+      .build();
+    assertThat(methodSpec.exceptions).isEqualTo(Arrays.asList(ioException, timeoutException));
+    assertThat(methodSpec.toBuilder().addException(ioException).build().exceptions)
+      .isEqualTo(Arrays.asList(ioException, timeoutException));
+  }
+
+  @Test public void nullIsNotAValidMethodName() {
+    try {
+      MethodSpec.methodBuilder(null);
+      fail("NullPointerException expected");
+    } catch (NullPointerException e) {
+      assertThat(e.getMessage()).isEqualTo("name == null");
+    }
+  }
+
+  @Test public void addModifiersVarargsShouldNotBeNull() {
+    try {
+      MethodSpec.methodBuilder("taco")
+              .addModifiers((Modifier[]) null);
+      fail("NullPointerException expected");
+    } catch (NullPointerException e) {
+      assertThat(e.getMessage()).isEqualTo("modifiers == null");
+    }
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/NameAllocatorTest.java b/src/test/java/com/squareup/javapoet/NameAllocatorTest.java
new file mode 100644
index 0000000..1840107
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/NameAllocatorTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+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");
+    assertThat(nameAllocator.newName("bar", 2)).isEqualTo("bar");
+    assertThat(nameAllocator.get(1)).isEqualTo("foo");
+    assertThat(nameAllocator.get(2)).isEqualTo("bar");
+  }
+
+  @Test public void nameCollision() throws Exception {
+    NameAllocator nameAllocator = new NameAllocator();
+    assertThat(nameAllocator.newName("foo")).isEqualTo("foo");
+    assertThat(nameAllocator.newName("foo")).isEqualTo("foo_");
+    assertThat(nameAllocator.newName("foo")).isEqualTo("foo__");
+  }
+
+  @Test public void nameCollisionWithTag() throws Exception {
+    NameAllocator nameAllocator = new NameAllocator();
+    assertThat(nameAllocator.newName("foo", 1)).isEqualTo("foo");
+    assertThat(nameAllocator.newName("foo", 2)).isEqualTo("foo_");
+    assertThat(nameAllocator.newName("foo", 3)).isEqualTo("foo__");
+    assertThat(nameAllocator.get(1)).isEqualTo("foo");
+    assertThat(nameAllocator.get(2)).isEqualTo("foo_");
+    assertThat(nameAllocator.get(3)).isEqualTo("foo__");
+  }
+
+  @Test public void characterMappingSubstitute() throws Exception {
+    NameAllocator nameAllocator = new NameAllocator();
+    assertThat(nameAllocator.newName("a-b", 1)).isEqualTo("a_b");
+  }
+
+  @Test public void characterMappingSurrogate() throws Exception {
+    NameAllocator nameAllocator = new NameAllocator();
+    assertThat(nameAllocator.newName("a\uD83C\uDF7Ab", 1)).isEqualTo("a_b");
+  }
+
+  @Test public void characterMappingInvalidStartButValidPart() throws Exception {
+    NameAllocator nameAllocator = new NameAllocator();
+    assertThat(nameAllocator.newName("1ab", 1)).isEqualTo("_1ab");
+  }
+
+  @Test public void characterMappingInvalidStartIsInvalidPart() throws Exception {
+    NameAllocator nameAllocator = new NameAllocator();
+    assertThat(nameAllocator.newName("&ab", 1)).isEqualTo("_ab");
+  }
+
+  @Test public void javaKeyword() throws Exception {
+    NameAllocator nameAllocator = new NameAllocator();
+    assertThat(nameAllocator.newName("public", 1)).isEqualTo("public_");
+    assertThat(nameAllocator.get(1)).isEqualTo("public_");
+  }
+
+  @Test public void tagReuseForbidden() throws Exception {
+    NameAllocator nameAllocator = new NameAllocator();
+    nameAllocator.newName("foo", 1);
+    try {
+      nameAllocator.newName("bar", 1);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("tag 1 cannot be used for both 'foo' and 'bar'");
+    }
+  }
+
+  @Test public void useBeforeAllocateForbidden() throws Exception {
+    NameAllocator nameAllocator = new NameAllocator();
+    try {
+      nameAllocator.get(1);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("unknown tag: 1");
+    }
+  }
+
+  @Test public void cloneUsage() throws Exception {
+    NameAllocator outterAllocator = new NameAllocator();
+    outterAllocator.newName("foo", 1);
+
+    NameAllocator innerAllocator1 = outterAllocator.clone();
+    assertThat(innerAllocator1.newName("bar", 2)).isEqualTo("bar");
+    assertThat(innerAllocator1.newName("foo", 3)).isEqualTo("foo_");
+
+    NameAllocator innerAllocator2 = outterAllocator.clone();
+    assertThat(innerAllocator2.newName("foo", 2)).isEqualTo("foo_");
+    assertThat(innerAllocator2.newName("bar", 3)).isEqualTo("bar");
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/ParameterSpecTest.java b/src/test/java/com/squareup/javapoet/ParameterSpecTest.java
new file mode 100644
index 0000000..2f81866
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/ParameterSpecTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import javax.lang.model.element.Modifier;
+
+public class ParameterSpecTest {
+  @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());
+    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());
+  }
+
+  @Test public void nullAnnotationsAddition() {
+    try {
+      ParameterSpec.builder(int.class, "foo").addAnnotations(null);
+      fail();
+    } catch (Exception e) {
+      assertThat(e.getMessage())
+          .isEqualTo("annotationSpecs == null");
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/squareup/javapoet/TestFiler.java b/src/test/java/com/squareup/javapoet/TestFiler.java
new file mode 100644
index 0000000..274877c
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/TestFiler.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.processing.Filer;
+import javax.lang.model.element.Element;
+import javax.tools.FileObject;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+
+final class TestFiler implements Filer {
+  class Source extends SimpleJavaFileObject {
+    private final Path path;
+    protected Source(Path path) {
+      super(path.toUri(), Kind.SOURCE);
+      this.path = path;
+    }
+    @Override public OutputStream openOutputStream() throws IOException {
+      Path parent = path.getParent();
+      if (!Files.exists(parent)) fileSystemProvider.createDirectory(parent);
+      return fileSystemProvider.newOutputStream(path);
+    }
+  }
+
+  private final String separator;
+  private final Path fileSystemRoot;
+  private final FileSystemProvider fileSystemProvider;
+  private final Map<Path, Set<Element>> originatingElementsMap;
+
+  public TestFiler(FileSystem fileSystem, Path fsRoot) {
+    separator = fileSystem.getSeparator();
+    fileSystemRoot = fsRoot;
+    fileSystemProvider = fileSystem.provider();
+    originatingElementsMap = new LinkedHashMap<>();
+  }
+
+  public Set<Element> getOriginatingElements(Path path) {
+    return originatingElementsMap.get(path);
+  }
+
+  @Override public JavaFileObject createSourceFile(
+      CharSequence name, Element... originatingElements) throws IOException {
+    String relative = name.toString().replace(".", separator) + ".java"; // Assumes well-formed.
+    Path path = fileSystemRoot.resolve(relative);
+    originatingElementsMap.put(path, Util.immutableSet(Arrays.asList(originatingElements)));
+    return new Source(path);
+  }
+
+  @Override public JavaFileObject createClassFile(CharSequence name, Element... originatingElements)
+      throws IOException {
+    throw new UnsupportedOperationException("Not implemented.");
+  }
+
+  @Override public FileObject createResource(JavaFileManager.Location location, CharSequence pkg,
+      CharSequence relativeName, Element... originatingElements) throws IOException {
+    throw new UnsupportedOperationException("Not implemented.");
+  }
+
+  @Override public FileObject getResource(JavaFileManager.Location location, CharSequence pkg,
+      CharSequence relativeName) throws IOException {
+    throw new UnsupportedOperationException("Not implemented.");
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/TypeNameTest.java b/src/test/java/com/squareup/javapoet/TypeNameTest.java
new file mode 100644
index 0000000..99ed58d
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/TypeNameTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+
+public class TypeNameTest {
+
+  protected <E extends Enum<E>> E generic(E[] values) {
+    return values[0];
+  }
+
+  protected static class TestGeneric<T> {
+    class Inner {}
+
+    class InnerGeneric<T2> {}
+
+    static class NestedNonGeneric {}
+  }
+
+  protected static TestGeneric<String>.Inner testGenericStringInner() {
+    return null;
+  }
+
+  protected static TestGeneric<Integer>.Inner testGenericIntInner() {
+    return null;
+  }
+
+  protected static TestGeneric<Short>.InnerGeneric<Long> testGenericInnerLong() {
+    return null;
+  }
+
+  protected static TestGeneric<Short>.InnerGeneric<Integer> testGenericInnerInt() {
+    return null;
+  }
+
+  protected static TestGeneric.NestedNonGeneric testNestedNonGeneric() {
+    return null;
+  }
+
+  @Test public void genericType() throws Exception {
+    Method recursiveEnum = getClass().getDeclaredMethod("generic", Enum[].class);
+    TypeName.get(recursiveEnum.getReturnType());
+    TypeName.get(recursiveEnum.getGenericReturnType());
+    TypeName genericTypeName = TypeName.get(recursiveEnum.getParameterTypes()[0]);
+    TypeName.get(recursiveEnum.getGenericParameterTypes()[0]);
+
+    // Make sure the generic argument is present
+    assertThat(genericTypeName.toString()).contains("Enum");
+  }
+
+  @Test public void innerClassInGenericType() throws Exception {
+    Method genericStringInner = getClass().getDeclaredMethod("testGenericStringInner");
+    TypeName.get(genericStringInner.getReturnType());
+    TypeName genericTypeName = TypeName.get(genericStringInner.getGenericReturnType());
+    assertNotEquals(TypeName.get(genericStringInner.getGenericReturnType()),
+        TypeName.get(getClass().getDeclaredMethod("testGenericIntInner").getGenericReturnType()));
+
+    // Make sure the generic argument is present
+    assertThat(genericTypeName.toString()).isEqualTo(
+        TestGeneric.class.getCanonicalName() + "<java.lang.String>.Inner");
+  }
+
+  @Test public void innerGenericInGenericType() throws Exception {
+    Method genericStringInner = getClass().getDeclaredMethod("testGenericInnerLong");
+    TypeName.get(genericStringInner.getReturnType());
+    TypeName genericTypeName = TypeName.get(genericStringInner.getGenericReturnType());
+    assertNotEquals(TypeName.get(genericStringInner.getGenericReturnType()),
+        TypeName.get(getClass().getDeclaredMethod("testGenericInnerInt").getGenericReturnType()));
+
+    // Make sure the generic argument is present
+    assertThat(genericTypeName.toString()).isEqualTo(
+        TestGeneric.class.getCanonicalName() + "<java.lang.Short>.InnerGeneric<java.lang.Long>");
+  }
+
+  @Test public void innerStaticInGenericType() throws Exception {
+    Method staticInGeneric = getClass().getDeclaredMethod("testNestedNonGeneric");
+    TypeName.get(staticInGeneric.getReturnType());
+    TypeName typeName = TypeName.get(staticInGeneric.getGenericReturnType());
+
+    // Make sure there are no generic arguments
+    assertThat(typeName.toString()).isEqualTo(
+        TestGeneric.class.getCanonicalName() + ".NestedNonGeneric");
+  }
+
+  @Test public void equalsAndHashCodePrimitive() {
+    assertEqualsHashCodeAndToString(TypeName.BOOLEAN, TypeName.BOOLEAN);
+    assertEqualsHashCodeAndToString(TypeName.BYTE, TypeName.BYTE);
+    assertEqualsHashCodeAndToString(TypeName.CHAR, TypeName.CHAR);
+    assertEqualsHashCodeAndToString(TypeName.DOUBLE, TypeName.DOUBLE);
+    assertEqualsHashCodeAndToString(TypeName.FLOAT, TypeName.FLOAT);
+    assertEqualsHashCodeAndToString(TypeName.INT, TypeName.INT);
+    assertEqualsHashCodeAndToString(TypeName.LONG, TypeName.LONG);
+    assertEqualsHashCodeAndToString(TypeName.SHORT, TypeName.SHORT);
+    assertEqualsHashCodeAndToString(TypeName.VOID, TypeName.VOID);
+  }
+
+  @Test public void equalsAndHashCodeArrayTypeName() {
+    assertEqualsHashCodeAndToString(ArrayTypeName.of(Object.class),
+        ArrayTypeName.of(Object.class));
+    assertEqualsHashCodeAndToString(TypeName.get(Object[].class),
+        ArrayTypeName.of(Object.class));
+  }
+
+  @Test public void equalsAndHashCodeClassName() {
+    assertEqualsHashCodeAndToString(ClassName.get(Object.class), ClassName.get(Object.class));
+    assertEqualsHashCodeAndToString(TypeName.get(Object.class), ClassName.get(Object.class));
+    assertEqualsHashCodeAndToString(ClassName.bestGuess("java.lang.Object"),
+        ClassName.get(Object.class));
+  }
+
+  @Test public void equalsAndHashCodeParameterizedTypeName() {
+    assertEqualsHashCodeAndToString(ParameterizedTypeName.get(Object.class),
+        ParameterizedTypeName.get(Object.class));
+    assertEqualsHashCodeAndToString(ParameterizedTypeName.get(Set.class, UUID.class),
+        ParameterizedTypeName.get(Set.class, UUID.class));
+    assertNotEquals(ClassName.get(List.class), ParameterizedTypeName.get(List.class,
+        String.class));
+  }
+
+  @Test public void equalsAndHashCodeTypeVariableName() {
+    assertEqualsHashCodeAndToString(TypeVariableName.get(Object.class),
+        TypeVariableName.get(Object.class));
+    TypeVariableName typeVar1 = TypeVariableName.get("T", Comparator.class, Serializable.class);
+    TypeVariableName typeVar2 = TypeVariableName.get("T", Comparator.class, Serializable.class);
+    assertEqualsHashCodeAndToString(typeVar1, typeVar2);
+  }
+
+  @Test public void equalsAndHashCodeWildcardTypeName() {
+    assertEqualsHashCodeAndToString(WildcardTypeName.subtypeOf(Object.class),
+        WildcardTypeName.subtypeOf(Object.class));
+    assertEqualsHashCodeAndToString(WildcardTypeName.subtypeOf(Serializable.class),
+        WildcardTypeName.subtypeOf(Serializable.class));
+    assertEqualsHashCodeAndToString(WildcardTypeName.supertypeOf(String.class),
+        WildcardTypeName.supertypeOf(String.class));
+  }
+
+  @Test public void isPrimitive() throws Exception {
+    assertThat(TypeName.INT.isPrimitive()).isTrue();
+    assertThat(ClassName.get("java.lang", "Integer").isPrimitive()).isFalse();
+    assertThat(ClassName.get("java.lang", "String").isPrimitive()).isFalse();
+    assertThat(TypeName.VOID.isPrimitive()).isFalse();
+    assertThat(ClassName.get("java.lang", "Void").isPrimitive()).isFalse();
+  }
+
+  @Test public void isBoxedPrimitive() throws Exception {
+    assertThat(TypeName.INT.isBoxedPrimitive()).isFalse();
+    assertThat(ClassName.get("java.lang", "Integer").isBoxedPrimitive()).isTrue();
+    assertThat(ClassName.get("java.lang", "String").isBoxedPrimitive()).isFalse();
+    assertThat(TypeName.VOID.isBoxedPrimitive()).isFalse();
+    assertThat(ClassName.get("java.lang", "Void").isBoxedPrimitive()).isFalse();
+  }
+
+  private void assertEqualsHashCodeAndToString(TypeName a, TypeName b) {
+    assertEquals(a.toString(), b.toString());
+    assertThat(a.equals(b)).isTrue();
+    assertThat(a.hashCode()).isEqualTo(b.hashCode());
+    assertFalse(a.equals(null));
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/TypeSpecTest.java b/src/test/java/com/squareup/javapoet/TypeSpecTest.java
new file mode 100644
index 0000000..9cd22c2
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/TypeSpecTest.java
@@ -0,0 +1,2359 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.javapoet;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.testing.compile.CompilationRule;
+import java.io.IOException;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EventListener;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+@RunWith(JUnit4.class)
+public final class TypeSpecTest {
+  private final String tacosPackage = "com.squareup.tacos";
+  private static final String donutsPackage = "com.squareup.donuts";
+
+  @Rule public final CompilationRule compilation = new CompilationRule();
+
+  private TypeElement getElement(Class<?> clazz) {
+    return compilation.getElements().getTypeElement(clazz.getCanonicalName());
+  }
+
+  @Test public void basic() throws Exception {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addMethod(MethodSpec.methodBuilder("toString")
+            .addAnnotation(Override.class)
+            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+            .returns(String.class)
+            .addCode("return $S;\n", "taco")
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Override;\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  @Override\n"
+        + "  public final String toString() {\n"
+        + "    return \"taco\";\n"
+        + "  }\n"
+        + "}\n");
+    assertEquals(472949424, taco.hashCode()); // update expected number if source changes
+  }
+
+  @Test public void interestingTypes() throws Exception {
+    TypeName listOfAny = ParameterizedTypeName.get(
+        ClassName.get(List.class), WildcardTypeName.subtypeOf(Object.class));
+    TypeName listOfExtends = ParameterizedTypeName.get(
+        ClassName.get(List.class), WildcardTypeName.subtypeOf(Serializable.class));
+    TypeName listOfSuper = ParameterizedTypeName.get(ClassName.get(List.class),
+        WildcardTypeName.supertypeOf(String.class));
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addField(listOfAny, "extendsObject")
+        .addField(listOfExtends, "extendsSerializable")
+        .addField(listOfSuper, "superString")
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.io.Serializable;\n"
+        + "import java.lang.String;\n"
+        + "import java.util.List;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  List<?> extendsObject;\n"
+        + "\n"
+        + "  List<? extends Serializable> extendsSerializable;\n"
+        + "\n"
+        + "  List<? super String> superString;\n"
+        + "}\n");
+  }
+
+  @Test public void anonymousInnerClass() throws Exception {
+    ClassName foo = ClassName.get(tacosPackage, "Foo");
+    ClassName bar = ClassName.get(tacosPackage, "Bar");
+    ClassName thingThang = ClassName.get(tacosPackage, "Thing", "Thang");
+    TypeName thingThangOfFooBar = ParameterizedTypeName.get(thingThang, foo, bar);
+    ClassName thung = ClassName.get(tacosPackage, "Thung");
+    ClassName simpleThung = ClassName.get(tacosPackage, "SimpleThung");
+    TypeName thungOfSuperBar = ParameterizedTypeName.get(thung, WildcardTypeName.supertypeOf(bar));
+    TypeName thungOfSuperFoo = ParameterizedTypeName.get(thung, WildcardTypeName.supertypeOf(foo));
+    TypeName simpleThungOfBar = ParameterizedTypeName.get(simpleThung, bar);
+
+    ParameterSpec thungParameter = ParameterSpec.builder(thungOfSuperFoo, "thung")
+        .addModifiers(Modifier.FINAL)
+        .build();
+    TypeSpec aSimpleThung = TypeSpec.anonymousClassBuilder(CodeBlock.of("$N", thungParameter))
+        .superclass(simpleThungOfBar)
+        .addMethod(MethodSpec.methodBuilder("doSomething")
+            .addAnnotation(Override.class)
+            .addModifiers(Modifier.PUBLIC)
+            .addParameter(bar, "bar")
+            .addCode("/* code snippets */\n")
+            .build())
+        .build();
+    TypeSpec aThingThang = TypeSpec.anonymousClassBuilder("")
+        .superclass(thingThangOfFooBar)
+        .addMethod(MethodSpec.methodBuilder("call")
+            .addAnnotation(Override.class)
+            .addModifiers(Modifier.PUBLIC)
+            .returns(thungOfSuperBar)
+            .addParameter(thungParameter)
+            .addCode("return $L;\n", aSimpleThung)
+            .build())
+        .build();
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addField(FieldSpec.builder(thingThangOfFooBar, "NAME")
+            .addModifiers(Modifier.STATIC, Modifier.FINAL, Modifier.FINAL)
+            .initializer("$L", aThingThang)
+            .build())
+        .build();
+
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Override;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  static final Thing.Thang<Foo, Bar> NAME = new Thing.Thang<Foo, Bar>() {\n"
+        + "    @Override\n"
+        + "    public Thung<? super Bar> call(final Thung<? super Foo> thung) {\n"
+        + "      return new SimpleThung<Bar>(thung) {\n"
+        + "        @Override\n"
+        + "        public void doSomething(Bar bar) {\n"
+        + "          /* code snippets */\n"
+        + "        }\n"
+        + "      };\n"
+        + "    }\n"
+        + "  };\n"
+        + "}\n");
+  }
+
+  @Test public void annotatedParameters() throws Exception {
+    TypeSpec service = TypeSpec.classBuilder("Foo")
+        .addMethod(MethodSpec.constructorBuilder()
+            .addModifiers(Modifier.PUBLIC)
+            .addParameter(long.class, "id")
+            .addParameter(ParameterSpec.builder(String.class, "one")
+                .addAnnotation(ClassName.get(tacosPackage, "Ping"))
+                .build())
+            .addParameter(ParameterSpec.builder(String.class, "two")
+                .addAnnotation(ClassName.get(tacosPackage, "Ping"))
+                .build())
+            .addParameter(ParameterSpec.builder(String.class, "three")
+                .addAnnotation(AnnotationSpec.builder(ClassName.get(tacosPackage, "Pong"))
+                    .addMember("value", "$S", "pong")
+                    .build())
+                .build())
+            .addParameter(ParameterSpec.builder(String.class, "four")
+                .addAnnotation(ClassName.get(tacosPackage, "Ping"))
+                .build())
+            .addCode("/* code snippets */\n")
+            .build())
+        .build();
+
+    assertThat(toString(service)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  public Foo(long id, @Ping String one, @Ping String two, @Pong(\"pong\") String three,\n"
+        + "      @Ping String four) {\n"
+        + "    /* code snippets */\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  /**
+   * We had a bug where annotations were preventing us from doing the right thing when resolving
+   * imports. https://github.com/square/javapoet/issues/422
+   */
+  @Test public void annotationsAndJavaLangTypes() throws Exception {
+    ClassName freeRange = ClassName.get("javax.annotation", "FreeRange");
+    TypeSpec taco = TypeSpec.classBuilder("EthicalTaco")
+        .addField(ClassName.get(String.class)
+            .annotated(AnnotationSpec.builder(freeRange).build()), "meat")
+        .build();
+
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.String;\n"
+        + "import javax.annotation.FreeRange;\n"
+        + "\n"
+        + "class EthicalTaco {\n"
+        + "  @FreeRange String meat;\n"
+        + "}\n");
+  }
+
+  @Test public void retrofitStyleInterface() throws Exception {
+    ClassName observable = ClassName.get(tacosPackage, "Observable");
+    ClassName fooBar = ClassName.get(tacosPackage, "FooBar");
+    ClassName thing = ClassName.get(tacosPackage, "Thing");
+    ClassName things = ClassName.get(tacosPackage, "Things");
+    ClassName map = ClassName.get("java.util", "Map");
+    ClassName string = ClassName.get("java.lang", "String");
+    ClassName headers = ClassName.get(tacosPackage, "Headers");
+    ClassName post = ClassName.get(tacosPackage, "POST");
+    ClassName body = ClassName.get(tacosPackage, "Body");
+    ClassName queryMap = ClassName.get(tacosPackage, "QueryMap");
+    ClassName header = ClassName.get(tacosPackage, "Header");
+    TypeSpec service = TypeSpec.interfaceBuilder("Service")
+        .addMethod(MethodSpec.methodBuilder("fooBar")
+            .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+            .addAnnotation(AnnotationSpec.builder(headers)
+                .addMember("value", "$S", "Accept: application/json")
+                .addMember("value", "$S", "User-Agent: foobar")
+                .build())
+            .addAnnotation(AnnotationSpec.builder(post)
+                .addMember("value", "$S", "/foo/bar")
+                .build())
+            .returns(ParameterizedTypeName.get(observable, fooBar))
+            .addParameter(ParameterSpec.builder(ParameterizedTypeName.get(things, thing), "things")
+                .addAnnotation(body)
+                .build())
+            .addParameter(ParameterSpec.builder(
+                ParameterizedTypeName.get(map, string, string), "query")
+                .addAnnotation(AnnotationSpec.builder(queryMap)
+                    .addMember("encodeValues", "false")
+                    .build())
+                .build())
+            .addParameter(ParameterSpec.builder(string, "authorization")
+                .addAnnotation(AnnotationSpec.builder(header)
+                    .addMember("value", "$S", "Authorization")
+                    .build())
+                .build())
+            .build())
+        .build();
+
+    assertThat(toString(service)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.String;\n"
+        + "import java.util.Map;\n"
+        + "\n"
+        + "interface Service {\n"
+        + "  @Headers({\n"
+        + "      \"Accept: application/json\",\n"
+        + "      \"User-Agent: foobar\"\n"
+        + "  })\n"
+        + "  @POST(\"/foo/bar\")\n"
+        + "  Observable<FooBar> fooBar(@Body Things<Thing> things,\n"
+        + "      @QueryMap(encodeValues = false) Map<String, String> query,\n"
+        + "      @Header(\"Authorization\") String authorization);\n"
+        + "}\n");
+  }
+
+  @Test public void annotatedField() throws Exception {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addField(FieldSpec.builder(String.class, "thing", Modifier.PRIVATE, Modifier.FINAL)
+            .addAnnotation(AnnotationSpec.builder(ClassName.get(tacosPackage, "JsonAdapter"))
+                .addMember("value", "$T.class", ClassName.get(tacosPackage, "Foo"))
+                .build())
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  @JsonAdapter(Foo.class)\n"
+        + "  private final String thing;\n"
+        + "}\n");
+  }
+
+  @Test public void annotatedClass() throws Exception {
+    ClassName someType = ClassName.get(tacosPackage, "SomeType");
+    TypeSpec taco = TypeSpec.classBuilder("Foo")
+        .addAnnotation(AnnotationSpec.builder(ClassName.get(tacosPackage, "Something"))
+            .addMember("hi", "$T.$N", someType, "FIELD")
+            .addMember("hey", "$L", 12)
+            .addMember("hello", "$S", "goodbye")
+            .build())
+        .addModifiers(Modifier.PUBLIC)
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "@Something(\n"
+        + "    hi = SomeType.FIELD,\n"
+        + "    hey = 12,\n"
+        + "    hello = \"goodbye\"\n"
+        + ")\n"
+        + "public class Foo {\n"
+        + "}\n");
+  }
+
+  @Test public void addAnnotationDisallowsNull() {
+    try {
+      TypeSpec.classBuilder("Foo").addAnnotation((AnnotationSpec) null);
+      fail();
+    } catch (NullPointerException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("annotationSpec == null");
+    }
+    try {
+      TypeSpec.classBuilder("Foo").addAnnotation((ClassName) null);
+      fail();
+    } catch (NullPointerException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("type == null");
+    }
+    try {
+      TypeSpec.classBuilder("Foo").addAnnotation((Class<?>) null);
+      fail();
+    } catch (NullPointerException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("clazz == null");
+    }
+  }
+
+  @Test public void enumWithSubclassing() throws Exception {
+    TypeSpec roshambo = TypeSpec.enumBuilder("Roshambo")
+        .addModifiers(Modifier.PUBLIC)
+        .addEnumConstant("ROCK", TypeSpec.anonymousClassBuilder("")
+            .addJavadoc("Avalanche!\n")
+            .build())
+        .addEnumConstant("PAPER", TypeSpec.anonymousClassBuilder("$S", "flat")
+            .addMethod(MethodSpec.methodBuilder("toString")
+                .addAnnotation(Override.class)
+                .addModifiers(Modifier.PUBLIC)
+                .returns(String.class)
+                .addCode("return $S;\n", "paper airplane!")
+                .build())
+            .build())
+        .addEnumConstant("SCISSORS", TypeSpec.anonymousClassBuilder("$S", "peace sign")
+            .build())
+        .addField(String.class, "handPosition", Modifier.PRIVATE, Modifier.FINAL)
+        .addMethod(MethodSpec.constructorBuilder()
+            .addParameter(String.class, "handPosition")
+            .addCode("this.handPosition = handPosition;\n")
+            .build())
+        .addMethod(MethodSpec.constructorBuilder()
+            .addCode("this($S);\n", "fist")
+            .build())
+        .build();
+    assertThat(toString(roshambo)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Override;\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "public enum Roshambo {\n"
+        + "  /**\n"
+        + "   * Avalanche!\n"
+        + "   */\n"
+        + "  ROCK,\n"
+        + "\n"
+        + "  PAPER(\"flat\") {\n"
+        + "    @Override\n"
+        + "    public String toString() {\n"
+        + "      return \"paper airplane!\";\n"
+        + "    }\n"
+        + "  },\n"
+        + "\n"
+        + "  SCISSORS(\"peace sign\");\n"
+        + "\n"
+        + "  private final String handPosition;\n"
+        + "\n"
+        + "  Roshambo(String handPosition) {\n"
+        + "    this.handPosition = handPosition;\n"
+        + "  }\n"
+        + "\n"
+        + "  Roshambo() {\n"
+        + "    this(\"fist\");\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  /** https://github.com/square/javapoet/issues/193 */
+  @Test public void enumsMayDefineAbstractMethods() throws Exception {
+    TypeSpec roshambo = TypeSpec.enumBuilder("Tortilla")
+        .addModifiers(Modifier.PUBLIC)
+        .addEnumConstant("CORN", TypeSpec.anonymousClassBuilder("")
+            .addMethod(MethodSpec.methodBuilder("fold")
+                .addAnnotation(Override.class)
+                .addModifiers(Modifier.PUBLIC)
+                .build())
+            .build())
+        .addMethod(MethodSpec.methodBuilder("fold")
+            .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+            .build())
+        .build();
+    assertThat(toString(roshambo)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Override;\n"
+        + "\n"
+        + "public enum Tortilla {\n"
+        + "  CORN {\n"
+        + "    @Override\n"
+        + "    public void fold() {\n"
+        + "    }\n"
+        + "  };\n"
+        + "\n"
+        + "  public abstract void fold();\n"
+        + "}\n");
+  }
+
+  @Test public void enumConstantsRequired() throws Exception {
+    try {
+      TypeSpec.enumBuilder("Roshambo")
+          .build();
+      fail();
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  @Test public void onlyEnumsMayHaveEnumConstants() throws Exception {
+    try {
+      TypeSpec.classBuilder("Roshambo")
+          .addEnumConstant("ROCK")
+          .build();
+      fail();
+    } catch (IllegalStateException expected) {
+    }
+  }
+
+  @Test public void enumWithMembersButNoConstructorCall() throws Exception {
+    TypeSpec roshambo = TypeSpec.enumBuilder("Roshambo")
+        .addEnumConstant("SPOCK", TypeSpec.anonymousClassBuilder("")
+            .addMethod(MethodSpec.methodBuilder("toString")
+                .addAnnotation(Override.class)
+                .addModifiers(Modifier.PUBLIC)
+                .returns(String.class)
+                .addCode("return $S;\n", "west side")
+                .build())
+            .build())
+        .build();
+    assertThat(toString(roshambo)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Override;\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "enum Roshambo {\n"
+        + "  SPOCK {\n"
+        + "    @Override\n"
+        + "    public String toString() {\n"
+        + "      return \"west side\";\n"
+        + "    }\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  /** https://github.com/square/javapoet/issues/253 */
+  @Test public void enumWithAnnotatedValues() throws Exception {
+    TypeSpec roshambo = TypeSpec.enumBuilder("Roshambo")
+        .addModifiers(Modifier.PUBLIC)
+        .addEnumConstant("ROCK", TypeSpec.anonymousClassBuilder("")
+            .addAnnotation(Deprecated.class)
+            .build())
+        .addEnumConstant("PAPER")
+        .addEnumConstant("SCISSORS")
+        .build();
+    assertThat(toString(roshambo)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Deprecated;\n"
+        + "\n"
+        + "public enum Roshambo {\n"
+        + "  @Deprecated\n"
+        + "  ROCK,\n"
+        + "\n"
+        + "  PAPER,\n"
+        + "\n"
+        + "  SCISSORS\n"
+        + "}\n");
+  }
+
+  @Test public void methodThrows() throws Exception {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addModifiers(Modifier.ABSTRACT)
+        .addMethod(MethodSpec.methodBuilder("throwOne")
+            .addException(IOException.class)
+            .build())
+        .addMethod(MethodSpec.methodBuilder("throwTwo")
+            .addException(IOException.class)
+            .addException(ClassName.get(tacosPackage, "SourCreamException"))
+            .build())
+        .addMethod(MethodSpec.methodBuilder("abstractThrow")
+            .addModifiers(Modifier.ABSTRACT)
+            .addException(IOException.class)
+            .build())
+        .addMethod(MethodSpec.methodBuilder("nativeThrow")
+            .addModifiers(Modifier.NATIVE)
+            .addException(IOException.class)
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.io.IOException;\n"
+        + "\n"
+        + "abstract class Taco {\n"
+        + "  void throwOne() throws IOException {\n"
+        + "  }\n"
+        + "\n"
+        + "  void throwTwo() throws IOException, SourCreamException {\n"
+        + "  }\n"
+        + "\n"
+        + "  abstract void abstractThrow() throws IOException;\n"
+        + "\n"
+        + "  native void nativeThrow() throws IOException;\n"
+        + "}\n");
+  }
+
+  @Test public void typeVariables() throws Exception {
+    TypeVariableName t = TypeVariableName.get("T");
+    TypeVariableName p = TypeVariableName.get("P", Number.class);
+    ClassName location = ClassName.get(tacosPackage, "Location");
+    TypeSpec typeSpec = TypeSpec.classBuilder("Location")
+        .addTypeVariable(t)
+        .addTypeVariable(p)
+        .addSuperinterface(ParameterizedTypeName.get(ClassName.get(Comparable.class), p))
+        .addField(t, "label")
+        .addField(p, "x")
+        .addField(p, "y")
+        .addMethod(MethodSpec.methodBuilder("compareTo")
+            .addAnnotation(Override.class)
+            .addModifiers(Modifier.PUBLIC)
+            .returns(int.class)
+            .addParameter(p, "p")
+            .addCode("return 0;\n")
+            .build())
+        .addMethod(MethodSpec.methodBuilder("of")
+            .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+            .addTypeVariable(t)
+            .addTypeVariable(p)
+            .returns(ParameterizedTypeName.get(location, t, p))
+            .addParameter(t, "label")
+            .addParameter(p, "x")
+            .addParameter(p, "y")
+            .addCode("throw new $T($S);\n", UnsupportedOperationException.class, "TODO")
+            .build())
+        .build();
+    assertThat(toString(typeSpec)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Comparable;\n"
+        + "import java.lang.Number;\n"
+        + "import java.lang.Override;\n"
+        + "import java.lang.UnsupportedOperationException;\n"
+        + "\n"
+        + "class Location<T, P extends Number> implements Comparable<P> {\n"
+        + "  T label;\n"
+        + "\n"
+        + "  P x;\n"
+        + "\n"
+        + "  P y;\n"
+        + "\n"
+        + "  @Override\n"
+        + "  public int compareTo(P p) {\n"
+        + "    return 0;\n"
+        + "  }\n"
+        + "\n"
+        + "  public static <T, P extends Number> Location<T, P> of(T label, P x, P y) {\n"
+        + "    throw new UnsupportedOperationException(\"TODO\");\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void typeVariableWithBounds() {
+    AnnotationSpec a = AnnotationSpec.builder(ClassName.get("com.squareup.tacos", "A")).build();
+    TypeVariableName p = TypeVariableName.get("P", Number.class);
+    TypeVariableName q = (TypeVariableName) TypeVariableName.get("Q", Number.class).annotated(a);
+    TypeSpec typeSpec = TypeSpec.classBuilder("Location")
+        .addTypeVariable(p.withBounds(Comparable.class))
+        .addTypeVariable(q.withBounds(Comparable.class))
+        .addField(p, "x")
+        .addField(q, "y")
+        .build();
+    assertThat(toString(typeSpec)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Comparable;\n"
+        + "import java.lang.Number;\n"
+        + "\n"
+        + "class Location<P extends Number & Comparable, @A Q extends Number & Comparable> {\n"
+        + "  P x;\n"
+        + "\n"
+        + "  @A Q y;\n"
+        + "}\n");
+  }
+
+  @Test public void classImplementsExtends() throws Exception {
+    ClassName taco = ClassName.get(tacosPackage, "Taco");
+    ClassName food = ClassName.get("com.squareup.tacos", "Food");
+    TypeSpec typeSpec = TypeSpec.classBuilder("Taco")
+        .addModifiers(Modifier.ABSTRACT)
+        .superclass(ParameterizedTypeName.get(ClassName.get(AbstractSet.class), food))
+        .addSuperinterface(Serializable.class)
+        .addSuperinterface(ParameterizedTypeName.get(ClassName.get(Comparable.class), taco))
+        .build();
+    assertThat(toString(typeSpec)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.io.Serializable;\n"
+        + "import java.lang.Comparable;\n"
+        + "import java.util.AbstractSet;\n"
+        + "\n"
+        + "abstract class Taco extends AbstractSet<Food> "
+        + "implements Serializable, Comparable<Taco> {\n"
+        + "}\n");
+  }
+
+  @Test public void classImplementsNestedClass() throws Exception {
+    ClassName outer = ClassName.get(tacosPackage, "Outer");
+    ClassName inner = outer.nestedClass("Inner");
+    ClassName callable = ClassName.get(Callable.class);
+    TypeSpec typeSpec = TypeSpec.classBuilder("Outer")
+        .superclass(ParameterizedTypeName.get(callable,
+            inner))
+        .addType(TypeSpec.classBuilder("Inner")
+            .addModifiers(Modifier.STATIC)
+            .build())
+        .build();
+
+    assertThat(toString(typeSpec)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.util.concurrent.Callable;\n"
+        + "\n"
+        + "class Outer extends Callable<Outer.Inner> {\n"
+        + "  static class Inner {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void enumImplements() throws Exception {
+    TypeSpec typeSpec = TypeSpec.enumBuilder("Food")
+        .addSuperinterface(Serializable.class)
+        .addSuperinterface(Cloneable.class)
+        .addEnumConstant("LEAN_GROUND_BEEF")
+        .addEnumConstant("SHREDDED_CHEESE")
+        .build();
+    assertThat(toString(typeSpec)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.io.Serializable;\n"
+        + "import java.lang.Cloneable;\n"
+        + "\n"
+        + "enum Food implements Serializable, Cloneable {\n"
+        + "  LEAN_GROUND_BEEF,\n"
+        + "\n"
+        + "  SHREDDED_CHEESE\n"
+        + "}\n");
+  }
+
+  @Test public void interfaceExtends() throws Exception {
+    ClassName taco = ClassName.get(tacosPackage, "Taco");
+    TypeSpec typeSpec = TypeSpec.interfaceBuilder("Taco")
+        .addSuperinterface(Serializable.class)
+        .addSuperinterface(ParameterizedTypeName.get(ClassName.get(Comparable.class), taco))
+        .build();
+    assertThat(toString(typeSpec)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.io.Serializable;\n"
+        + "import java.lang.Comparable;\n"
+        + "\n"
+        + "interface Taco extends Serializable, Comparable<Taco> {\n"
+        + "}\n");
+  }
+
+  @Test public void nestedClasses() throws Exception {
+    ClassName taco = ClassName.get(tacosPackage, "Combo", "Taco");
+    ClassName topping = ClassName.get(tacosPackage, "Combo", "Taco", "Topping");
+    ClassName chips = ClassName.get(tacosPackage, "Combo", "Chips");
+    ClassName sauce = ClassName.get(tacosPackage, "Combo", "Sauce");
+    TypeSpec typeSpec = TypeSpec.classBuilder("Combo")
+        .addField(taco, "taco")
+        .addField(chips, "chips")
+        .addType(TypeSpec.classBuilder(taco.simpleName())
+            .addModifiers(Modifier.STATIC)
+            .addField(ParameterizedTypeName.get(ClassName.get(List.class), topping), "toppings")
+            .addField(sauce, "sauce")
+            .addType(TypeSpec.enumBuilder(topping.simpleName())
+                .addEnumConstant("SHREDDED_CHEESE")
+                .addEnumConstant("LEAN_GROUND_BEEF")
+                .build())
+            .build())
+        .addType(TypeSpec.classBuilder(chips.simpleName())
+            .addModifiers(Modifier.STATIC)
+            .addField(topping, "topping")
+            .addField(sauce, "dippingSauce")
+            .build())
+        .addType(TypeSpec.enumBuilder(sauce.simpleName())
+            .addEnumConstant("SOUR_CREAM")
+            .addEnumConstant("SALSA")
+            .addEnumConstant("QUESO")
+            .addEnumConstant("MILD")
+            .addEnumConstant("FIRE")
+            .build())
+        .build();
+
+    assertThat(toString(typeSpec)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.util.List;\n"
+        + "\n"
+        + "class Combo {\n"
+        + "  Taco taco;\n"
+        + "\n"
+        + "  Chips chips;\n"
+        + "\n"
+        + "  static class Taco {\n"
+        + "    List<Topping> toppings;\n"
+        + "\n"
+        + "    Sauce sauce;\n"
+        + "\n"
+        + "    enum Topping {\n"
+        + "      SHREDDED_CHEESE,\n"
+        + "\n"
+        + "      LEAN_GROUND_BEEF\n"
+        + "    }\n"
+        + "  }\n"
+        + "\n"
+        + "  static class Chips {\n"
+        + "    Taco.Topping topping;\n"
+        + "\n"
+        + "    Sauce dippingSauce;\n"
+        + "  }\n"
+        + "\n"
+        + "  enum Sauce {\n"
+        + "    SOUR_CREAM,\n"
+        + "\n"
+        + "    SALSA,\n"
+        + "\n"
+        + "    QUESO,\n"
+        + "\n"
+        + "    MILD,\n"
+        + "\n"
+        + "    FIRE\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void annotation() throws Exception {
+    TypeSpec annotation = TypeSpec.annotationBuilder("MyAnnotation")
+        .addModifiers(Modifier.PUBLIC)
+        .addMethod(MethodSpec.methodBuilder("test")
+            .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+            .defaultValue("$L", 0)
+            .returns(int.class)
+            .build())
+        .build();
+
+    assertThat(toString(annotation)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "public @interface MyAnnotation {\n"
+        + "  int test() default 0;\n"
+        + "}\n"
+    );
+  }
+
+  @Test public void innerAnnotationInAnnotationDeclaration() throws Exception {
+    TypeSpec bar = TypeSpec.annotationBuilder("Bar")
+        .addMethod(MethodSpec.methodBuilder("value")
+            .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+            .defaultValue("@$T", Deprecated.class)
+            .returns(Deprecated.class)
+            .build())
+        .build();
+
+    assertThat(toString(bar)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Deprecated;\n"
+        + "\n"
+        + "@interface Bar {\n"
+        + "  Deprecated value() default @Deprecated;\n"
+        + "}\n"
+    );
+  }
+
+  @Test public void annotationWithFields() {
+    FieldSpec field = FieldSpec.builder(int.class, "FOO")
+        .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
+        .initializer("$L", 101)
+        .build();
+
+    TypeSpec anno = TypeSpec.annotationBuilder("Anno")
+        .addField(field)
+        .build();
+
+    assertThat(toString(anno)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "@interface Anno {\n"
+        + "  int FOO = 101;\n"
+        + "}\n"
+    );
+  }
+
+  @Test
+  public void classCannotHaveDefaultValueForMethod() throws Exception {
+    try {
+      TypeSpec.classBuilder("Tacos")
+          .addMethod(MethodSpec.methodBuilder("test")
+              .addModifiers(Modifier.PUBLIC)
+              .defaultValue("0")
+              .returns(int.class)
+              .build())
+          .build();
+      fail();
+    } catch (IllegalStateException expected) {
+    }
+  }
+
+  @Test
+  public void classCannotHaveDefaultMethods() throws Exception {
+    try {
+      TypeSpec.classBuilder("Tacos")
+          .addMethod(MethodSpec.methodBuilder("test")
+              .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
+              .returns(int.class)
+              .addCode(CodeBlock.builder().addStatement("return 0").build())
+              .build())
+          .build();
+      fail();
+    } catch (IllegalStateException expected) {
+    }
+  }
+
+  @Test
+  public void interfaceStaticMethods() throws Exception {
+    TypeSpec bar = TypeSpec.interfaceBuilder("Tacos")
+        .addMethod(MethodSpec.methodBuilder("test")
+            .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+            .returns(int.class)
+            .addCode(CodeBlock.builder().addStatement("return 0").build())
+            .build())
+        .build();
+
+    assertThat(toString(bar)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "interface Tacos {\n"
+        + "  static int test() {\n"
+        + "    return 0;\n"
+        + "  }\n"
+        + "}\n"
+    );
+  }
+
+  @Test
+  public void interfaceDefaultMethods() throws Exception {
+    TypeSpec bar = TypeSpec.interfaceBuilder("Tacos")
+        .addMethod(MethodSpec.methodBuilder("test")
+            .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
+            .returns(int.class)
+            .addCode(CodeBlock.builder().addStatement("return 0").build())
+            .build())
+        .build();
+
+    assertThat(toString(bar)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "interface Tacos {\n"
+        + "  default int test() {\n"
+        + "    return 0;\n"
+        + "  }\n"
+        + "}\n"
+    );
+  }
+
+  @Test public void referencedAndDeclaredSimpleNamesConflict() throws Exception {
+    FieldSpec internalTop = FieldSpec.builder(
+        ClassName.get(tacosPackage, "Top"), "internalTop").build();
+    FieldSpec internalBottom = FieldSpec.builder(
+        ClassName.get(tacosPackage, "Top", "Middle", "Bottom"), "internalBottom").build();
+    FieldSpec externalTop = FieldSpec.builder(
+        ClassName.get(donutsPackage, "Top"), "externalTop").build();
+    FieldSpec externalBottom = FieldSpec.builder(
+        ClassName.get(donutsPackage, "Bottom"), "externalBottom").build();
+    TypeSpec top = TypeSpec.classBuilder("Top")
+        .addField(internalTop)
+        .addField(internalBottom)
+        .addField(externalTop)
+        .addField(externalBottom)
+        .addType(TypeSpec.classBuilder("Middle")
+            .addField(internalTop)
+            .addField(internalBottom)
+            .addField(externalTop)
+            .addField(externalBottom)
+            .addType(TypeSpec.classBuilder("Bottom")
+                .addField(internalTop)
+                .addField(internalBottom)
+                .addField(externalTop)
+                .addField(externalBottom)
+                .build())
+            .build())
+        .build();
+    assertThat(toString(top)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import com.squareup.donuts.Bottom;\n"
+        + "\n"
+        + "class Top {\n"
+        + "  Top internalTop;\n"
+        + "\n"
+        + "  Middle.Bottom internalBottom;\n"
+        + "\n"
+        + "  com.squareup.donuts.Top externalTop;\n"
+        + "\n"
+        + "  Bottom externalBottom;\n"
+        + "\n"
+        + "  class Middle {\n"
+        + "    Top internalTop;\n"
+        + "\n"
+        + "    Bottom internalBottom;\n"
+        + "\n"
+        + "    com.squareup.donuts.Top externalTop;\n"
+        + "\n"
+        + "    com.squareup.donuts.Bottom externalBottom;\n"
+        + "\n"
+        + "    class Bottom {\n"
+        + "      Top internalTop;\n"
+        + "\n"
+        + "      Bottom internalBottom;\n"
+        + "\n"
+        + "      com.squareup.donuts.Top externalTop;\n"
+        + "\n"
+        + "      com.squareup.donuts.Bottom externalBottom;\n"
+        + "    }\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void simpleNamesConflictInThisAndOtherPackage() throws Exception {
+    FieldSpec internalOther = FieldSpec.builder(
+        ClassName.get(tacosPackage, "Other"), "internalOther").build();
+    FieldSpec externalOther = FieldSpec.builder(
+        ClassName.get(donutsPackage, "Other"), "externalOther").build();
+    TypeSpec gen = TypeSpec.classBuilder("Gen")
+        .addField(internalOther)
+        .addField(externalOther)
+        .build();
+    assertThat(toString(gen)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Gen {\n"
+        + "  Other internalOther;\n"
+        + "\n"
+        + "  com.squareup.donuts.Other externalOther;\n"
+        + "}\n");
+  }
+
+  @Test public void originatingElementsIncludesThoseOfNestedTypes() {
+    Element outerElement = Mockito.mock(Element.class);
+    Element innerElement = Mockito.mock(Element.class);
+    TypeSpec outer = TypeSpec.classBuilder("Outer")
+        .addOriginatingElement(outerElement)
+        .addType(TypeSpec.classBuilder("Inner")
+            .addOriginatingElement(innerElement)
+            .build())
+        .build();
+    assertThat(outer.originatingElements).containsExactly(outerElement, innerElement);
+  }
+
+  @Test public void intersectionType() {
+    TypeVariableName typeVariable = TypeVariableName.get("T", Comparator.class, Serializable.class);
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addMethod(MethodSpec.methodBuilder("getComparator")
+            .addTypeVariable(typeVariable)
+            .returns(typeVariable)
+            .addCode("return null;\n")
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.io.Serializable;\n"
+        + "import java.util.Comparator;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  <T extends Comparator & Serializable> T getComparator() {\n"
+        + "    return null;\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void arrayType() {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addField(int[].class, "ints")
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  int[] ints;\n"
+        + "}\n");
+  }
+
+  @Test public void javadoc() {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addJavadoc("A hard or soft tortilla, loosely folded and filled with whatever {@link \n")
+        .addJavadoc("{@link $T random} tex-mex stuff we could find in the pantry\n", Random.class)
+        .addJavadoc(CodeBlock.of("and some {@link $T} cheese.\n", String.class))
+        .addField(FieldSpec.builder(boolean.class, "soft")
+            .addJavadoc("True for a soft flour tortilla; false for a crunchy corn tortilla.\n")
+            .build())
+        .addMethod(MethodSpec.methodBuilder("refold")
+            .addJavadoc("Folds the back of this taco to reduce sauce leakage.\n"
+                + "\n"
+                + "<p>For {@link $T#KOREAN}, the front may also be folded.\n", Locale.class)
+            .addParameter(Locale.class, "locale")
+            .build())
+        .build();
+    // Mentioning a type in Javadoc will not cause an import to be added (java.util.Random here),
+    // but the short name will be used if it's already imported (java.util.Locale here).
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.util.Locale;\n"
+        + "\n"
+        + "/**\n"
+        + " * A hard or soft tortilla, loosely folded and filled with whatever {@link \n"
+        + " * {@link java.util.Random random} tex-mex stuff we could find in the pantry\n"
+        + " * and some {@link java.lang.String} cheese.\n"
+        + " */\n"
+        + "class Taco {\n"
+        + "  /**\n"
+        + "   * True for a soft flour tortilla; false for a crunchy corn tortilla.\n"
+        + "   */\n"
+        + "  boolean soft;\n"
+        + "\n"
+        + "  /**\n"
+        + "   * Folds the back of this taco to reduce sauce leakage.\n"
+        + "   *\n"
+        + "   * <p>For {@link Locale#KOREAN}, the front may also be folded.\n"
+        + "   */\n"
+        + "  void refold(Locale locale) {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void annotationsInAnnotations() throws Exception {
+    ClassName beef = ClassName.get(tacosPackage, "Beef");
+    ClassName chicken = ClassName.get(tacosPackage, "Chicken");
+    ClassName option = ClassName.get(tacosPackage, "Option");
+    ClassName mealDeal = ClassName.get(tacosPackage, "MealDeal");
+    TypeSpec menu = TypeSpec.classBuilder("Menu")
+        .addAnnotation(AnnotationSpec.builder(mealDeal)
+            .addMember("price", "$L", 500)
+            .addMember("options", "$L", AnnotationSpec.builder(option)
+                .addMember("name", "$S", "taco")
+                .addMember("meat", "$T.class", beef)
+                .build())
+            .addMember("options", "$L", AnnotationSpec.builder(option)
+                .addMember("name", "$S", "quesadilla")
+                .addMember("meat", "$T.class", chicken)
+                .build())
+            .build())
+        .build();
+    assertThat(toString(menu)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "@MealDeal(\n"
+        + "    price = 500,\n"
+        + "    options = {\n"
+        + "        @Option(name = \"taco\", meat = Beef.class),\n"
+        + "        @Option(name = \"quesadilla\", meat = Chicken.class)\n"
+        + "    }\n"
+        + ")\n"
+        + "class Menu {\n"
+        + "}\n");
+  }
+
+  @Test public void varargs() throws Exception {
+    TypeSpec taqueria = TypeSpec.classBuilder("Taqueria")
+        .addMethod(MethodSpec.methodBuilder("prepare")
+            .addParameter(int.class, "workers")
+            .addParameter(Runnable[].class, "jobs")
+            .varargs()
+            .build())
+        .build();
+    assertThat(toString(taqueria)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Runnable;\n"
+        + "\n"
+        + "class Taqueria {\n"
+        + "  void prepare(int workers, Runnable... jobs) {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void codeBlocks() throws Exception {
+    CodeBlock ifBlock = CodeBlock.builder()
+        .beginControlFlow("if (!a.equals(b))")
+        .addStatement("return i")
+        .endControlFlow()
+        .build();
+    CodeBlock methodBody = CodeBlock.builder()
+        .addStatement("$T size = $T.min(listA.size(), listB.size())", int.class, Math.class)
+        .beginControlFlow("for ($T i = 0; i < size; i++)", int.class)
+        .addStatement("$T $N = $N.get(i)", String.class, "a", "listA")
+        .addStatement("$T $N = $N.get(i)", String.class, "b", "listB")
+        .add("$L", ifBlock)
+        .endControlFlow()
+        .addStatement("return size")
+        .build();
+    CodeBlock fieldBlock = CodeBlock.builder()
+        .add("$>$>")
+        .add("\n$T.<$T, $T>builder()$>$>", ImmutableMap.class, String.class, String.class)
+        .add("\n.add($S, $S)", '\'', "&#39;")
+        .add("\n.add($S, $S)", '&', "&amp;")
+        .add("\n.add($S, $S)", '<', "&lt;")
+        .add("\n.add($S, $S)", '>', "&gt;")
+        .add("\n.build()$<$<")
+        .add("$<$<")
+        .build();
+    FieldSpec escapeHtml = FieldSpec.builder(ParameterizedTypeName.get(
+        Map.class, String.class, String.class), "ESCAPE_HTML")
+        .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
+        .initializer(fieldBlock)
+        .build();
+    TypeSpec util = TypeSpec.classBuilder("Util")
+        .addField(escapeHtml)
+        .addMethod(MethodSpec.methodBuilder("commonPrefixLength")
+            .returns(int.class)
+            .addParameter(ParameterizedTypeName.get(List.class, String.class), "listA")
+            .addParameter(ParameterizedTypeName.get(List.class, String.class), "listB")
+            .addCode(methodBody)
+            .build())
+        .build();
+    assertThat(toString(util)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import com.google.common.collect.ImmutableMap;\n"
+        + "import java.lang.Math;\n"
+        + "import java.lang.String;\n"
+        + "import java.util.List;\n"
+        + "import java.util.Map;\n"
+        + "\n"
+        + "class Util {\n"
+        + "  private static final Map<String, String> ESCAPE_HTML = \n"
+        + "      ImmutableMap.<String, String>builder()\n"
+        + "          .add(\"\'\", \"&#39;\")\n"
+        + "          .add(\"&\", \"&amp;\")\n"
+        + "          .add(\"<\", \"&lt;\")\n"
+        + "          .add(\">\", \"&gt;\")\n"
+        + "          .build();\n"
+        + "\n"
+        + "  int commonPrefixLength(List<String> listA, List<String> listB) {\n"
+        + "    int size = Math.min(listA.size(), listB.size());\n"
+        + "    for (int i = 0; i < size; i++) {\n"
+        + "      String a = listA.get(i);\n"
+        + "      String b = listB.get(i);\n"
+        + "      if (!a.equals(b)) {\n"
+        + "        return i;\n"
+        + "      }\n"
+        + "    }\n"
+        + "    return size;\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void indexedElseIf() throws Exception {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addMethod(MethodSpec.methodBuilder("choices")
+            .beginControlFlow("if ($1L != null || $1L == $2L)", "taco", "otherTaco")
+            .addStatement("$T.out.println($S)", System.class, "only one taco? NOO!")
+            .nextControlFlow("else if ($1L.$3L && $2L.$3L)", "taco", "otherTaco", "isSupreme()")
+            .addStatement("$T.out.println($S)", System.class, "taco heaven")
+            .endControlFlow()
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.System;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  void choices() {\n"
+        + "    if (taco != null || taco == otherTaco) {\n"
+        + "      System.out.println(\"only one taco? NOO!\");\n"
+        + "    } else if (taco.isSupreme() && otherTaco.isSupreme()) {\n"
+        + "      System.out.println(\"taco heaven\");\n"
+        + "    }\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void elseIf() throws Exception {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addMethod(MethodSpec.methodBuilder("choices")
+            .beginControlFlow("if (5 < 4) ")
+            .addStatement("$T.out.println($S)", System.class, "wat")
+            .nextControlFlow("else if (5 < 6)")
+            .addStatement("$T.out.println($S)", System.class, "hello")
+            .endControlFlow()
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.System;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  void choices() {\n"
+        + "    if (5 < 4)  {\n"
+        + "      System.out.println(\"wat\");\n"
+        + "    } else if (5 < 6) {\n"
+        + "      System.out.println(\"hello\");\n"
+        + "    }\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void doWhile() throws Exception {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addMethod(MethodSpec.methodBuilder("loopForever")
+            .beginControlFlow("do")
+            .addStatement("$T.out.println($S)", System.class, "hello")
+            .endControlFlow("while (5 < 6)")
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.System;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  void loopForever() {\n"
+        + "    do {\n"
+        + "      System.out.println(\"hello\");\n"
+        + "    } while (5 < 6);\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void inlineIndent() throws Exception {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addMethod(MethodSpec.methodBuilder("inlineIndent")
+            .addCode("if (3 < 4) {\n$>$T.out.println($S);\n$<}\n", System.class, "hello")
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.System;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  void inlineIndent() {\n"
+        + "    if (3 < 4) {\n"
+        + "      System.out.println(\"hello\");\n"
+        + "    }\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void defaultModifiersForInterfaceMembers() throws Exception {
+    TypeSpec taco = TypeSpec.interfaceBuilder("Taco")
+        .addField(FieldSpec.builder(String.class, "SHELL")
+            .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
+            .initializer("$S", "crunchy corn")
+            .build())
+        .addMethod(MethodSpec.methodBuilder("fold")
+            .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+            .build())
+        .addType(TypeSpec.classBuilder("Topping")
+            .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "interface Taco {\n"
+        + "  String SHELL = \"crunchy corn\";\n"
+        + "\n"
+        + "  void fold();\n"
+        + "\n"
+        + "  class Topping {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void defaultModifiersForMemberInterfacesAndEnums() throws Exception {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addType(TypeSpec.classBuilder("Meat")
+            .addModifiers(Modifier.STATIC)
+            .build())
+        .addType(TypeSpec.interfaceBuilder("Tortilla")
+            .addModifiers(Modifier.STATIC)
+            .build())
+        .addType(TypeSpec.enumBuilder("Topping")
+            .addModifiers(Modifier.STATIC)
+            .addEnumConstant("SALSA")
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  static class Meat {\n"
+        + "  }\n"
+        + "\n"
+        + "  interface Tortilla {\n"
+        + "  }\n"
+        + "\n"
+        + "  enum Topping {\n"
+        + "    SALSA\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void membersOrdering() throws Exception {
+    // Hand out names in reverse-alphabetical order to defend against unexpected sorting.
+    TypeSpec taco = TypeSpec.classBuilder("Members")
+        .addType(TypeSpec.classBuilder("Z").build())
+        .addType(TypeSpec.classBuilder("Y").build())
+        .addField(String.class, "X", Modifier.STATIC)
+        .addField(String.class, "W")
+        .addField(String.class, "V", Modifier.STATIC)
+        .addField(String.class, "U")
+        .addMethod(MethodSpec.methodBuilder("T").addModifiers(Modifier.STATIC).build())
+        .addMethod(MethodSpec.methodBuilder("S").build())
+        .addMethod(MethodSpec.methodBuilder("R").addModifiers(Modifier.STATIC).build())
+        .addMethod(MethodSpec.methodBuilder("Q").build())
+        .addMethod(MethodSpec.constructorBuilder().addParameter(int.class, "p").build())
+        .addMethod(MethodSpec.constructorBuilder().addParameter(long.class, "o").build())
+        .build();
+    // Static fields, instance fields, constructors, methods, classes.
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "class Members {\n"
+        + "  static String X;\n"
+        + "\n"
+        + "  static String V;\n"
+        + "\n"
+        + "  String W;\n"
+        + "\n"
+        + "  String U;\n"
+        + "\n"
+        + "  Members(int p) {\n"
+        + "  }\n"
+        + "\n"
+        + "  Members(long o) {\n"
+        + "  }\n"
+        + "\n"
+        + "  static void T() {\n"
+        + "  }\n"
+        + "\n"
+        + "  void S() {\n"
+        + "  }\n"
+        + "\n"
+        + "  static void R() {\n"
+        + "  }\n"
+        + "\n"
+        + "  void Q() {\n"
+        + "  }\n"
+        + "\n"
+        + "  class Z {\n"
+        + "  }\n"
+        + "\n"
+        + "  class Y {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void nativeMethods() throws Exception {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addMethod(MethodSpec.methodBuilder("nativeInt")
+            .addModifiers(Modifier.NATIVE)
+            .returns(int.class)
+            .build())
+        // GWT JSNI
+        .addMethod(MethodSpec.methodBuilder("alert")
+            .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.NATIVE)
+            .addParameter(String.class, "msg")
+            .addCode(CodeBlock.builder()
+                .add(" /*-{\n")
+                .indent()
+                .addStatement("$$wnd.alert(msg)")
+                .unindent()
+                .add("}-*/")
+                .build())
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  native int nativeInt();\n"
+        + "\n"
+        + "  public static native void alert(String msg) /*-{\n"
+        + "    $wnd.alert(msg);\n"
+        + "  }-*/;\n"
+        + "}\n");
+  }
+
+  @Test public void nullStringLiteral() throws Exception {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addField(FieldSpec.builder(String.class, "NULL")
+            .initializer("$S", (Object) null)
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  String NULL = null;\n"
+        + "}\n");
+  }
+
+  @Test public void annotationToString() throws Exception {
+    AnnotationSpec annotation = AnnotationSpec.builder(SuppressWarnings.class)
+        .addMember("value", "$S", "unused")
+        .build();
+    assertThat(annotation.toString()).isEqualTo("@java.lang.SuppressWarnings(\"unused\")");
+  }
+
+  @Test public void codeBlockToString() throws Exception {
+    CodeBlock codeBlock = CodeBlock.builder()
+        .addStatement("$T $N = $S.substring(0, 3)", String.class, "s", "taco")
+        .build();
+    assertThat(codeBlock.toString()).isEqualTo("java.lang.String s = \"taco\".substring(0, 3);\n");
+  }
+
+  @Test public void codeBlockAddStatementOfCodeBlockToString() throws Exception {
+    CodeBlock contents = CodeBlock.of("$T $N = $S.substring(0, 3)", String.class, "s", "taco");
+    CodeBlock statement = CodeBlock.builder().addStatement(contents).build();
+    assertThat(statement.toString()).isEqualTo("java.lang.String s = \"taco\".substring(0, 3);\n");
+  }
+
+  @Test public void fieldToString() throws Exception {
+    FieldSpec field = FieldSpec.builder(String.class, "s", Modifier.FINAL)
+        .initializer("$S.substring(0, 3)", "taco")
+        .build();
+    assertThat(field.toString())
+        .isEqualTo("final java.lang.String s = \"taco\".substring(0, 3);\n");
+  }
+
+  @Test public void methodToString() throws Exception {
+    MethodSpec method = MethodSpec.methodBuilder("toString")
+        .addAnnotation(Override.class)
+        .addModifiers(Modifier.PUBLIC)
+        .returns(String.class)
+        .addStatement("return $S", "taco")
+        .build();
+    assertThat(method.toString()).isEqualTo(""
+        + "@java.lang.Override\n"
+        + "public java.lang.String toString() {\n"
+        + "  return \"taco\";\n"
+        + "}\n");
+  }
+
+  @Test public void constructorToString() throws Exception {
+    MethodSpec constructor = MethodSpec.constructorBuilder()
+        .addModifiers(Modifier.PUBLIC)
+        .addParameter(ClassName.get(tacosPackage, "Taco"), "taco")
+        .addStatement("this.$N = $N", "taco", "taco")
+        .build();
+    assertThat(constructor.toString()).isEqualTo(""
+        + "public Constructor(com.squareup.tacos.Taco taco) {\n"
+        + "  this.taco = taco;\n"
+        + "}\n");
+  }
+
+  @Test public void parameterToString() throws Exception {
+    ParameterSpec parameter = ParameterSpec.builder(ClassName.get(tacosPackage, "Taco"), "taco")
+        .addModifiers(Modifier.FINAL)
+        .addAnnotation(ClassName.get("javax.annotation", "Nullable"))
+        .build();
+    assertThat(parameter.toString())
+        .isEqualTo("@javax.annotation.Nullable final com.squareup.tacos.Taco taco");
+  }
+
+  @Test public void classToString() throws Exception {
+    TypeSpec type = TypeSpec.classBuilder("Taco")
+        .build();
+    assertThat(type.toString()).isEqualTo(""
+        + "class Taco {\n"
+        + "}\n");
+  }
+
+  @Test public void anonymousClassToString() throws Exception {
+    TypeSpec type = TypeSpec.anonymousClassBuilder("")
+        .addSuperinterface(Runnable.class)
+        .addMethod(MethodSpec.methodBuilder("run")
+            .addAnnotation(Override.class)
+            .addModifiers(Modifier.PUBLIC)
+            .build())
+        .build();
+    assertThat(type.toString()).isEqualTo(""
+        + "new java.lang.Runnable() {\n"
+        + "  @java.lang.Override\n"
+        + "  public void run() {\n"
+        + "  }\n"
+        + "}");
+  }
+
+  @Test public void interfaceClassToString() throws Exception {
+    TypeSpec type = TypeSpec.interfaceBuilder("Taco")
+        .build();
+    assertThat(type.toString()).isEqualTo(""
+        + "interface Taco {\n"
+        + "}\n");
+  }
+
+  @Test public void annotationDeclarationToString() throws Exception {
+    TypeSpec type = TypeSpec.annotationBuilder("Taco")
+        .build();
+    assertThat(type.toString()).isEqualTo(""
+        + "@interface Taco {\n"
+        + "}\n");
+  }
+
+  private String toString(TypeSpec typeSpec) {
+    return JavaFile.builder(tacosPackage, typeSpec).build().toString();
+  }
+
+  @Test public void multilineStatement() throws Exception {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addMethod(MethodSpec.methodBuilder("toString")
+            .addAnnotation(Override.class)
+            .addModifiers(Modifier.PUBLIC)
+            .returns(String.class)
+            .addStatement("return $S\n+ $S\n+ $S\n+ $S\n+ $S",
+                "Taco(", "beef,", "lettuce,", "cheese", ")")
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Override;\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  @Override\n"
+        + "  public String toString() {\n"
+        + "    return \"Taco(\"\n"
+        + "        + \"beef,\"\n"
+        + "        + \"lettuce,\"\n"
+        + "        + \"cheese\"\n"
+        + "        + \")\";\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void multilineStatementWithAnonymousClass() throws Exception {
+    TypeName stringComparator = ParameterizedTypeName.get(Comparator.class, String.class);
+    TypeName listOfString = ParameterizedTypeName.get(List.class, String.class);
+    TypeSpec prefixComparator = TypeSpec.anonymousClassBuilder("")
+        .addSuperinterface(stringComparator)
+        .addMethod(MethodSpec.methodBuilder("compare")
+            .addAnnotation(Override.class)
+            .addModifiers(Modifier.PUBLIC)
+            .returns(int.class)
+            .addParameter(String.class, "a")
+            .addParameter(String.class, "b")
+            .addStatement("return a.substring(0, length)\n"
+                + ".compareTo(b.substring(0, length))")
+            .build())
+        .build();
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addMethod(MethodSpec.methodBuilder("comparePrefix")
+            .returns(stringComparator)
+            .addParameter(int.class, "length", Modifier.FINAL)
+            .addStatement("return $L", prefixComparator)
+            .build())
+        .addMethod(MethodSpec.methodBuilder("sortPrefix")
+            .addParameter(listOfString, "list")
+            .addParameter(int.class, "length", Modifier.FINAL)
+            .addStatement("$T.sort(\nlist,\n$L)", Collections.class, prefixComparator)
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Override;\n"
+        + "import java.lang.String;\n"
+        + "import java.util.Collections;\n"
+        + "import java.util.Comparator;\n"
+        + "import java.util.List;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  Comparator<String> comparePrefix(final int length) {\n"
+        + "    return new Comparator<String>() {\n"
+        + "      @Override\n"
+        + "      public int compare(String a, String b) {\n"
+        + "        return a.substring(0, length)\n"
+        + "            .compareTo(b.substring(0, length));\n"
+        + "      }\n"
+        + "    };\n"
+        + "  }\n"
+        + "\n"
+        + "  void sortPrefix(List<String> list, final int length) {\n"
+        + "    Collections.sort(\n"
+        + "        list,\n"
+        + "        new Comparator<String>() {\n"
+        + "          @Override\n"
+        + "          public int compare(String a, String b) {\n"
+        + "            return a.substring(0, length)\n"
+        + "                .compareTo(b.substring(0, length));\n"
+        + "          }\n"
+        + "        });\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void multilineStrings() throws Exception {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addField(FieldSpec.builder(String.class, "toppings")
+            .initializer("$S", "shell\nbeef\nlettuce\ncheese\n")
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  String toppings = \"shell\\n\"\n"
+        + "      + \"beef\\n\"\n"
+        + "      + \"lettuce\\n\"\n"
+        + "      + \"cheese\\n\";\n"
+        + "}\n");
+  }
+
+  @Test public void doubleFieldInitialization() {
+    try {
+      FieldSpec.builder(String.class, "listA")
+          .initializer("foo")
+          .initializer("bar")
+          .build();
+      fail();
+    } catch (IllegalStateException expected) {
+    }
+
+    try {
+      FieldSpec.builder(String.class, "listA")
+          .initializer(CodeBlock.builder().add("foo").build())
+          .initializer(CodeBlock.builder().add("bar").build())
+          .build();
+      fail();
+    } catch (IllegalStateException expected) {
+    }
+  }
+
+  @Test public void nullAnnotationsAddition() {
+    try {
+      TypeSpec.classBuilder("Taco").addAnnotations(null);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected.getMessage())
+          .isEqualTo("annotationSpecs == null");
+    }
+  }
+
+  @Test public void multipleAnnotationAddition() {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addAnnotations(Arrays.asList(
+            AnnotationSpec.builder(SuppressWarnings.class)
+                .addMember("value", "$S", "unchecked")
+                .build(),
+            AnnotationSpec.builder(Deprecated.class).build()))
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Deprecated;\n"
+        + "import java.lang.SuppressWarnings;\n"
+        + "\n"
+        + "@SuppressWarnings(\"unchecked\")\n"
+        + "@Deprecated\n"
+        + "class Taco {\n"
+        + "}\n");
+  }
+
+  @Test public void nullFieldsAddition() {
+    try {
+      TypeSpec.classBuilder("Taco").addFields(null);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected.getMessage())
+          .isEqualTo("fieldSpecs == null");
+    }
+  }
+
+  @Test public void multipleFieldAddition() {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addFields(Arrays.asList(
+            FieldSpec.builder(int.class, "ANSWER", Modifier.STATIC, Modifier.FINAL).build(),
+            FieldSpec.builder(BigDecimal.class, "price", Modifier.PRIVATE).build()))
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.math.BigDecimal;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  static final int ANSWER;\n"
+        + "\n"
+        + "  private BigDecimal price;\n"
+        + "}\n");
+  }
+
+  @Test public void nullMethodsAddition() {
+    try {
+      TypeSpec.classBuilder("Taco").addMethods(null);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected.getMessage())
+          .isEqualTo("methodSpecs == null");
+    }
+  }
+
+  @Test public void multipleMethodAddition() {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addMethods(Arrays.asList(
+            MethodSpec.methodBuilder("getAnswer")
+                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+                .returns(int.class)
+                .addStatement("return $L", 42)
+                .build(),
+            MethodSpec.methodBuilder("getRandomQuantity")
+                .addModifiers(Modifier.PUBLIC)
+                .returns(int.class)
+                .addJavadoc("chosen by fair dice roll ;)")
+                .addStatement("return $L", 4)
+                .build()))
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  public static int getAnswer() {\n"
+        + "    return 42;\n"
+        + "  }\n"
+        + "\n"
+        + "  /**\n"
+        + "   * chosen by fair dice roll ;) */\n"
+        + "  public int getRandomQuantity() {\n"
+        + "    return 4;\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void nullSuperinterfacesAddition() {
+    try {
+      TypeSpec.classBuilder("Taco").addSuperinterfaces(null);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected.getMessage())
+          .isEqualTo("superinterfaces == null");
+    }
+  }
+
+  @Test public void nullSingleSuperinterfaceAddition() {
+    try {
+      TypeSpec.classBuilder("Taco").addSuperinterface((TypeName) null);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected.getMessage())
+          .isEqualTo("superinterface == null");
+    }
+  }
+
+  @Test public void nullInSuperinterfaceIterableAddition() {
+    List<TypeName> superinterfaces = new ArrayList<>();
+    superinterfaces.add(TypeName.get(List.class));
+    superinterfaces.add(null);
+
+    try {
+      TypeSpec.classBuilder("Taco").addSuperinterfaces(superinterfaces);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected.getMessage())
+          .isEqualTo("superinterface == null");
+    }
+  }
+
+  @Test public void multipleSuperinterfaceAddition() {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addSuperinterfaces(Arrays.asList(
+            TypeName.get(Serializable.class),
+            TypeName.get(EventListener.class)))
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.io.Serializable;\n"
+        + "import java.util.EventListener;\n"
+        + "\n"
+        + "class Taco implements Serializable, EventListener {\n"
+        + "}\n");
+  }
+
+  @Test public void nullModifiersAddition() {
+    try {
+      TypeSpec.classBuilder("Taco").addModifiers((Modifier) null);
+      fail();
+    } catch(IllegalArgumentException expected) {
+      assertThat(expected.getMessage())
+          .isEqualTo("modifiers contain null");
+    }
+  }
+
+  @Test public void nullTypeVariablesAddition() {
+    try {
+      TypeSpec.classBuilder("Taco").addTypeVariables(null);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected.getMessage())
+          .isEqualTo("typeVariables == null");
+    }
+  }
+
+  @Test public void multipleTypeVariableAddition() {
+    TypeSpec location = TypeSpec.classBuilder("Location")
+        .addTypeVariables(Arrays.asList(
+            TypeVariableName.get("T"),
+            TypeVariableName.get("P", Number.class)))
+        .build();
+    assertThat(toString(location)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Number;\n"
+        + "\n"
+        + "class Location<T, P extends Number> {\n"
+        + "}\n");
+  }
+
+  @Test public void nullTypesAddition() {
+    try {
+      TypeSpec.classBuilder("Taco").addTypes(null);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected.getMessage())
+          .isEqualTo("typeSpecs == null");
+    }
+  }
+
+  @Test public void multipleTypeAddition() {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addTypes(Arrays.asList(
+            TypeSpec.classBuilder("Topping").build(),
+            TypeSpec.classBuilder("Sauce").build()))
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  class Topping {\n"
+        + "  }\n"
+        + "\n"
+        + "  class Sauce {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void tryCatch() {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addMethod(MethodSpec.methodBuilder("addTopping")
+            .addParameter(ClassName.get("com.squareup.tacos", "Topping"), "topping")
+            .beginControlFlow("try")
+            .addCode("/* do something tricky with the topping */\n")
+            .nextControlFlow("catch ($T e)",
+                ClassName.get("com.squareup.tacos", "IllegalToppingException"))
+            .endControlFlow()
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  void addTopping(Topping topping) {\n"
+        + "    try {\n"
+        + "      /* do something tricky with the topping */\n"
+        + "    } catch (IllegalToppingException e) {\n"
+        + "    }\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void ifElse() {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addMethod(
+            MethodSpec.methodBuilder("isDelicious")
+                .addParameter(TypeName.INT, "count")
+                .returns(TypeName.BOOLEAN)
+                .beginControlFlow("if (count > 0)")
+                .addStatement("return true")
+                .nextControlFlow("else")
+                .addStatement("return false")
+                .endControlFlow()
+                .build()
+        )
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  boolean isDelicious(int count) {\n"
+        + "    if (count > 0) {\n"
+        + "      return true;\n"
+        + "    } else {\n"
+        + "      return false;\n"
+        + "    }\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void literalFromAnything() {
+    Object value = new Object() {
+      @Override public String toString() {
+        return "foo";
+      }
+    };
+    assertThat(CodeBlock.of("$L", value).toString()).isEqualTo("foo");
+  }
+
+  @Test public void nameFromCharSequence() {
+    assertThat(CodeBlock.of("$N", "text").toString()).isEqualTo("text");
+  }
+
+  @Test public void nameFromField() {
+    FieldSpec field = FieldSpec.builder(String.class, "field").build();
+    assertThat(CodeBlock.of("$N", field).toString()).isEqualTo("field");
+  }
+
+  @Test public void nameFromParameter() {
+    ParameterSpec parameter = ParameterSpec.builder(String.class, "parameter").build();
+    assertThat(CodeBlock.of("$N", parameter).toString()).isEqualTo("parameter");
+  }
+
+  @Test public void nameFromMethod() {
+    MethodSpec method = MethodSpec.methodBuilder("method")
+        .addModifiers(Modifier.ABSTRACT)
+        .returns(String.class)
+        .build();
+    assertThat(CodeBlock.of("$N", method).toString()).isEqualTo("method");
+  }
+
+  @Test public void nameFromType() {
+    TypeSpec type = TypeSpec.classBuilder("Type").build();
+    assertThat(CodeBlock.of("$N", type).toString()).isEqualTo("Type");
+  }
+
+  @Test public void nameFromUnsupportedType() {
+    try {
+      CodeBlock.builder().add("$N", String.class);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("expected name but was " + String.class);
+    }
+  }
+
+  @Test public void stringFromAnything() {
+    Object value = new Object() {
+      @Override public String toString() {
+        return "foo";
+      }
+    };
+    assertThat(CodeBlock.of("$S", value).toString()).isEqualTo("\"foo\"");
+  }
+
+  @Test public void stringFromNull() {
+    assertThat(CodeBlock.of("$S", new Object[] {null}).toString()).isEqualTo("null");
+  }
+
+  @Test public void typeFromTypeName() {
+    TypeName typeName = TypeName.get(String.class);
+    assertThat(CodeBlock.of("$T", typeName).toString()).isEqualTo("java.lang.String");
+  }
+
+  @Test public void typeFromTypeMirror() {
+    TypeMirror mirror = getElement(String.class).asType();
+    assertThat(CodeBlock.of("$T", mirror).toString()).isEqualTo("java.lang.String");
+  }
+
+  @Test public void typeFromTypeElement() {
+    TypeElement element = getElement(String.class);
+    assertThat(CodeBlock.of("$T", element).toString()).isEqualTo("java.lang.String");
+  }
+
+  @Test public void typeFromReflectType() {
+    assertThat(CodeBlock.of("$T", String.class).toString()).isEqualTo("java.lang.String");
+  }
+
+  @Test public void typeFromUnsupportedType() {
+    try {
+      CodeBlock.builder().add("$T", "java.lang.String");
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("expected type but was java.lang.String");
+    }
+  }
+
+  @Test public void tooFewArguments() {
+    try {
+      CodeBlock.builder().add("$S");
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("index 1 for '$S' not in range (received 0 arguments)");
+    }
+  }
+
+  @Test public void unusedArgumentsRelative() {
+    try {
+      CodeBlock.builder().add("$L $L", "a", "b", "c");
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("unused arguments: expected 2, received 3");
+    }
+  }
+
+  @Test public void unusedArgumentsIndexed() {
+    try {
+      CodeBlock.builder().add("$1L $2L", "a", "b", "c");
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("unused argument: $3");
+    }
+    try {
+      CodeBlock.builder().add("$1L $1L $1L", "a", "b", "c");
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("unused arguments: $2, $3");
+    }
+    try {
+      CodeBlock.builder().add("$3L $1L $3L $1L $3L", "a", "b", "c", "d");
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("unused arguments: $2, $4");
+    }
+  }
+
+  @Test public void superClassOnlyValidForClasses() {
+    try {
+      TypeSpec.annotationBuilder("A").superclass(ClassName.get(Object.class));
+      fail();
+    } catch (IllegalStateException expected) {
+    }
+    try {
+      TypeSpec.enumBuilder("E").superclass(ClassName.get(Object.class));
+      fail();
+    } catch (IllegalStateException expected) {
+    }
+    try {
+      TypeSpec.interfaceBuilder("I").superclass(ClassName.get(Object.class));
+      fail();
+    } catch (IllegalStateException expected) {
+    }
+  }
+
+  @Test public void invalidSuperClass() {
+    try {
+      TypeSpec.classBuilder("foo")
+          .superclass(ClassName.get(List.class))
+          .superclass(ClassName.get(Map.class));
+      fail();
+    } catch (IllegalStateException expected) {
+    }
+    try {
+      TypeSpec.classBuilder("foo")
+          .superclass(TypeName.INT);
+      fail();
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  @Test public void staticCodeBlock() {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addField(String.class, "foo", Modifier.PRIVATE)
+        .addField(String.class, "FOO", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
+        .addStaticBlock(CodeBlock.builder()
+            .addStatement("FOO = $S", "FOO")
+            .build())
+        .addMethod(MethodSpec.methodBuilder("toString")
+            .addAnnotation(Override.class)
+            .addModifiers(Modifier.PUBLIC)
+            .returns(String.class)
+            .addCode("return FOO;\n")
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Override;\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  private static final String FOO;\n"
+        + "\n"
+        + "  static {\n"
+        + "    FOO = \"FOO\";\n"
+        + "  }\n"
+        + "\n"
+        + "  private String foo;\n"
+        + "\n"
+        + "  @Override\n"
+        + "  public String toString() {\n"
+        + "    return FOO;\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void initializerBlockInRightPlace() {
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addField(String.class, "foo", Modifier.PRIVATE)
+        .addField(String.class, "FOO", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
+        .addStaticBlock(CodeBlock.builder()
+            .addStatement("FOO = $S", "FOO")
+            .build())
+        .addMethod(MethodSpec.constructorBuilder().build())
+        .addMethod(MethodSpec.methodBuilder("toString")
+            .addAnnotation(Override.class)
+            .addModifiers(Modifier.PUBLIC)
+            .returns(String.class)
+            .addCode("return FOO;\n")
+            .build())
+        .addInitializerBlock(CodeBlock.builder()
+            .addStatement("foo = $S", "FOO")
+            .build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Override;\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  private static final String FOO;\n"
+        + "\n"
+        + "  static {\n"
+        + "    FOO = \"FOO\";\n"
+        + "  }\n"
+        + "\n"
+        + "  private String foo;\n"
+        + "\n"
+        + "  {\n"
+        + "    foo = \"FOO\";\n"
+        + "  }\n"
+        + "\n"
+        + "  Taco() {\n"
+        + "  }\n"
+        + "\n"
+        + "  @Override\n"
+        + "  public String toString() {\n"
+        + "    return FOO;\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void initializersToBuilder() {
+    // Tests if toBuilder() contains correct static and instance initializers
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addField(String.class, "foo", Modifier.PRIVATE)
+        .addField(String.class, "FOO", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
+        .addStaticBlock(CodeBlock.builder()
+            .addStatement("FOO = $S", "FOO")
+            .build())
+        .addMethod(MethodSpec.constructorBuilder().build())
+        .addMethod(MethodSpec.methodBuilder("toString")
+            .addAnnotation(Override.class)
+            .addModifiers(Modifier.PUBLIC)
+            .returns(String.class)
+            .addCode("return FOO;\n")
+            .build())
+        .addInitializerBlock(CodeBlock.builder()
+            .addStatement("foo = $S", "FOO")
+            .build())
+        .build();
+
+    TypeSpec recreatedTaco = taco.toBuilder().build();
+    assertThat(toString(taco)).isEqualTo(toString(recreatedTaco));
+
+    TypeSpec initializersAdded = taco.toBuilder()
+        .addInitializerBlock(CodeBlock.builder()
+            .addStatement("foo = $S", "instanceFoo")
+            .build())
+        .addStaticBlock(CodeBlock.builder()
+            .addStatement("FOO = $S", "staticFoo")
+            .build())
+        .build();
+
+    assertThat(toString(initializersAdded)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.Override;\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  private static final String FOO;\n"
+        + "\n"
+        + "  static {\n"
+        + "    FOO = \"FOO\";\n"
+        + "  }\n"
+        + "  static {\n"
+        + "    FOO = \"staticFoo\";\n"
+        + "  }\n"
+        + "\n"
+        + "  private String foo;\n"
+        + "\n"
+        + "  {\n"
+        + "    foo = \"FOO\";\n"
+        + "  }\n"
+        + "  {\n"
+        + "    foo = \"instanceFoo\";\n"
+        + "  }\n"
+        + "\n"
+        + "  Taco() {\n"
+        + "  }\n"
+        + "\n"
+        + "  @Override\n"
+        + "  public String toString() {\n"
+        + "    return FOO;\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void initializerBlockUnsupportedExceptionOnInterface() {
+    TypeSpec.Builder interfaceBuilder = TypeSpec.interfaceBuilder("Taco");
+    try {
+      interfaceBuilder.addInitializerBlock(CodeBlock.builder().build());
+      fail("Exception expected");
+    } catch (UnsupportedOperationException e) {
+    }
+  }
+
+  @Test public void initializerBlockUnsupportedExceptionOnAnnotation() {
+    TypeSpec.Builder annotationBuilder = TypeSpec.annotationBuilder("Taco");
+    try {
+      annotationBuilder.addInitializerBlock(CodeBlock.builder().build());
+      fail("Exception expected");
+    } catch (UnsupportedOperationException e) {
+    }
+  }
+
+  @Test public void lineWrapping() {
+    MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("call");
+    methodBuilder.addCode("$[call(");
+    for (int i = 0; i < 32; i++) {
+      methodBuilder.addParameter(String.class, "s" + i);
+      methodBuilder.addCode(i > 0 ? ",$W$S" : "$S", i);
+    }
+    methodBuilder.addCode(");$]\n");
+
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addMethod(methodBuilder.build())
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "import java.lang.String;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  void call(String s0, String s1, String s2, String s3, String s4, String s5, String s6, String s7,\n"
+        + "      String s8, String s9, String s10, String s11, String s12, String s13, String s14, String s15,\n"
+        + "      String s16, String s17, String s18, String s19, String s20, String s21, String s22,\n"
+        + "      String s23, String s24, String s25, String s26, String s27, String s28, String s29,\n"
+        + "      String s30, String s31) {\n"
+        + "    call(\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\", \"11\", \"12\", \"13\", \"14\", \"15\", \"16\",\n"
+        + "        \"17\", \"18\", \"19\", \"20\", \"21\", \"22\", \"23\", \"24\", \"25\", \"26\", \"27\", \"28\", \"29\", \"30\", \"31\");\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void lineWrappingWithZeroWidthSpace() {
+    MethodSpec method = MethodSpec.methodBuilder("call")
+        .addCode("$[iAmSickOfWaitingInLine($Z")
+        .addCode("it, has, been, far, too, long, of, a, wait, and, i, would, like, to, eat, ")
+        .addCode("this, is, a, run, on, sentence")
+        .addCode(");$]\n")
+        .build();
+
+    TypeSpec taco = TypeSpec.classBuilder("Taco")
+        .addMethod(method)
+        .build();
+    assertThat(toString(taco)).isEqualTo(""
+        + "package com.squareup.tacos;\n"
+        + "\n"
+        + "class Taco {\n"
+        + "  void call() {\n"
+        + "    iAmSickOfWaitingInLine(\n"
+        + "        it, has, been, far, too, long, of, a, wait, and, i, would, like, to, eat, this, is, a, run, on, sentence);\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void equalsAndHashCode() {
+    TypeSpec a = TypeSpec.interfaceBuilder("taco").build();
+    TypeSpec b = TypeSpec.interfaceBuilder("taco").build();
+    assertThat(a.equals(b)).isTrue();
+    assertThat(a.hashCode()).isEqualTo(b.hashCode());
+    a = TypeSpec.classBuilder("taco").build();
+    b = TypeSpec.classBuilder("taco").build();
+    assertThat(a.equals(b)).isTrue();
+    assertThat(a.hashCode()).isEqualTo(b.hashCode());
+    a = TypeSpec.enumBuilder("taco").addEnumConstant("SALSA").build();
+    b = TypeSpec.enumBuilder("taco").addEnumConstant("SALSA").build();
+    assertThat(a.equals(b)).isTrue();
+    assertThat(a.hashCode()).isEqualTo(b.hashCode());
+    a = TypeSpec.annotationBuilder("taco").build();
+    b = TypeSpec.annotationBuilder("taco").build();
+    assertThat(a.equals(b)).isTrue();
+    assertThat(a.hashCode()).isEqualTo(b.hashCode());
+  }
+
+  @Test public void classNameFactories() {
+    ClassName className = ClassName.get("com.example", "Example");
+    assertThat(TypeSpec.classBuilder(className).build().name).isEqualTo("Example");
+    assertThat(TypeSpec.interfaceBuilder(className).build().name).isEqualTo("Example");
+    assertThat(TypeSpec.enumBuilder(className).addEnumConstant("A").build().name).isEqualTo("Example");
+    assertThat(TypeSpec.annotationBuilder(className).build().name).isEqualTo("Example");
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/TypesEclipseTest.java b/src/test/java/com/squareup/javapoet/TypesEclipseTest.java
new file mode 100644
index 0000000..2759f17
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/TypesEclipseTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import static com.google.common.base.Charsets.*;
+import static com.google.common.base.Preconditions.*;
+
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+
+import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.runners.model.Statement;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+@RunWith(JUnit4.class)
+public final class TypesEclipseTest extends AbstractTypesTest {
+  /**
+   * A {@link JUnit4} {@link Rule} that executes tests such that a instances of {@link Elements} and
+   * {@link Types} are available during execution.
+   *
+   * <p>To use this rule in a test, just add the following field: <pre>   {@code
+   *   @Rule public CompilationRule compilationRule = new CompilationRule();}
+   *
+   * @author Gregory Kick
+   */
+  public static final class CompilationRule implements TestRule {
+    private Elements elements;
+    private Types types;
+
+    @Override
+    public Statement apply(final Statement base, Description description) {
+      return new Statement() {
+        @Override public void evaluate() throws Throwable {
+          final AtomicReference<Throwable> thrown = new AtomicReference<>();
+          boolean successful = compile(ImmutableList.of(new AbstractProcessor() {
+            @Override
+            public SourceVersion getSupportedSourceVersion() {
+              return SourceVersion.latest();
+            }
+
+            @Override
+            public Set<String> getSupportedAnnotationTypes() {
+              return ImmutableSet.of("*");
+            }
+
+            @Override
+            public synchronized void init(ProcessingEnvironment processingEnv) {
+              super.init(processingEnv);
+              elements = processingEnv.getElementUtils();
+              types = processingEnv.getTypeUtils();
+            }
+
+            @Override
+            public boolean process(Set<? extends TypeElement> annotations,
+                RoundEnvironment roundEnv) {
+              // just run the test on the last round after compilation is over
+              if (roundEnv.processingOver()) {
+                try {
+                  base.evaluate();
+                } catch (Throwable e) {
+                  thrown.set(e);
+                }
+              }
+              return false;
+            }
+          }));
+          checkState(successful);
+          Throwable t = thrown.get();
+          if (t != null) {
+            throw t;
+          }
+        }
+      };
+    }
+
+    /**
+     * Returns the {@link Elements} instance associated with the current execution of the rule.
+     *
+     * @throws IllegalStateException if this method is invoked outside the execution of the rule.
+     */
+    public Elements getElements() {
+      checkState(elements != null, "Not running within the rule");
+      return elements;
+    }
+
+    /**
+     * Returns the {@link Types} instance associated with the current execution of the rule.
+     *
+     * @throws IllegalStateException if this method is invoked outside the execution of the rule.
+     */
+    public Types getTypes() {
+      checkState(elements != null, "Not running within the rule");
+      return types;
+    }
+
+    static private boolean compile(Iterable<? extends Processor> processors) {
+      JavaCompiler compiler = new EclipseCompiler();
+      DiagnosticCollector<JavaFileObject> diagnosticCollector =
+          new DiagnosticCollector<>();
+      JavaFileManager fileManager = compiler.getStandardFileManager(diagnosticCollector, Locale.getDefault(), UTF_8);
+      JavaCompiler.CompilationTask task = compiler.getTask(
+          null,
+          fileManager,
+          diagnosticCollector,
+          ImmutableSet.of(),
+          ImmutableSet.of(TypesEclipseTest.class.getCanonicalName()),
+          ImmutableSet.of());
+      task.setProcessors(processors);
+      return task.call();
+    }
+  }
+
+  @Rule public final CompilationRule compilation = new CompilationRule();
+
+  @Override
+  protected Elements getElements() {
+    return compilation.getElements();
+  }
+
+  @Override
+  protected Types getTypes() {
+    return compilation.getTypes();
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/TypesTest.java b/src/test/java/com/squareup/javapoet/TypesTest.java
new file mode 100644
index 0000000..2455ae5
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/TypesTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import com.google.testing.compile.CompilationRule;
+
+@RunWith(JUnit4.class)
+public final class TypesTest extends AbstractTypesTest {
+  @Rule public final CompilationRule compilation = new CompilationRule();
+
+  @Override
+  protected Elements getElements() {
+    return compilation.getElements();
+  }
+
+  @Override
+  protected Types getTypes() {
+    return compilation.getTypes();
+  }
+}
diff --git a/src/test/java/com/squareup/javapoet/UtilTest.java b/src/test/java/com/squareup/javapoet/UtilTest.java
new file mode 100644
index 0000000..d4b9c52
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/UtilTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+package com.squareup.javapoet;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class UtilTest {
+  @Test public void characterLiteral() {
+    assertEquals("a", Util.characterLiteralWithoutSingleQuotes('a'));
+    assertEquals("b", Util.characterLiteralWithoutSingleQuotes('b'));
+    assertEquals("c", Util.characterLiteralWithoutSingleQuotes('c'));
+    assertEquals("%", Util.characterLiteralWithoutSingleQuotes('%'));
+    // common escapes
+    assertEquals("\\b", Util.characterLiteralWithoutSingleQuotes('\b'));
+    assertEquals("\\t", Util.characterLiteralWithoutSingleQuotes('\t'));
+    assertEquals("\\n", Util.characterLiteralWithoutSingleQuotes('\n'));
+    assertEquals("\\f", Util.characterLiteralWithoutSingleQuotes('\f'));
+    assertEquals("\\r", Util.characterLiteralWithoutSingleQuotes('\r'));
+    assertEquals("\"", Util.characterLiteralWithoutSingleQuotes('"'));
+    assertEquals("\\'", Util.characterLiteralWithoutSingleQuotes('\''));
+    assertEquals("\\\\", Util.characterLiteralWithoutSingleQuotes('\\'));
+    // octal escapes
+    assertEquals("\\u0000", Util.characterLiteralWithoutSingleQuotes('\0'));
+    assertEquals("\\u0007", Util.characterLiteralWithoutSingleQuotes('\7'));
+    assertEquals("?", Util.characterLiteralWithoutSingleQuotes('\77'));
+    assertEquals("\\u007f", Util.characterLiteralWithoutSingleQuotes('\177'));
+    assertEquals("¿", Util.characterLiteralWithoutSingleQuotes('\277'));
+    assertEquals("ÿ", Util.characterLiteralWithoutSingleQuotes('\377'));
+    // unicode escapes
+    assertEquals("\\u0000", Util.characterLiteralWithoutSingleQuotes('\u0000'));
+    assertEquals("\\u0001", Util.characterLiteralWithoutSingleQuotes('\u0001'));
+    assertEquals("\\u0002", Util.characterLiteralWithoutSingleQuotes('\u0002'));
+    assertEquals("€", Util.characterLiteralWithoutSingleQuotes('\u20AC'));
+    assertEquals("☃", Util.characterLiteralWithoutSingleQuotes('\u2603'));
+    assertEquals("♠", Util.characterLiteralWithoutSingleQuotes('\u2660'));
+    assertEquals("♣", Util.characterLiteralWithoutSingleQuotes('\u2663'));
+    assertEquals("♥", Util.characterLiteralWithoutSingleQuotes('\u2665'));
+    assertEquals("♦", Util.characterLiteralWithoutSingleQuotes('\u2666'));
+    assertEquals("✵", Util.characterLiteralWithoutSingleQuotes('\u2735'));
+    assertEquals("✺", Util.characterLiteralWithoutSingleQuotes('\u273A'));
+    assertEquals("/", Util.characterLiteralWithoutSingleQuotes('\uFF0F'));
+  }
+
+  @Test public void stringLiteral() {
+    stringLiteral("abc");
+    stringLiteral("♦♥♠♣");
+    stringLiteral("€\\t@\\t$", "€\t@\t$", " ");
+    stringLiteral("abc();\\n\"\n  + \"def();", "abc();\ndef();", " ");
+    stringLiteral("This is \\\"quoted\\\"!", "This is \"quoted\"!", " ");
+    stringLiteral("e^{i\\\\pi}+1=0", "e^{i\\pi}+1=0", " ");
+  }
+
+  void stringLiteral(String string) {
+    stringLiteral(string, string, " ");
+  }
+
+  void stringLiteral(String expected, String value, String indent) {
+    assertEquals("\"" + expected + "\"", Util.stringLiteralWithDoubleQuotes(value, indent));
+  }
+}