[javawriter] 01/05: Imported Upstream version 2.5.1

komal sukhani komal-guest at moszumanska.debian.org
Fri Jul 17 14:29:48 UTC 2015


This is an automated email from the git hooks/post-receive script.

komal-guest pushed a commit to branch master
in repository javawriter.

commit ebb3721a8d2c81439639bfbdfa0b7b857229346c
Author: Komal Sukhani <komaldsukhani at gmail.com>
Date:   Thu Jul 16 17:24:31 2015 +0530

    Imported Upstream version 2.5.1
---
 .gitignore                                         |  23 +
 .travis.yml                                        |   4 +
 CHANGELOG.md                                       | 124 +++
 CONTRIBUTING.md                                    |  17 +
 LICENSE.txt                                        | 202 +++++
 README.md                                          |  83 ++
 checkstyle.xml                                     | 121 +++
 deploy_javadoc.sh                                  |  41 +
 pom.xml                                            |  99 +++
 .../java/com/squareup/javawriter/JavaWriter.java   | 870 +++++++++++++++++++
 .../com/squareup/javawriter/StringLiteral.java     |  91 ++
 src/test/java/com/example/Binding.java             |   5 +
 .../com/squareup/javawriter/JavaWriterTest.java    | 922 +++++++++++++++++++++
 .../com/squareup/javawriter/StringLiteralTest.java |  46 +
 14 files changed, 2648 insertions(+)

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..1896e30
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,4 @@
+language: java
+
+notifications:
+  email: false
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..a4630f2
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,124 @@
+Change Log
+==========
+
+Version 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.
+
+
+Version 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.
+
+
+Version 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.
+
+
+Version 2.3.1 *(2013-12-16)*
+----------------------------
+
+ * Fix: Properly handle subpackages of `java.lang` in `compressType`.
+
+
+Version 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.
+
+
+Version 2.2.1 *(2013-10-23)*
+----------------------------
+
+ * Fix: Do not emit trailing whitespace for empty Javadoc lines.
+
+
+Version 2.2.0 *(2013-09-25)*
+----------------------------
+
+ * `setCompressingTypes` controls whether types are emitted as fully-qualified or not.
+
+
+Version 2.1.2 *(2013-08-23)*
+----------------------------
+
+ * Attempt to keep annotations on a single line.
+
+
+Version 2.1.1 *(2013-07-23)*
+----------------------------
+
+ * Fix: `stringLiteral` now correctly handles escapes and control characters.
+
+
+Version 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 version 3.0.
+ * Annotations with a single "value" attribute will now omit the key.
+
+
+Version 2.0.1 *(2013-06-17)*
+----------------------------
+
+ * Correct casing of `emitSingleLineComment`.
+
+
+Version 2.0.0 *(2013-06-06)*
+----------------------------
+
+ * Package name is now `com.squareup.javawriter`.
+ * Support declaring `throws` clause on methods.
+
+
+Version 1.0.5 *(2013-05-08)*
+----------------------------
+
+ * Fix: Fully qualify types whose simple name matches an import.
+
+
+Version 1.0.4 *(2013-03-15)*
+----------------------------
+
+ * Fix: Static import emit now properly supports method imports.
+
+
+Version 1.0.3 *(2013-02-21)*
+-----------------------------
+
+ * Add support for emitting static imports.
+
+
+Version 1.0.2 *(2013-02-11)*
+----------------------------
+
+ * Add `type` API for helping build generic types.
+ * Minor performance improvements.
+
+
+Version 1.0.1 *(2013-02-03)*
+----------------------------
+
+ * Expose `compressType` API.
+
+
+Version 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..1529ff1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,83 @@
+Java Writer
+===========
+
+`JavaWriter` is a utility class which aids in generating Java source files.
+
+Source file generation can 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
+-------
+
+```java
+writer.emitPackage("com.example")
+    .beginType("com.example.Person", "class", EnumSet.of(PUBLIC, FINAL))
+    .emitField("String", "firstName", EnumSet.of(PRIVATE))
+    .emitField("String", "lastName", EnumSet.of(PRIVATE))
+    .emitJavadoc("Returns the person's full name.")
+    .beginMethod("String", "getName", EnumSet.of(PUBLIC))
+    .emitStatement("return firstName + \" \" + lastName")
+    .endMethod()
+    .endType();
+```
+
+Would produce the following source output:
+
+```java
+package com.example;
+
+public final class Person {
+  private String firstName;
+  private String lastName;
+  /**
+   * Returns the person's full name.
+   */
+  public String getName() {
+    return firstName + " " + lastName;
+  }
+}
+```
+
+
+
+Download
+--------
+
+Download [the latest .jar][dl] or depend via Maven:
+```xml
+<dependency>
+  <groupId>com.squareup</groupId>
+  <artifactId>javawriter</artifactId>
+  <version>2.5.1</version>
+</dependency>
+```
+or Gradle:
+```groovy
+compile 'com.squareup:javawriter:2.5.1'
+```
+
+
+
+License
+-------
+
+    Copyright 2013 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.
+
+
+
+ [dl]: https://search.maven.org/remote_content?g=com.squareup&a=javawriter&v=LATEST
diff --git a/checkstyle.xml b/checkstyle.xml
new file mode 100644
index 0000000..6b919c9
--- /dev/null
+++ b/checkstyle.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0"?>
+<!DOCTYPE module PUBLIC
+    "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
+    "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+
+<module name="Checker">
+  <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>
+
+  <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"/>
+
+
+    <!-- Checks for Size Violations.                    -->
+    <!-- See http://checkstyle.sf.net/config_sizes.html -->
+    <module name="LineLength">
+      <property name="max" value="100"/>
+    </module>
+    <module name="MethodLength"/>
+    <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"/>
+
+
+    <!-- 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="DoubleCheckedLocking"/>
+    <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"/>
+  </module>
+</module>
diff --git a/deploy_javadoc.sh b/deploy_javadoc.sh
new file mode 100755
index 0000000..3074cd2
--- /dev/null
+++ b/deploy_javadoc.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+set -ex
+
+REPO="git at github.com:square/javawriter.git"
+GROUP_ID="com.squareup"
+ARTIFACT_ID="javawriter"
+
+DIR=temp-clone
+
+# Delete any existing temporary website clone
+rm -rf $DIR
+
+# Clone the current repo into temp folder
+git clone $REPO $DIR
+
+# Move working directory into temp folder
+cd $DIR
+
+# Checkout and track the gh-pages branch
+git checkout -t origin/gh-pages
+
+# Delete everything
+rm -rf *
+
+# Download the latest javadoc
+curl -L "https://search.maven.org/remote_content?g=$GROUP_ID&a=$ARTIFACT_ID&v=LATEST&c=javadoc" > javadoc.zip
+unzip javadoc.zip
+rm javadoc.zip
+
+# Stage all files in git and create a commit
+git add .
+git add -u
+git commit -m "Website at $(date)"
+
+# Push the new files up to GitHub
+git push origin gh-pages
+
+# Delete our temp folder
+cd ..
+rm -rf $DIR
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..187e92a
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,99 @@
+<?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>javawriter</artifactId>
+  <version>2.5.1</version>
+
+  <name>JavaWriter</name>
+  <description>A utility class which aids in generating Java source files.</description>
+  <url>http://github.com/square/javawriter/</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+    <java.version>1.6</java.version>
+    <junit.version>4.10</junit.version>
+    <fest.version>2.0M8</fest.version>
+  </properties>
+
+  <scm>
+    <url>http://github.com/square/javawriter/</url>
+    <connection>scm:git:git://github.com/square/javawriter.git</connection>
+    <developerConnection>scm:git:ssh://git@github.com/square/javawriter.git</developerConnection>
+    <tag>HEAD</tag>
+  </scm>
+
+  <issueManagement>
+    <system>GitHub Issues</system>
+    <url>http://github.com/square/javawriter/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>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>${junit.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.easytesting</groupId>
+      <artifactId>fest-assert-core</artifactId>
+      <version>${fest.version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.0</version>
+        <configuration>
+          <source>${java.version}</source>
+          <target>${java.version}</target>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+        <version>2.9.1</version>
+        <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>
+    </plugins>
+  </build>
+</project>
diff --git a/src/main/java/com/squareup/javawriter/JavaWriter.java b/src/main/java/com/squareup/javawriter/JavaWriter.java
new file mode 100644
index 0000000..52eae07
--- /dev/null
+++ b/src/main/java/com/squareup/javawriter/JavaWriter.java
@@ -0,0 +1,870 @@
+// Copyright 2013 Square, Inc.
+package com.squareup.javawriter;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.Writer;
+import java.lang.annotation.Annotation;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.lang.model.element.Modifier;
+
+import static javax.lang.model.element.Modifier.ABSTRACT;
+
+/** A utility class which aids in generating Java source files. */
+public class JavaWriter implements Closeable {
+  private static final Pattern TYPE_TRAILER = Pattern.compile("(.*?)(\\.\\.\\.|(?:\\[\\])+)$");
+  private static final Pattern TYPE_PATTERN = Pattern.compile("(?:[\\w$]+\\.)*([\\w\\.*$]+)");
+  private static final int MAX_SINGLE_LINE_ATTRIBUTES = 3;
+  private static final String INDENT = "  ";
+
+  /** Map fully qualified type names to their short names. */
+  private final Map<String, String> importedTypes = new LinkedHashMap<String, String>();
+
+  private String packagePrefix;
+  private final Deque<Scope> scopes = new ArrayDeque<Scope>();
+  private final Deque<String> types = new ArrayDeque<String>();
+  private final Writer out;
+  private boolean isCompressingTypes = true;
+  private String indent = INDENT;
+
+  /**
+   * @param out the stream to which Java source will be written. This should be a buffered stream.
+   */
+  public JavaWriter(Writer out) {
+    this.out = out;
+  }
+
+  public void setCompressingTypes(boolean isCompressingTypes) {
+    this.isCompressingTypes = isCompressingTypes;
+  }
+
+  public boolean isCompressingTypes() {
+    return isCompressingTypes;
+  }
+
+  public void setIndent(String indent) {
+    this.indent = indent;
+  }
+
+  public String getIndent() {
+    return indent;
+  }
+
+  /** Emit a package declaration and empty line. */
+  public JavaWriter emitPackage(String packageName) throws IOException {
+    if (this.packagePrefix != null) {
+      throw new IllegalStateException();
+    }
+    if (packageName.isEmpty()) {
+      this.packagePrefix = "";
+    } else {
+      out.write("package ");
+      out.write(packageName);
+      out.write(";\n\n");
+      this.packagePrefix = packageName + ".";
+    }
+    return this;
+  }
+
+  /**
+   * Emit an import for each {@code type} provided. For the duration of the file, all references to
+   * these classes will be automatically shortened.
+   */
+  public JavaWriter emitImports(String... types) throws IOException {
+    return emitImports(Arrays.asList(types));
+  }
+
+  /**
+   * Emit an import for each {@code type} provided. For the duration of the file, all references to
+   * these classes will be automatically shortened.
+   */
+  public JavaWriter emitImports(Class<?>... types) throws IOException {
+    List<String> classNames = new ArrayList<String>(types.length);
+    for (Class<?> classToImport : types) {
+      classNames.add(classToImport.getCanonicalName());
+    }
+    return emitImports(classNames);
+  }
+
+  /**
+   * Emit an import for each {@code type} in the provided {@code Collection}. For the duration of
+   * the file, all references to these classes will be automatically shortened.
+   */
+  public JavaWriter emitImports(Collection<String> types) throws IOException {
+    for (String type : new TreeSet<String>(types)) {
+      Matcher matcher = TYPE_PATTERN.matcher(type);
+      if (!matcher.matches()) {
+        throw new IllegalArgumentException(type);
+      }
+      if (importedTypes.put(type, matcher.group(1)) != null) {
+        throw new IllegalArgumentException(type);
+      }
+      out.write("import ");
+      out.write(type);
+      out.write(";\n");
+    }
+    return this;
+  }
+
+  /**
+   * Emit a static import for each {@code type} provided. For the duration of the file,
+   * all references to these classes will be automatically shortened.
+   */
+  public JavaWriter emitStaticImports(String... types) throws IOException {
+    return emitStaticImports(Arrays.asList(types));
+  }
+
+  /**
+   * Emit a static import for each {@code type} in the provided {@code Collection}. For the
+   * duration of the file, all references to these classes will be automatically shortened.
+   */
+  public JavaWriter emitStaticImports(Collection<String> types) throws IOException {
+    for (String type : new TreeSet<String>(types)) {
+      Matcher matcher = TYPE_PATTERN.matcher(type);
+      if (!matcher.matches()) {
+        throw new IllegalArgumentException(type);
+      }
+      if (importedTypes.put(type, matcher.group(1)) != null) {
+        throw new IllegalArgumentException(type);
+      }
+      out.write("import static ");
+      out.write(type);
+      out.write(";\n");
+    }
+    return this;
+  }
+
+  /**
+   * Emits a name like {@code java.lang.String} or {@code java.util.List<java.lang.String>},
+   * compressing it with imports if possible. Type compression will only be enabled if
+   * {@link #isCompressingTypes} is true.
+   */
+  private JavaWriter emitCompressedType(String type) throws IOException {
+    if (isCompressingTypes) {
+      out.write(compressType(type));
+    } else {
+      out.write(type);
+    }
+    return this;
+  }
+
+  /** Try to compress a fully-qualified class name to only the class name. */
+  public String compressType(String type) {
+    Matcher trailer = TYPE_TRAILER.matcher(type);
+    if (trailer.matches()) {
+      type = trailer.group(1);
+    }
+
+    StringBuilder sb = new StringBuilder();
+    if (this.packagePrefix == null) {
+      throw new IllegalStateException();
+    }
+
+    Matcher m = TYPE_PATTERN.matcher(type);
+    int pos = 0;
+    while (true) {
+      boolean found = m.find(pos);
+
+      // Copy non-matching characters like "<".
+      int typeStart = found ? m.start() : type.length();
+      sb.append(type, pos, typeStart);
+
+      if (!found) {
+        break;
+      }
+
+      // Copy a single class name, shortening it if possible.
+      String name = m.group(0);
+      String imported = importedTypes.get(name);
+      if (imported != null) {
+        sb.append(imported);
+      } else if (isClassInPackage(name, packagePrefix)) {
+        String compressed = name.substring(packagePrefix.length());
+        if (isAmbiguous(compressed)) {
+          sb.append(name);
+        } else {
+          sb.append(compressed);
+        }
+      } else if (isClassInPackage(name, "java.lang.")) {
+        sb.append(name.substring("java.lang.".length()));
+      } else {
+        sb.append(name);
+      }
+      pos = m.end();
+    }
+
+    if (trailer.matches()) {
+      sb.append(trailer.group(2));
+    }
+    return sb.toString();
+  }
+
+  private static boolean isClassInPackage(String name, String packagePrefix) {
+    if (name.startsWith(packagePrefix)) {
+      if (name.indexOf('.', packagePrefix.length()) == -1) {
+        return true;
+      }
+      // check to see if the part after the package looks like a class
+      if (Character.isUpperCase(name.charAt(packagePrefix.length()))) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if the imports contain a class with same simple name as {@code compressed}.
+   *
+   * @param compressed simple name of the type
+   */
+  private boolean isAmbiguous(String compressed) {
+    return importedTypes.values().contains(compressed);
+  }
+
+  /**
+   * Emits an initializer declaration.
+   *
+   * @param isStatic true if it should be an static initializer, false for an instance initializer.
+   */
+  public JavaWriter beginInitializer(boolean isStatic) throws IOException {
+    indent();
+    if (isStatic) {
+      out.write("static");
+      out.write(" {\n");
+    } else {
+      out.write("{\n");
+    }
+    scopes.push(Scope.INITIALIZER);
+    return this;
+  }
+
+  /** Ends the current initializer declaration. */
+  public JavaWriter endInitializer() throws IOException {
+    popScope(Scope.INITIALIZER);
+    indent();
+    out.write("}\n");
+    return this;
+  }
+
+ /**
+  * Emits a type declaration.
+  *
+  * @param kind such as "class", "interface" or "enum".
+  */
+  public JavaWriter beginType(String type, String kind) throws IOException {
+    return beginType(type, kind, EnumSet.noneOf(Modifier.class), null);
+  }
+
+  /**
+   * Emits a type declaration.
+   *
+   * @param kind such as "class", "interface" or "enum".
+   */
+  public JavaWriter beginType(String type, String kind, Set<Modifier> modifiers)
+      throws IOException {
+    return beginType(type, kind, modifiers, null);
+  }
+
+  /**
+   * Emits a type declaration.
+   *
+   * @param kind such as "class", "interface" or "enum".
+   * @param extendsType the class to extend, or null for no extends clause.
+   */
+  public JavaWriter beginType(String type, String kind, Set<Modifier> modifiers, String extendsType,
+      String... implementsTypes) throws IOException {
+    indent();
+    emitModifiers(modifiers);
+    out.write(kind);
+    out.write(" ");
+    emitCompressedType(type);
+    if (extendsType != null) {
+      out.write(" extends ");
+      emitCompressedType(extendsType);
+    }
+    if (implementsTypes.length > 0) {
+      out.write("\n");
+      indent();
+      out.write("    implements ");
+      for (int i = 0; i < implementsTypes.length; i++) {
+        if (i != 0) {
+          out.write(", ");
+        }
+        emitCompressedType(implementsTypes[i]);
+      }
+    }
+    out.write(" {\n");
+    scopes.push("interface".equals(kind) ? Scope.INTERFACE_DECLARATION : Scope.TYPE_DECLARATION);
+    types.push(type);
+    return this;
+  }
+
+  /** Completes the current type declaration. */
+  public JavaWriter endType() throws IOException {
+    popScope(Scope.TYPE_DECLARATION, Scope.INTERFACE_DECLARATION);
+    types.pop();
+    indent();
+    out.write("}\n");
+    return this;
+  }
+
+  /** Emits a field declaration. */
+  public JavaWriter emitField(String type, String name) throws IOException {
+    return emitField(type, name, EnumSet.noneOf(Modifier.class), null);
+  }
+
+  /** Emits a field declaration. */
+  public JavaWriter emitField(String type, String name, Set<Modifier> modifiers)
+      throws IOException {
+    return emitField(type, name, modifiers, null);
+  }
+
+  /** Emits a field declaration. */
+  public JavaWriter emitField(String type, String name, Set<Modifier> modifiers,
+      String initialValue) throws IOException {
+    indent();
+    emitModifiers(modifiers);
+    emitCompressedType(type);
+    out.write(" ");
+    out.write(name);
+
+    if (initialValue != null) {
+      out.write(" =");
+      if (!initialValue.startsWith("\n")) {
+        out.write(" ");
+      }
+
+      String[] lines = initialValue.split("\n", -1);
+      out.write(lines[0]);
+      for (int i = 1; i < lines.length; i++) {
+        out.write("\n");
+        hangingIndent();
+        out.write(lines[i]);
+      }
+    }
+    out.write(";\n");
+    return this;
+  }
+
+  /**
+   * Emit a method declaration.
+   *
+   * <p>A {@code null} return type may be used to indicate a constructor, but
+   * {@link #beginConstructor(Set, String...)} should be preferred. This behavior may be removed in
+   * a future release.
+   *
+   * @param returnType the method's return type, or null for constructors
+   * @param name the method name, or the fully qualified class name for constructors.
+   * @param modifiers the set of modifiers to be applied to the method
+   * @param parameters alternating parameter types and names.
+   */
+  public JavaWriter beginMethod(String returnType, String name, Set<Modifier> modifiers,
+      String... parameters) throws IOException {
+    return beginMethod(returnType, name, modifiers, Arrays.asList(parameters), null);
+  }
+
+  /**
+   * Emit a method declaration.
+   *
+   * <p>A {@code null} return type may be used to indicate a constructor, but
+   * {@link #beginConstructor(Set, List, List)} should be preferred. This behavior may be removed in
+   * a future release.
+   *
+   * @param returnType the method's return type, or null for constructors.
+   * @param name the method name, or the fully qualified class name for constructors.
+   * @param modifiers the set of modifiers to be applied to the method
+   * @param parameters alternating parameter types and names.
+   * @param throwsTypes the classes to throw, or null for no throws clause.
+   */
+  public JavaWriter beginMethod(String returnType, String name, Set<Modifier> modifiers,
+      List<String> parameters, List<String> throwsTypes) throws IOException {
+    indent();
+    emitModifiers(modifiers);
+    if (returnType != null) {
+      emitCompressedType(returnType);
+      out.write(" ");
+      out.write(name);
+    } else {
+      emitCompressedType(name);
+    }
+    out.write("(");
+    if (parameters != null) {
+      for (int p = 0; p < parameters.size();) {
+        if (p != 0) {
+          out.write(", ");
+        }
+        emitCompressedType(parameters.get(p++));
+        out.write(" ");
+        emitCompressedType(parameters.get(p++));
+      }
+    }
+    out.write(")");
+    if (throwsTypes != null && throwsTypes.size() > 0) {
+      out.write("\n");
+      indent();
+      out.write("    throws ");
+      for (int i = 0; i < throwsTypes.size(); i++) {
+        if (i != 0) {
+          out.write(", ");
+        }
+        emitCompressedType(throwsTypes.get(i));
+      }
+    }
+    if (modifiers.contains(ABSTRACT) || Scope.INTERFACE_DECLARATION.equals(scopes.peek())) {
+      out.write(";\n");
+      scopes.push(Scope.ABSTRACT_METHOD);
+    } else {
+      out.write(" {\n");
+      scopes.push(returnType == null ? Scope.CONSTRUCTOR : Scope.NON_ABSTRACT_METHOD);
+    }
+    return this;
+  }
+
+  public JavaWriter beginConstructor(Set<Modifier> modifiers, String... parameters)
+      throws IOException {
+    beginMethod(null, rawType(types.peekFirst()), modifiers, parameters);
+    return this;
+  }
+
+  public JavaWriter beginConstructor(Set<Modifier> modifiers,
+      List<String> parameters, List<String> throwsTypes)
+      throws IOException {
+    beginMethod(null, rawType(types.peekFirst()), modifiers, parameters, throwsTypes);
+    return this;
+  }
+
+  /** Emits some Javadoc comments with line separated by {@code \n}. */
+  public JavaWriter emitJavadoc(String javadoc, Object... params) throws IOException {
+    String formatted = String.format(javadoc, params);
+
+    indent();
+    out.write("/**\n");
+    for (String line : formatted.split("\n")) {
+      indent();
+      out.write(" *");
+      if (!line.isEmpty()) {
+        out.write(" ");
+        out.write(line);
+      }
+      out.write("\n");
+    }
+    indent();
+    out.write(" */\n");
+    return this;
+  }
+
+  /** Emits a single line comment. */
+  public JavaWriter emitSingleLineComment(String comment, Object... args) throws IOException {
+    indent();
+    out.write("// ");
+    out.write(String.format(comment, args));
+    out.write("\n");
+    return this;
+  }
+
+  public JavaWriter emitEmptyLine() throws IOException {
+    out.write("\n");
+    return this;
+  }
+
+  public JavaWriter emitEnumValue(String name) throws IOException {
+    indent();
+    out.write(name);
+    out.write(",\n");
+    return this;
+  }
+
+  /**
+   * A simple switch to emit the proper enum depending if its last causing it to be terminated
+   * by a semi-colon ({@code ;}).
+   */
+  public JavaWriter emitEnumValue(String name, boolean isLast) throws IOException {
+    return isLast ? emitLastEnumValue(name) : emitEnumValue(name);
+  }
+
+  private JavaWriter emitLastEnumValue(String name) throws IOException {
+    indent();
+    out.write(name);
+    out.write(";\n");
+    return this;
+  }
+
+  /** Emit a list of enum values followed by a semi-colon ({@code ;}). */
+  public JavaWriter emitEnumValues(Iterable<String> names) throws IOException {
+    Iterator<String> iterator = names.iterator();
+
+    while (iterator.hasNext()) {
+      String name = iterator.next();
+      if (iterator.hasNext()) {
+        emitEnumValue(name);
+      } else {
+        emitLastEnumValue(name);
+      }
+    }
+
+    return this;
+  }
+
+  /** Equivalent to {@code annotation(annotation, emptyMap())}. */
+  public JavaWriter emitAnnotation(String annotation) throws IOException {
+    return emitAnnotation(annotation, Collections.<String, Object>emptyMap());
+  }
+
+  /** Equivalent to {@code annotation(annotationType.getName(), emptyMap())}. */
+  public JavaWriter emitAnnotation(Class<? extends Annotation> annotationType) throws IOException {
+    return emitAnnotation(type(annotationType), Collections.<String, Object>emptyMap());
+  }
+
+  /**
+   * Annotates the next element with {@code annotationType} and a {@code value}.
+   *
+   * @param value an object used as the default (value) parameter of the annotation. The value will
+   *     be encoded using Object.toString(); use {@link #stringLiteral} for String values. Object
+   *     arrays are written one element per line.
+   */
+  public JavaWriter emitAnnotation(Class<? extends Annotation> annotationType, Object value)
+      throws IOException {
+    return emitAnnotation(type(annotationType), value);
+  }
+
+  /**
+   * Annotates the next element with {@code annotation} and a {@code value}.
+   *
+   * @param value an object used as the default (value) parameter of the annotation. The value will
+   *     be encoded using Object.toString(); use {@link #stringLiteral} for String values. Object
+   *     arrays are written one element per line.
+   */
+  public JavaWriter emitAnnotation(String annotation, Object value) throws IOException {
+    indent();
+    out.write("@");
+    emitCompressedType(annotation);
+    out.write("(");
+    emitAnnotationValue(value);
+    out.write(")");
+    out.write("\n");
+    return this;
+  }
+
+  /** Equivalent to {@code annotation(annotationType.getName(), attributes)}. */
+  public JavaWriter emitAnnotation(Class<? extends Annotation> annotationType,
+      Map<String, ?> attributes) throws IOException {
+    return emitAnnotation(type(annotationType), attributes);
+  }
+
+  /**
+   * Annotates the next element with {@code annotation} and {@code attributes}.
+   *
+   * @param attributes a map from annotation attribute names to their values. Values are encoded
+   *     using Object.toString(); use {@link #stringLiteral} for String values. Object arrays are
+   *     written one element per line.
+   */
+  public JavaWriter emitAnnotation(String annotation, Map<String, ?> attributes)
+      throws IOException {
+    indent();
+    out.write("@");
+    emitCompressedType(annotation);
+    switch (attributes.size()) {
+      case 0:
+        break;
+      case 1:
+        Entry<String, ?> onlyEntry = attributes.entrySet().iterator().next();
+        out.write("(");
+        if (!"value".equals(onlyEntry.getKey())) {
+          out.write(onlyEntry.getKey());
+          out.write(" = ");
+        }
+        emitAnnotationValue(onlyEntry.getValue());
+        out.write(")");
+        break;
+      default:
+        boolean split = attributes.size() > MAX_SINGLE_LINE_ATTRIBUTES
+            || containsArray(attributes.values());
+        out.write("(");
+        scopes.push(Scope.ANNOTATION_ATTRIBUTE);
+        String separator = split ? "\n" : "";
+        for (Map.Entry<String, ?> entry : attributes.entrySet()) {
+          out.write(separator);
+          separator = split ? ",\n" : ", ";
+          if (split) {
+            indent();
+          }
+          out.write(entry.getKey());
+          out.write(" = ");
+          Object value = entry.getValue();
+          emitAnnotationValue(value);
+        }
+        popScope(Scope.ANNOTATION_ATTRIBUTE);
+        if (split) {
+          out.write("\n");
+          indent();
+        }
+        out.write(")");
+        break;
+    }
+    out.write("\n");
+    return this;
+  }
+
+  private boolean containsArray(Collection<?> values) {
+    for (Object value : values) {
+      if (value instanceof Object[]) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Writes a single annotation value. If the value is an array, each element in the array will be
+   * written to its own line.
+   */
+  private JavaWriter emitAnnotationValue(Object value) throws IOException {
+    if (value instanceof Object[]) {
+      out.write("{");
+      boolean firstValue = true;
+      scopes.push(Scope.ANNOTATION_ARRAY_VALUE);
+      for (Object o : ((Object[]) value)) {
+        if (firstValue) {
+          firstValue = false;
+          out.write("\n");
+        } else {
+          out.write(",\n");
+        }
+        indent();
+        out.write(o.toString());
+      }
+      popScope(Scope.ANNOTATION_ARRAY_VALUE);
+      out.write("\n");
+      indent();
+      out.write("}");
+    } else {
+      out.write(value.toString());
+    }
+    return this;
+  }
+
+  /**
+   * @param pattern a code pattern like "int i = %s". Newlines will be further indented. Should not
+   *     contain trailing semicolon.
+   */
+  public JavaWriter emitStatement(String pattern, Object... args) throws IOException {
+    checkInMethod();
+    String[] lines = String.format(pattern, args).split("\n", -1);
+    indent();
+    out.write(lines[0]);
+    for (int i = 1; i < lines.length; i++) {
+      out.write("\n");
+      hangingIndent();
+      out.write(lines[i]);
+    }
+    out.write(";\n");
+    return this;
+  }
+
+  /**
+   * @param controlFlow the control flow construct and its code, such as "if (foo == 5)". Shouldn't
+   *     contain braces or newline characters.
+   */
+  // NOTE: This method is for binary compatibility with previous versions.
+  public JavaWriter beginControlFlow(String controlFlow) throws IOException {
+    return beginControlFlow(controlFlow, new Object[0]);
+  }
+
+  /**
+   * @param controlFlow the control flow construct and its code, such as "if (foo == 5)". Shouldn't
+   *     contain braces or newline characters.
+   */
+  public JavaWriter beginControlFlow(String controlFlow, Object... args) throws IOException {
+    checkInMethod();
+    indent();
+    out.write(String.format(controlFlow, args));
+    out.write(" {\n");
+    scopes.push(Scope.CONTROL_FLOW);
+    return this;
+  }
+
+  /**
+   * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
+   *     Shouldn't contain braces or newline characters.
+   */
+  // NOTE: This method is for binary compatibility with previous versions.
+  public JavaWriter nextControlFlow(String controlFlow) throws IOException {
+    return nextControlFlow(controlFlow, new Object[0]);
+  }
+
+  /**
+   * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
+   *     Shouldn't contain braces or newline characters.
+   */
+  public JavaWriter nextControlFlow(String controlFlow, Object... args) throws IOException {
+    popScope(Scope.CONTROL_FLOW);
+    indent();
+    scopes.push(Scope.CONTROL_FLOW);
+    out.write("} ");
+    out.write(String.format(controlFlow, args));
+    out.write(" {\n");
+    return this;
+  }
+
+  public JavaWriter endControlFlow() throws IOException {
+    return endControlFlow(null);
+  }
+
+  /**
+   * @param controlFlow the optional control flow construct and its code, such as
+   *     "while(foo == 20)". Only used for "do/while" control flows.
+   */
+  // NOTE: This method is for binary compatibility with previous versions.
+  public JavaWriter endControlFlow(String controlFlow) throws IOException {
+    return endControlFlow(controlFlow, new Object[0]);
+  }
+
+  /**
+   * @param controlFlow the optional control flow construct and its code, such as
+   *     "while(foo == 20)". Only used for "do/while" control flows.
+   */
+  public JavaWriter endControlFlow(String controlFlow, Object... args) throws IOException {
+    popScope(Scope.CONTROL_FLOW);
+    indent();
+    if (controlFlow != null) {
+      out.write("} ");
+      out.write(String.format(controlFlow, args));
+      out.write(";\n");
+    } else {
+      out.write("}\n");
+    }
+    return this;
+  }
+
+  /** Completes the current method declaration. */
+  public JavaWriter endMethod() throws IOException {
+    Scope popped = scopes.pop();
+    // support calling a constructor a "method" to support the legacy code
+    if (popped == Scope.NON_ABSTRACT_METHOD || popped == Scope.CONSTRUCTOR) {
+      indent();
+      out.write("}\n");
+    } else if (popped != Scope.ABSTRACT_METHOD) {
+      throw new IllegalStateException();
+    }
+    return this;
+  }
+
+  /** Completes the current constructor declaration. */
+  public JavaWriter endConstructor() throws IOException {
+    popScope(Scope.CONSTRUCTOR);
+    indent();
+    out.write("}\n");
+    return this;
+  }
+
+  /**
+   * Returns the string literal representing {@code data}, including wrapping quotes.
+   *
+   * @deprecated use {@link StringLiteral} and its {@link StringLiteral#literal()} method instead.
+   */
+  @Deprecated
+  public static String stringLiteral(String data) {
+    return StringLiteral.forValue(data).literal();
+  }
+
+  /** Build a string representation of a type and optionally its generic type arguments. */
+  public static String type(Class<?> raw, String... parameters) {
+    if (parameters.length == 0) {
+      return raw.getCanonicalName();
+    }
+    if (raw.getTypeParameters().length != parameters.length) {
+      throw new IllegalArgumentException();
+    }
+    StringBuilder result = new StringBuilder();
+    result.append(raw.getCanonicalName());
+    result.append("<");
+    result.append(parameters[0]);
+    for (int i = 1; i < parameters.length; i++) {
+      result.append(", ");
+      result.append(parameters[i]);
+    }
+    result.append(">");
+    return result.toString();
+  }
+
+  /** Build a string representation of the raw type for a (optionally generic) type. */
+  public static String rawType(String type) {
+    int lessThanIndex = type.indexOf('<');
+    if (lessThanIndex != -1) {
+      return type.substring(0, lessThanIndex);
+    }
+    return type;
+  }
+
+  @Override public void close() throws IOException {
+    out.close();
+  }
+
+  /** Emits the modifiers to the writer. */
+  private void emitModifiers(Set<Modifier> modifiers) throws IOException {
+    if (modifiers.isEmpty()) {
+      return;
+    }
+    // Use an EnumSet to ensure the proper ordering
+    if (!(modifiers instanceof EnumSet)) {
+      modifiers = EnumSet.copyOf(modifiers);
+    }
+    for (Modifier modifier : modifiers) {
+      out.append(modifier.toString()).append(' ');
+    }
+  }
+
+  private void indent() throws IOException {
+    for (int i = 0, count = scopes.size(); i < count; i++) {
+      out.write(indent);
+    }
+  }
+
+  private void hangingIndent() throws IOException {
+    for (int i = 0, count = scopes.size() + 2; i < count; i++) {
+      out.write(indent);
+    }
+  }
+
+  private static final EnumSet<Scope> METHOD_SCOPES = EnumSet.of(
+      Scope.NON_ABSTRACT_METHOD, Scope.CONSTRUCTOR, Scope.CONTROL_FLOW, Scope.INITIALIZER);
+
+  private void checkInMethod() {
+    if (!METHOD_SCOPES.contains(scopes.peekFirst())) {
+      throw new IllegalArgumentException();
+    }
+  }
+
+  private void popScope(Scope... expected) {
+    if (!EnumSet.copyOf(Arrays.asList(expected)).contains(scopes.pop())) {
+      throw new IllegalStateException();
+    }
+  }
+
+  private enum Scope {
+    TYPE_DECLARATION,
+    INTERFACE_DECLARATION,
+    ABSTRACT_METHOD,
+    NON_ABSTRACT_METHOD,
+    CONSTRUCTOR,
+    CONTROL_FLOW,
+    ANNOTATION_ATTRIBUTE,
+    ANNOTATION_ARRAY_VALUE,
+    INITIALIZER
+  }
+}
diff --git a/src/main/java/com/squareup/javawriter/StringLiteral.java b/src/main/java/com/squareup/javawriter/StringLiteral.java
new file mode 100644
index 0000000..c0a846d
--- /dev/null
+++ b/src/main/java/com/squareup/javawriter/StringLiteral.java
@@ -0,0 +1,91 @@
+// Copyright 2014 Square, Inc.
+package com.squareup.javawriter;
+
+import java.util.Formatter;
+
+/**
+ * Represents a string literal as found in Java source code.
+ */
+public final class StringLiteral {
+  /** Returns a new {@link StringLiteral} instance for the intended value of the literal. */
+  public static StringLiteral forValue(String value) {
+    return new StringLiteral(value, stringLiteral(value));
+  }
+
+  /** Returns the string literal representing {@code data}, including wrapping quotes. */
+  private static String stringLiteral(String value) {
+    StringBuilder result = new StringBuilder();
+    result.append('"');
+    for (int i = 0; i < value.length(); i++) {
+      char c = value.charAt(i);
+      switch (c) {
+        case '"':
+          result.append("\\\"");
+          break;
+        case '\\':
+          result.append("\\\\");
+          break;
+        case '\b':
+          result.append("\\b");
+          break;
+        case '\t':
+          result.append("\\t");
+          break;
+        case '\n':
+          result.append("\\n");
+          break;
+        case '\f':
+          result.append("\\f");
+          break;
+        case '\r':
+          result.append("\\r");
+          break;
+        default:
+          if (Character.isISOControl(c)) {
+            new Formatter(result).format("\\u%04x", (int) c);
+          } else {
+            result.append(c);
+          }
+      }
+    }
+    result.append('"');
+    return result.toString();
+  }
+
+  private final String value;
+  private final String literal;
+
+  private StringLiteral(String value, String literal) {
+    this.value = value;
+    this.literal = literal;
+  }
+
+  public String value() {
+    return value;
+  }
+
+  public String literal() {
+    return literal;
+  }
+
+  @Override
+  public String toString() {
+    return literal;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    } else if (obj instanceof StringLiteral) {
+      return this.value.equals(((StringLiteral) obj).value);
+    } else {
+      return false;
+    }
+  }
+
+  @Override
+  public int hashCode() {
+    return value.hashCode();
+  }
+}
diff --git a/src/test/java/com/example/Binding.java b/src/test/java/com/example/Binding.java
new file mode 100644
index 0000000..678c0c4
--- /dev/null
+++ b/src/test/java/com/example/Binding.java
@@ -0,0 +1,5 @@
+// Copyright 2013 Square, Inc.
+package com.example;
+
+public class Binding<T> {
+}
diff --git a/src/test/java/com/squareup/javawriter/JavaWriterTest.java b/src/test/java/com/squareup/javawriter/JavaWriterTest.java
new file mode 100644
index 0000000..d97f20a
--- /dev/null
+++ b/src/test/java/com/squareup/javawriter/JavaWriterTest.java
@@ -0,0 +1,922 @@
+// Copyright 2013 Square, Inc.
+package com.squareup.javawriter;
+
+import com.example.Binding;
+import java.io.IOException;
+import java.io.StringWriter;
+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.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.Modifier;
+import org.junit.Test;
+
+import static javax.lang.model.element.Modifier.ABSTRACT;
+import static javax.lang.model.element.Modifier.FINAL;
+import static javax.lang.model.element.Modifier.PRIVATE;
+import static javax.lang.model.element.Modifier.PUBLIC;
+import static javax.lang.model.element.Modifier.STATIC;
+import static org.fest.assertions.api.Assertions.assertThat;
+import static org.fest.assertions.api.Assertions.failBecauseExceptionWasNotThrown;
+
+public final class JavaWriterTest {
+  private final StringWriter stringWriter = new StringWriter();
+  private final JavaWriter javaWriter = new JavaWriter(stringWriter);
+
+  @Test public void typeDeclaration() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class", EnumSet.of(PUBLIC, FINAL));
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "public final class Foo {\n"
+        + "}\n");
+  }
+
+  @Test public void enumDeclaration() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "enum", EnumSet.of(PUBLIC));
+    javaWriter.emitEnumValue("BAR");
+    javaWriter.emitEnumValue("BAZ");
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "public enum Foo {\n"
+        + "  BAR,\n"
+        + "  BAZ,\n"
+        + "}\n");
+  }
+
+  @Test public void enumDeclarationWithMethod() throws IOException{
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "enum", EnumSet.of(PUBLIC));
+    javaWriter.emitEnumValues(Arrays.asList("BAR", "BAZ"));
+    javaWriter.beginMethod("void", "foo", EnumSet.of(PUBLIC));
+    javaWriter.endMethod();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "public enum Foo {\n"
+        + "  BAR,\n"
+        + "  BAZ;\n"
+        + "  public void foo() {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void enumDeclarationWithAnnotationAndMethod() throws IOException{
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "enum", EnumSet.of(PUBLIC));
+    List<String> list = Arrays.asList("BAR", "BAZ");
+    int index = 0;
+    for (Iterator<String> i = list.iterator(); i.hasNext(); ) {
+      javaWriter.emitAnnotation("ProtoEnum", index);
+      javaWriter.emitEnumValue(i.next(), !i.hasNext());
+      index++;
+    }
+    javaWriter.beginMethod("void", "foo", EnumSet.of(PUBLIC));
+    javaWriter.endMethod();
+    javaWriter.endType();
+    assertCode(""
+            + "package com.squareup;\n"
+            + "\n"
+            + "public enum Foo {\n"
+            + "  @ProtoEnum(0)\n"
+            + "  BAR,\n"
+            + "  @ProtoEnum(1)\n"
+            + "  BAZ;\n"
+            + "  public void foo() {\n"
+            + "  }\n"
+            + "}\n");
+  }
+
+  @Test public void fieldDeclaration() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.emitField("java.lang.String", "string", EnumSet.of(PRIVATE, STATIC));
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  private static String string;\n"
+        + "}\n");
+  }
+
+  @Test public void fieldDeclarationWithInitialValue() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.emitField("java.lang.String", "string", EnumSet.noneOf(Modifier.class),
+        "\"bar\" + \"baz\"");
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  String string = \"bar\" + \"baz\";\n"
+        + "}\n");
+  }
+
+  @Test public void fieldDeclarationWithWrappingInitialValue() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.emitField("java.lang.String", "string", EnumSet.noneOf(Modifier.class),
+        "\"bar\"\n+ \"baz\"\n+ \"biz\"");
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  String string = \"bar\"\n"
+        + "      + \"baz\"\n"
+        + "      + \"biz\";\n"
+        + "}\n");
+  }
+
+  // If the initializer begins with a newline, don't emit a space after the '='.
+  @Test public void fieldDeclarationWithNewlineInitialValue() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.emitField("java.lang.String", "string", EnumSet.noneOf(Modifier.class),
+        "\n\"bar\"\n+ \"baz\"\n+ \"biz\"");
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  String string =\n"
+        + "      \"bar\"\n"
+        + "      + \"baz\"\n"
+        + "      + \"biz\";\n"
+        + "}\n");
+  }
+
+  @Test public void abstractMethodDeclaration() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.beginMethod("java.lang.String", "foo", EnumSet.of(ABSTRACT, PUBLIC),
+        "java.lang.Object", "object", "java.lang.String", "s");
+    javaWriter.endMethod();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  public abstract String foo(Object object, String s);\n"
+        + "}\n");
+  }
+
+  @Test public void abstractMethodDeclarationWithThrows() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.beginMethod("java.lang.String", "foo", EnumSet.of(ABSTRACT, PUBLIC),
+        Arrays.asList("java.lang.Object", "object", "java.lang.String", "s"),
+        Arrays.asList("java.io.IOException"));
+    javaWriter.endMethod();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  public abstract String foo(Object object, String s)\n"
+        + "      throws java.io.IOException;\n"
+        + "}\n");
+  }
+
+  @Test public void nonAbstractMethodDeclaration() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.beginMethod("int", "foo", EnumSet.noneOf(Modifier.class), "java.lang.String", "s");
+    javaWriter.endMethod();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  int foo(String s) {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void nonAbstractMethodDeclarationWithThrows() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.beginMethod("int", "foo", EnumSet.noneOf(Modifier.class),
+        Arrays.asList("java.lang.String", "s"), Arrays.asList("java.io.IOException"));
+    javaWriter.endMethod();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  int foo(String s)\n"
+        + "      throws java.io.IOException {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void interfaceMethodDeclaration() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "interface");
+    javaWriter.beginMethod("java.lang.String", "foo", EnumSet.noneOf(Modifier.class),
+        "java.lang.Object", "object", "java.lang.String", "s");
+    javaWriter.endMethod();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "interface Foo {\n"
+        + "  String foo(Object object, String s);\n"
+        + "}\n");
+  }
+
+  @Test public void interfaceMethodDeclarationWithThrows() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "interface");
+    javaWriter.beginMethod("java.lang.String", "foo", EnumSet.noneOf(Modifier.class),
+        Arrays.asList("java.lang.Object", "object", "java.lang.String", "s"),
+        Arrays.asList("java.io.IOException"));
+    javaWriter.endMethod();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "interface Foo {\n"
+        + "  String foo(Object object, String s)\n"
+        + "      throws java.io.IOException;\n"
+        + "}\n");
+  }
+
+  @Test public void constructorDeclaration() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.beginConstructor(EnumSet.of(PUBLIC), "java.lang.String", "s");
+    javaWriter.endConstructor();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  public Foo(String s) {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void simpleConstructor() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.beginConstructor(EnumSet.of(PUBLIC), "java.lang.String", "s");
+    javaWriter.emitStatement("if (%s == null) throw new NullPointerException()", "s");
+    javaWriter.endConstructor();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  public Foo(String s) {\n"
+        + "    if (s == null) throw new NullPointerException();\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void genericsConstructor() throws IOException {
+      javaWriter.emitPackage("com.squareup");
+      javaWriter.beginType("com.squareup.Foo<T>", "class");
+      javaWriter.emitField("T", "fooType", EnumSet.of(PRIVATE));
+      javaWriter.beginConstructor(EnumSet.of(PUBLIC), "T", "s");
+      javaWriter.emitStatement("if (%s == null) throw new NullPointerException()", "s");
+      javaWriter.endConstructor();
+      javaWriter.beginMethod("T", "getFooType", EnumSet.of(PUBLIC));
+      javaWriter.emitStatement("return fooType");
+      javaWriter.endMethod();
+      javaWriter.endType();
+      assertCode(""
+          + "package com.squareup;\n"
+          + "\n"
+          + "class Foo<T> {\n"
+          + "  private T fooType;\n"
+          + "  public Foo(T s) {\n"
+          + "    if (s == null) throw new NullPointerException();\n"
+          + "  }\n"
+          + "  public T getFooType() {\n"
+          + "    return fooType;\n"
+          + "  }\n"
+          + "}\n");
+    }
+
+  @Test public void constructorDeclarationInNestedTypes() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.beginConstructor(EnumSet.of(PUBLIC), "java.lang.String", "s");
+    javaWriter.endConstructor();
+    javaWriter.beginType("com.squareup.Bar", "class");
+    javaWriter.beginConstructor(EnumSet.noneOf(Modifier.class));
+    javaWriter.endConstructor();
+    javaWriter.endType();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  public Foo(String s) {\n"
+        + "  }\n"
+        + "  class Bar {\n"
+        + "    Bar() {\n"
+        + "    }\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void constructorDeclarationWithThrows() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.beginConstructor(EnumSet.of(PUBLIC),
+        Arrays.asList("java.lang.String", "s"), Arrays.asList("java.io.IOException"));
+    javaWriter.endConstructor();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  public Foo(String s)\n"
+        + "      throws java.io.IOException {\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void statement() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.beginMethod("int", "foo", EnumSet.noneOf(Modifier.class), "java.lang.String", "s");
+    javaWriter.emitStatement("int j = s.length() + %s", 13);
+    javaWriter.endMethod();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  int foo(String s) {\n"
+        + "    int j = s.length() + 13;\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void statementPrecededByComment() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.beginMethod("int", "foo", EnumSet.noneOf(Modifier.class), "java.lang.String", "s");
+    javaWriter.emitSingleLineComment("foo");
+    javaWriter.emitStatement("int j = s.length() + %s", 13);
+    javaWriter.endMethod();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  int foo(String s) {\n"
+        + "    // foo\n"
+        + "    int j = s.length() + 13;\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void multiLineStatement() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Triangle", "class");
+    javaWriter.beginMethod("double", "pythagorean", EnumSet.noneOf(Modifier.class),
+        "int", "a", "int", "b");
+    javaWriter.emitStatement("int cSquared = a * a\n+ b * b");
+    javaWriter.emitStatement("return Math.sqrt(cSquared)");
+    javaWriter.endMethod();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Triangle {\n"
+        + "  double pythagorean(int a, int b) {\n"
+        + "    int cSquared = a * a\n"
+        + "        + b * b;\n"
+        + "    return Math.sqrt(cSquared);\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void addImport() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.emitImports("java.util.ArrayList");
+    javaWriter.beginType("com.squareup.Foo", "class", EnumSet.of(PUBLIC, FINAL));
+    javaWriter.emitField("java.util.ArrayList", "list", EnumSet.noneOf(Modifier.class),
+        "new java.util.ArrayList()");
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "import java.util.ArrayList;\n"
+        + "public final class Foo {\n"
+        + "  ArrayList list = new java.util.ArrayList();\n"
+        + "}\n");
+  }
+
+  @Test public void addImportAsClass() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.emitImports(ArrayList.class);
+    javaWriter.beginType("com.squareup.Foo", "class", EnumSet.of(PUBLIC, FINAL));
+    javaWriter.emitField("java.util.ArrayList", "list", EnumSet.noneOf(Modifier.class),
+        "new java.util.ArrayList()");
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "import java.util.ArrayList;\n"
+        + "public final class Foo {\n"
+        + "  ArrayList list = new java.util.ArrayList();\n"
+        + "}\n");
+  }
+
+  @Test public void addStaticImport() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.emitStaticImports("java.lang.System.getProperty");
+    javaWriter.beginType("com.squareup.Foo", "class", EnumSet.of(PUBLIC, FINAL));
+    javaWriter.emitField("String", "bar", EnumSet.noneOf(Modifier.class), "getProperty(\"bar\")");
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "import static java.lang.System.getProperty;\n"
+        + "public final class Foo {\n"
+        + "  String bar = getProperty(\"bar\");\n"
+        + "}\n");
+  }
+
+  @Test
+  public void addNestedClassImportAsClass() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.emitImports(NestedClass.class);
+    javaWriter.beginType("com.squareup.Foo", "class", EnumSet.of(PUBLIC, FINAL));
+    javaWriter.emitField("com.squareup.javawriter.JavaWriterTest.NestedClass", "nestedClass",
+        EnumSet.noneOf(Modifier.class), "new NestedClass()");
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "import com.squareup.javawriter.JavaWriterTest.NestedClass;\n"
+        + "public final class Foo {\n"
+        + "  NestedClass nestedClass = new NestedClass();\n"
+        + "}\n");
+  }
+
+  public static class NestedClass {}
+
+  @Test public void addStaticWildcardImport() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.emitStaticImports("java.lang.System.*");
+    javaWriter.beginType("com.squareup.Foo", "class", EnumSet.of(PUBLIC, FINAL));
+    javaWriter.emitField("String", "bar", EnumSet.noneOf(Modifier.class), "getProperty(\"bar\")");
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "import static java.lang.System.*;\n"
+        + "public final class Foo {\n"
+        + "  String bar = getProperty(\"bar\");\n"
+        + "}\n");
+  }
+
+  @Test public void emptyImports() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.emitImports(Collections.<String>emptyList());
+    javaWriter.beginType("com.squareup.Foo", "class", EnumSet.of(PUBLIC, FINAL));
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "public final class Foo {\n"
+        + "}\n");
+  }
+
+  @Test public void emptyStaticImports() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.emitStaticImports(Collections.<String>emptyList());
+    javaWriter.beginType("com.squareup.Foo", "class", EnumSet.of(PUBLIC, FINAL));
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "public final class Foo {\n"
+        + "}\n");
+  }
+
+  @Test public void addImportFromSubpackage() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class", EnumSet.of(PUBLIC, FINAL));
+    javaWriter.emitField("com.squareup.bar.Baz", "baz");
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "public final class Foo {\n"
+        + "  com.squareup.bar.Baz baz;\n"
+        + "}\n");
+  }
+
+  @Test public void ifControlFlow() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.beginMethod("int", "foo", EnumSet.noneOf(Modifier.class), "java.lang.String", "s");
+    javaWriter.beginControlFlow("if (s.isEmpty())");
+    javaWriter.emitStatement("int j = s.length() + %s", 13);
+    javaWriter.endControlFlow();
+    javaWriter.endMethod();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  int foo(String s) {\n"
+        + "    if (s.isEmpty()) {\n"
+        + "      int j = s.length() + 13;\n"
+        + "    }\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void doWhileControlFlow() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.beginMethod("int", "foo", EnumSet.noneOf(Modifier.class), "java.lang.String", "s");
+    javaWriter.beginControlFlow("do");
+    javaWriter.emitStatement("int j = s.length() + %s", 13);
+    javaWriter.endControlFlow("while (s.isEmpty())");
+    javaWriter.endMethod();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  int foo(String s) {\n"
+        + "    do {\n"
+        + "      int j = s.length() + 13;\n"
+        + "    } while (s.isEmpty());\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void tryCatchFinallyControlFlow() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.beginMethod("int", "foo", EnumSet.noneOf(Modifier.class), "java.lang.String", "s");
+    javaWriter.beginControlFlow("try");
+    javaWriter.emitStatement("int j = s.length() + %s", 13);
+    javaWriter.nextControlFlow("catch (RuntimeException e)");
+    javaWriter.emitStatement("e.printStackTrace()");
+    javaWriter.nextControlFlow("finally");
+    javaWriter.emitStatement("int k = %s", 13);
+    javaWriter.endControlFlow();
+    javaWriter.endMethod();
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  int foo(String s) {\n"
+        + "    try {\n"
+        + "      int j = s.length() + 13;\n"
+        + "    } catch (RuntimeException e) {\n"
+        + "      e.printStackTrace();\n"
+        + "    } finally {\n"
+        + "      int k = 13;\n"
+        + "    }\n"
+        + "  }\n"
+        + "}\n");
+  }
+
+  @Test public void annotatedType() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.emitImports("javax.inject.Singleton");
+    javaWriter.emitAnnotation("javax.inject.Singleton");
+    javaWriter.emitAnnotation(SuppressWarnings.class,
+        StringLiteral.forValue("unchecked"));
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "import javax.inject.Singleton;\n"
+        + "@Singleton\n"
+        + "@SuppressWarnings(\"unchecked\")\n"
+        + "class Foo {\n"
+        + "}\n");
+  }
+
+  @Test public void annotatedMember() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.emitAnnotation(Deprecated.class);
+    javaWriter.emitField("java.lang.String", "s");
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "  @Deprecated\n"
+        + "  String s;\n"
+        + "}\n");
+  }
+
+  @Test public void annotatedWithSingleAttribute() throws IOException {
+    Map<String, Object> attributes = new LinkedHashMap<String, Object>();
+    attributes.put("overrides", true);
+
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.emitAnnotation("Module", attributes);
+    javaWriter.beginType("com.squareup.FooModule", "class", EnumSet.noneOf(Modifier.class));
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "@Module(overrides = true)\n"
+        + "class FooModule {\n"
+        + "}\n");
+  }
+
+  @Test public void annotatedWithSingleValueAttribute() throws IOException {
+    Map<String, Object> attributes = new LinkedHashMap<String, Object>();
+    attributes.put("value", StringLiteral.forValue("blah.Generator"));
+
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.emitAnnotation("Generated", attributes);
+    javaWriter.beginType("com.squareup.FooModule", "class", EnumSet.noneOf(Modifier.class));
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "@Generated(\"blah.Generator\")\n"
+        + "class FooModule {\n"
+        + "}\n");
+  }
+
+  @Test public void annotatedWithTwoNonArrayAttributes() throws IOException {
+    Map<String, Object> attributes = new LinkedHashMap<String, Object>();
+    attributes.put("overrides", true);
+    attributes.put("foo", "bar");
+
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.emitAnnotation("Module", attributes);
+    javaWriter.beginType("com.squareup.FooModule", "class", EnumSet.noneOf(Modifier.class));
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "@Module(overrides = true, foo = bar)\n"
+        + "class FooModule {\n"
+        + "}\n");
+  }
+
+  @Test public void annotatedWithThreeNonArrayAttributes() throws IOException {
+    Map<String, Object> attributes = new LinkedHashMap<String, Object>();
+    attributes.put("overrides", true);
+    attributes.put("foo", "bar");
+    attributes.put("bar", "baz");
+
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.emitAnnotation("Module", attributes);
+    javaWriter.beginType("com.squareup.FooModule", "class", EnumSet.noneOf(Modifier.class));
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "@Module(overrides = true, foo = bar, bar = baz)\n"
+        + "class FooModule {\n"
+        + "}\n");
+  }
+
+  @Test public void annotatedWithAttributes() throws IOException {
+    Map<String, Object> attributes = new LinkedHashMap<String, Object>();
+    attributes.put("overrides", true);
+    attributes.put("entryPoints", new Object[] { "entryPointA", "entryPointB", "entryPointC" });
+    attributes.put("staticInjections", "com.squareup.Quux");
+
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.emitAnnotation("Module", attributes);
+    javaWriter.beginType("com.squareup.FooModule", "class");
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "@Module(\n"
+        + "  overrides = true,\n"
+        + "  entryPoints = {\n"
+        + "    entryPointA,\n"
+        + "    entryPointB,\n"
+        + "    entryPointC\n"
+        + "  },\n"
+        + "  staticInjections = com.squareup.Quux\n"
+        + ")\n"
+        + "class FooModule {\n"
+        + "}\n");
+  }
+
+  @Test public void parameterizedType() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.emitImports("java.util.Map", "java.util.Date");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.emitField("java.util.Map<java.lang.String, java.util.Date>", "map",
+        EnumSet.noneOf(Modifier.class));
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "import java.util.Date;\n"
+        + "import java.util.Map;\n"
+        + "class Foo {\n"
+        + "  Map<String, Date> map;\n"
+        + "}\n");
+  }
+
+  @Test public void eolComment() throws IOException {
+    javaWriter.emitSingleLineComment("foo");
+    assertCode("// foo\n");
+  }
+
+  @Test public void javadoc() throws IOException {
+    javaWriter.emitJavadoc("foo");
+    assertCode(""
+        + "/**\n"
+        + " * foo\n"
+        + " */\n");
+  }
+
+  @Test public void multilineJavadoc() throws IOException {
+    javaWriter.emitJavadoc("0123456789 0123456789 0123456789 0123456789 0123456789 0123456789\n"
+        + "0123456789 0123456789 0123456789 0123456789");
+    assertCode(""
+        + "/**\n"
+        + " * 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789\n"
+        + " * 0123456789 0123456789 0123456789 0123456789\n"
+        + " */\n");
+  }
+
+  @Test public void multilineJavadocDoesNotEmitTrailingSpaceForEmptyLines() throws IOException {
+    javaWriter.emitJavadoc("Foo\n\nBar");
+    assertCode(""
+        + "/**\n"
+        + " * Foo\n"
+        + " *\n"
+        + " * Bar\n"
+        + " */\n"
+    );
+  }
+
+  @Test public void testType() {
+    assertThat(JavaWriter.type(String.class)).as("simple type").isEqualTo("java.lang.String");
+    assertThat(JavaWriter.type(Set.class)).as("raw type").isEqualTo("java.util.Set");
+    assertThat(JavaWriter.type(Set.class, "?")).as("wildcard type").isEqualTo("java.util.Set<?>");
+    assertThat(JavaWriter.type(Map.class, JavaWriter.type(String.class), "?"))
+        .as("mixed type and wildcard generic type parameters")
+        .isEqualTo("java.util.Map<java.lang.String, ?>");
+    try {
+      JavaWriter.type(String.class, "foo");
+      failBecauseExceptionWasNotThrown(IllegalArgumentException.class);
+    } catch (Throwable e) {
+      assertThat(e).as("parameterized non-generic").isInstanceOf(IllegalArgumentException.class);
+    }
+    try {
+      JavaWriter.type(Map.class, "foo");
+      failBecauseExceptionWasNotThrown(IllegalArgumentException.class);
+    } catch (Throwable e) {
+      assertThat(e).as("too few type arguments").isInstanceOf(IllegalArgumentException.class);
+    }
+    try {
+      JavaWriter.type(Set.class, "foo", "bar");
+      failBecauseExceptionWasNotThrown(IllegalArgumentException.class);
+    } catch (Throwable e) {
+      assertThat(e).as("too many type arguments").isInstanceOf(IllegalArgumentException.class);
+    }
+  }
+
+  @Test public void testRawType() {
+      assertThat(JavaWriter.rawType(JavaWriter.type(Set.class)))
+          .as("raw type").isEqualTo("java.util.Set");
+      assertThat(JavaWriter.rawType(JavaWriter.type(Set.class, "?")))
+          .as("wildcard type").isEqualTo("java.util.Set");
+      assertThat(JavaWriter.rawType(JavaWriter.type(Set.class, "String")))
+          .as("parameterized type").isEqualTo("java.util.Set");
+      assertThat(JavaWriter.rawType(JavaWriter.type(Map.class, "String", "Integer")))
+          .as("parameterized type").isEqualTo("java.util.Map");
+      assertThat(JavaWriter.rawType("java.util.Set<com.example.Binding<com.blah.Foo.Blah>>"))
+          .as("nested parameterized type").isEqualTo("java.util.Set");
+  }
+
+  @Test public void compressType() throws IOException {
+    javaWriter.emitPackage("com.blah");
+    javaWriter.emitImports(Set.class.getCanonicalName(), Binding.class.getCanonicalName());
+    String actual =
+        javaWriter.compressType("java.util.Set<com.example.Binding<com.blah.Foo.Blah>>");
+    assertThat(actual).isEqualTo("Set<Binding<Foo.Blah>>");
+  }
+
+  @Test public void compressDeeperType() throws IOException {
+    javaWriter.emitPackage("com.blah");
+    javaWriter.emitImports(Binding.class.getCanonicalName());
+    String actual = javaWriter.compressType("com.example.Binding<com.blah.foo.Foo.Blah>");
+    assertThat(actual).isEqualTo("Binding<com.blah.foo.Foo.Blah>");
+  }
+
+  @Test public void compressNestedType() throws IOException {
+    javaWriter.emitPackage("com.blah");
+    String actual = javaWriter.compressType("com.blah.Enclosing.Nested");
+    assertThat(actual).isEqualTo("Enclosing.Nested");
+  }
+
+  @Test public void compressWildcardType() throws IOException {
+    javaWriter.emitPackage("com.blah");
+    javaWriter.emitImports(Binding.class.getCanonicalName());
+    String actual = javaWriter.compressType("com.example.Binding<? extends com.blah.Foo.Blah>");
+    assertThat(actual).isEqualTo("Binding<? extends Foo.Blah>");
+  }
+
+  @Test public void compressSimpleNameCollisionInSamePackage() throws IOException {
+    javaWriter.emitPackage("denominator");
+    javaWriter.emitImports("javax.inject.Provider", "dagger.internal.Binding");
+    String actual = javaWriter.compressType("dagger.internal.Binding<denominator.Provider>");
+    assertThat(actual).isEqualTo("Binding<denominator.Provider>");
+  }
+
+  @Test public void compressJavaLangClass() throws IOException {
+    javaWriter.emitPackage("com.blah");
+    String actual = javaWriter.compressType("java.lang.Class");
+    assertThat(actual).isEqualTo("Class");
+  }
+
+  @Test public void compressJavaLangSubPackageClass() throws IOException {
+    javaWriter.emitPackage("com.blah");
+    String actual = javaWriter.compressType("java.lang.annotation.Annotation");
+    assertThat(actual).isEqualTo("java.lang.annotation.Annotation");
+  }
+
+  @Test public void compressVarargsType() throws IOException {
+    javaWriter.emitPackage("com.blah");
+    javaWriter.emitImports("java.util.File");
+    String actual = javaWriter.compressType("java.util.File...");
+    assertThat(actual).isEqualTo("File...");
+  }
+
+  @Test public void compressArrayType() throws IOException {
+    javaWriter.emitPackage("com.blah");
+    javaWriter.emitImports("java.util.File");
+    String actual1 = javaWriter.compressType("java.util.File[]");
+    assertThat(actual1).isEqualTo("File[]");
+    String actual2 = javaWriter.compressType("java.util.File[][][]");
+    assertThat(actual2).isEqualTo("File[][][]");
+  }
+
+  @Test public void configurableIndent() throws IOException {
+    javaWriter.setIndent("    ");
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class");
+    javaWriter.emitField("String", "bar");
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "    String bar;\n"
+        + "}\n");
+  }
+
+  @Test public void outOfOrderModifierSet() throws IOException {
+    Set<Modifier> modifiers = new LinkedHashSet<Modifier>(Arrays.asList(FINAL, PUBLIC));
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class", modifiers);
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "public final class Foo {\n"
+        + "}\n");
+  }
+
+  @Test public void emptyNonEnumModifierSet() throws IOException {
+    javaWriter.emitPackage("com.squareup");
+    javaWriter.beginType("com.squareup.Foo", "class", new LinkedHashSet<Modifier>());
+    javaWriter.endType();
+    assertCode(""
+        + "package com.squareup;\n"
+        + "\n"
+        + "class Foo {\n"
+        + "}\n");
+  }
+
+  private void assertCode(String expected) {
+    assertThat(stringWriter.toString()).isEqualTo(expected);
+  }
+}
diff --git a/src/test/java/com/squareup/javawriter/StringLiteralTest.java b/src/test/java/com/squareup/javawriter/StringLiteralTest.java
new file mode 100644
index 0000000..bd5f8bf
--- /dev/null
+++ b/src/test/java/com/squareup/javawriter/StringLiteralTest.java
@@ -0,0 +1,46 @@
+// Copyright 2014 Square, Inc.
+package com.squareup.javawriter;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+
+ at RunWith(JUnit4.class)
+public final class StringLiteralTest {
+  @Test public void stringLiteral() {
+    assertThat(StringLiteral.forValue("").toString()).isEqualTo("\"\"");
+    assertThat(StringLiteral.forValue("JavaWriter").toString()).isEqualTo("\"JavaWriter\"");
+    assertThat(StringLiteral.forValue("\\").toString()).isEqualTo("\"\\\\\"");
+    assertThat(StringLiteral.forValue("\"").toString()).isEqualTo("\"\\\"\"");
+    assertThat(StringLiteral.forValue("\b").toString()).isEqualTo("\"\\b\"");
+    assertThat(StringLiteral.forValue("\t").toString()).isEqualTo("\"\\t\"");
+    assertThat(StringLiteral.forValue("\n").toString()).isEqualTo("\"\\n\"");
+    assertThat(StringLiteral.forValue("\f").toString()).isEqualTo("\"\\f\"");
+    assertThat(StringLiteral.forValue("\r").toString()).isEqualTo("\"\\r\"");
+
+    // Control characters
+    for (char i = 0x1; i <= 0x1f; i++) {
+      checkCharEscape(i);
+    }
+    for (char i = 0x7f; i <= 0x9f; i++) {
+      checkCharEscape(i);
+    }
+  }
+
+  private void checkCharEscape(char codePoint) {
+    String test = "" + codePoint;
+    String expected;
+    switch (codePoint) {
+      case 8: expected = "\"\\b\""; break;
+      case 9: expected = "\"\\t\""; break;
+      case 10: expected = "\"\\n\""; break;
+      case 12: expected = "\"\\f\""; break;
+      case 13: expected = "\"\\r\""; break;
+      default: expected = "\"\\u" + String.format("%04x", (int) codePoint) + "\"";
+    }
+    assertThat(StringLiteral.forValue(test).toString()).isEqualTo(expected);
+  }
+
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/javawriter.git



More information about the pkg-java-commits mailing list