aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAshley Rose <ashleyrose@google.com>2018-11-19 16:56:29 -0500
committerAshley Rose <ashleyrose@google.com>2018-11-19 16:56:29 -0500
commitf4c8cf525f18c05f4e5339b0ea9dba1f825fb5e6 (patch)
treec45e39951dd760bd55e5d56c9dc1575c3bdeefcc
parent6803793e9e910afe8eed9b71bce546d3e23a6b89 (diff)
parenta5db06d05f8bd799cef194af28ace07427e2d07e (diff)
downloadjavapoet-f4c8cf525f18c05f4e5339b0ea9dba1f825fb5e6.tar.gz
Merge commit 'a5db06d'
This is the upstream commit for JavaPoet v1.11.1 Change-Id: I3372f7a59c8888b66930b67e73f8a9d3a6c51167
-rwxr-xr-x.buildscript/deploy_snapshot.sh26
-rw-r--r--.buildscript/settings.xml9
-rw-r--r--.gitignore23
-rw-r--r--.travis.yml46
-rw-r--r--CHANGELOG.md302
-rw-r--r--CONTRIBUTING.md17
-rw-r--r--LICENSE.txt202
-rw-r--r--README.md889
-rw-r--r--checkstyle.xml148
-rw-r--r--pom.xml159
-rw-r--r--src/main/java/com/squareup/javapoet/AnnotationSpec.java279
-rw-r--r--src/main/java/com/squareup/javapoet/ArrayTypeName.java109
-rw-r--r--src/main/java/com/squareup/javapoet/ClassName.java280
-rw-r--r--src/main/java/com/squareup/javapoet/CodeBlock.java464
-rw-r--r--src/main/java/com/squareup/javapoet/CodeWriter.java497
-rw-r--r--src/main/java/com/squareup/javapoet/FieldSpec.java174
-rw-r--r--src/main/java/com/squareup/javapoet/JavaFile.java274
-rw-r--r--src/main/java/com/squareup/javapoet/LineWrapper.java137
-rw-r--r--src/main/java/com/squareup/javapoet/MethodSpec.java493
-rw-r--r--src/main/java/com/squareup/javapoet/NameAllocator.java168
-rw-r--r--src/main/java/com/squareup/javapoet/ParameterSpec.java172
-rw-r--r--src/main/java/com/squareup/javapoet/ParameterizedTypeName.java138
-rw-r--r--src/main/java/com/squareup/javapoet/TypeName.java385
-rw-r--r--src/main/java/com/squareup/javapoet/TypeSpec.java632
-rw-r--r--src/main/java/com/squareup/javapoet/TypeVariableName.java168
-rw-r--r--src/main/java/com/squareup/javapoet/Util.java131
-rw-r--r--src/main/java/com/squareup/javapoet/WildcardTypeName.java129
-rw-r--r--src/test/java/com/squareup/javapoet/AbstractTypesTest.java285
-rw-r--r--src/test/java/com/squareup/javapoet/AnnotatedTypeNameTest.java217
-rw-r--r--src/test/java/com/squareup/javapoet/AnnotationSpecTest.java377
-rw-r--r--src/test/java/com/squareup/javapoet/ClassNameTest.java196
-rw-r--r--src/test/java/com/squareup/javapoet/CodeBlockTest.java342
-rw-r--r--src/test/java/com/squareup/javapoet/FieldSpecTest.java47
-rw-r--r--src/test/java/com/squareup/javapoet/FileReadingTest.java122
-rw-r--r--src/test/java/com/squareup/javapoet/FileWritingTest.java219
-rw-r--r--src/test/java/com/squareup/javapoet/JavaFileTest.java692
-rw-r--r--src/test/java/com/squareup/javapoet/LineWrapperTest.java207
-rw-r--r--src/test/java/com/squareup/javapoet/MethodSpecTest.java305
-rw-r--r--src/test/java/com/squareup/javapoet/NameAllocatorTest.java108
-rw-r--r--src/test/java/com/squareup/javapoet/ParameterSpecTest.java46
-rw-r--r--src/test/java/com/squareup/javapoet/TestFiler.java87
-rw-r--r--src/test/java/com/squareup/javapoet/TypeNameTest.java184
-rw-r--r--src/test/java/com/squareup/javapoet/TypeSpecTest.java2359
-rw-r--r--src/test/java/com/squareup/javapoet/TypesEclipseTest.java158
-rw-r--r--src/test/java/com/squareup/javapoet/TypesTest.java40
-rw-r--r--src/test/java/com/squareup/javapoet/UtilTest.java75
46 files changed, 12517 insertions, 0 deletions
diff --git a/.buildscript/deploy_snapshot.sh b/.buildscript/deploy_snapshot.sh
new file mode 100755
index 0000000..395a873
--- /dev/null
+++ b/.buildscript/deploy_snapshot.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# Deploy a jar, source jar, and javadoc jar to Sonatype's snapshot repo.
+#
+# Adapted from https://coderwall.com/p/9b_lfq and
+# https://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/
+
+SLUG="square/javapoet"
+JDK="oraclejdk8"
+BRANCH="master"
+
+set -e
+
+if [ "$TRAVIS_REPO_SLUG" != "$SLUG" ]; then
+ echo "Skipping snapshot deployment: wrong repository. Expected '$SLUG' but was '$TRAVIS_REPO_SLUG'."
+elif [ "$TRAVIS_JDK_VERSION" != "$JDK" ]; then
+ echo "Skipping snapshot deployment: wrong JDK. Expected '$JDK' but was '$TRAVIS_JDK_VERSION'."
+elif [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
+ echo "Skipping snapshot deployment: was pull request."
+elif [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then
+ echo "Skipping snapshot deployment: wrong branch. Expected '$BRANCH' but was '$TRAVIS_BRANCH'."
+else
+ echo "Deploying snapshot..."
+ mvn clean source:jar javadoc:jar deploy --settings=".buildscript/settings.xml" -Dmaven.test.skip=true
+ echo "Snapshot deployed!"
+fi
diff --git a/.buildscript/settings.xml b/.buildscript/settings.xml
new file mode 100644
index 0000000..91f444b
--- /dev/null
+++ b/.buildscript/settings.xml
@@ -0,0 +1,9 @@
+<settings>
+ <servers>
+ <server>
+ <id>sonatype-nexus-snapshots</id>
+ <username>${env.CI_DEPLOY_USERNAME}</username>
+ <password>${env.CI_DEPLOY_PASSWORD}</password>
+ </server>
+ </servers>
+</settings>
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..be0d31a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+.classpath
+.project
+.settings
+.checkstyle
+eclipsebin
+
+bin
+gen
+build
+out
+lib
+
+target
+pom.xml.*
+release.properties
+
+.idea
+*.iml
+classes
+
+obj
+
+.DS_Store
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..4e0a881
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,46 @@
+language: java
+
+matrix:
+ include:
+ - env: JDK='Oracle JDK 8'
+ jdk: oraclejdk8
+ - env: JDK='Oracle JDK 9'
+ jdk: oraclejdk9
+ - env: JDK='Oracle JDK 10'
+ install: . ./install-jdk.sh -F 10 -L BCL
+ - env: JDK='OpenJDK 10'
+ install: . ./install-jdk.sh -F 10 -L GPL
+ - env: JDK='Oracle JDK 11'
+ install: . ./install-jdk.sh -F 11 -L BCL
+ - env: JDK='OpenJDK 11'
+ install: . ./install-jdk.sh -F 11 -L GPL
+ allow_failures:
+ # ErrorProne/javac is not yet working on JDK 11
+ - env: JDK='Oracle JDK 11'
+ - env: JDK='OpenJDK 11'
+
+# Direct usage of `install-jdk.sh` might be superseded by https://github.com/travis-ci/travis-build/pull/1347
+before_install:
+ - unset _JAVA_OPTIONS
+ - wget https://github.com/sormuras/bach/raw/1.0.1/install-jdk.sh
+
+after_success:
+ - .buildscript/deploy_snapshot.sh
+
+env:
+ global:
+ - secure: "nkVNCk8H2orIZOmow0t+Qub1lFQCYpJgNZf17zYI5x0JVqQNCqkcTYYDHqzwkvkmixXFCrfYZQuXy7x2qg9zjCX+vmhlmiMWwe8dNa34OLTseuuR2irS0C8nRGRYxKM7EGenRZSqbFVUksKRm2iWnHKxtmCzeDaS7MoMit2wdUo="
+ - secure: "j8+hPaZnyM+UlOBYOEA96fPbVWbN6bMQ28SGQnFMwxo2axHi9ww9Au1N7002HzHnxX8iyesdWFBigArnEL8zKEoXH9Bmur0sn3Ys4bu72C3ozscP4cjXfYSHj8aVLp1EIMdQPDF7MkCccx9l7ONdsW0ltmdiVUtDxzqkH+63WLU="
+
+branches:
+ except:
+ - gh-pages
+
+notifications:
+ email: false
+
+sudo: false
+
+cache:
+ directories:
+ - $HOME/.m2
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..3933dd0
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,302 @@
+Change Log
+==========
+
+JavaPoet 1.11.1 *(2018-05-16)*
+-----------------------------
+
+ * Fix: JavaPoet 1.11 had a regression where `TypeName.get()` would throw on error types, masking
+ other errors in an annotation processing round. This is fixed with a test to prevent future
+ regressions!
+
+
+JavaPoet 1.11.0 *(2018-04-29)*
+-----------------------------
+
+ * New: Support `TYPE_USE` annotations on each enclosing `ClassName`.
+ * New: Work around a compiler bug in `TypeName.get(TypeElement)`. There was a problem getting an
+ element's kind when building from source ABIs.
+
+
+JavaPoet 1.10.0 *(2018-01-27)*
+-----------------------------
+
+ * **JavaPoet now requires Java 8 or newer.**
+ * New: `$Z` as an optional newline (zero-width space) if a line may exceed 100 chars.
+ * New: `CodeBlock.join()` and `CodeBlock.joining()` let you join codeblocks by delimiters.
+ * New: Add `CodeBlock.Builder.isEmpty()`.
+ * New: `addStatement(CodeBlock)` overloads for `CodeBlock` and `MethodSpec`.
+ * Fix: Include annotations when emitting type variables.
+ * Fix: Use the right imports for annotated type parameters.
+ * Fix: Don't incorrectly escape classnames that start with `$`.
+
+
+JavaPoet 1.9.0 *(2017-05-13)*
+-----------------------------
+
+ * Fix: Don't emit incorrect code when the declared type's signature references another type with
+ the same simple name.
+ * Fix: Support anonymous inner classes in `ClassName.get()`.
+ * New: `MethodSpec.Builder.addNamedCode()` and `TypeSpec.anonymousClassBuilder(CodeBlock)`.
+
+
+JavaPoet 1.8.0 *(2016-11-09)*
+-----------------------------
+
+ * New: Basic support for line wrapping. Use `$W` to insert a Wrappable Whitespace character. It'll
+ emit either a single space or a newline with appropriate indentation.
+ * New: Named arguments in `CodeBlock`. These are intended to make larger code snippets easier to
+ read:
+
+ ```
+ Map<String, Object> map = new LinkedHashMap<>();
+ map.put("count", 3);
+ map.put("greeting", "Hello, ");
+ map.put("system", System.class);
+
+ String template = ""
+ + "for (int i = 0; i < $count:L; i++) {\n"
+ + " $system:T.out.println($greeting:S + list.get(i));\n"
+ + "}\n";
+
+ CodeBlock.Builder builder = CodeBlock.builder();
+ builder.addNamed(template, map);
+ ```
+
+ * New: `addJavadoc(CodeBlock)` overloads for TypeSpec, MethodSpec, and FieldSpec.
+ * New: `MethodSpec.addComment()` makes it easy to add a `// single-line comment.`
+ * New: `ClassName.getReflectionName()` returns a string like `java.util.Map$Entry`.
+ * Fix: Always write UTF-8. Previously JavaPoet would use the system default charset which was
+ potentially inconsistent across environments.
+ * Fix: Permit (constant) fields to be defined in annotation types.
+
+
+JavaPoet 1.7.0 *(2016-04-26)*
+-----------------------------
+
+ * New: Support parameterized types that enclose other types, like `Outer<String>.Inner`.
+ * New: `TypeName.isBoxedPrimitive()`.
+
+
+JavaPoet 1.6.1 *(2016-03-21)*
+-----------------------------
+
+ * Fix: Double quotes and backslashes in string literals were not properly quoted in 1.6.0. This is
+ now fixed.
+
+
+JavaPoet 1.6.0 *(2016-03-19)*
+-----------------------------
+
+ * New: Revive `CodeBlock.of()`, a handy factory method for building code blocks.
+ * New: Add `TypeSpec` factory methods that take a `ClassName`.
+ * New: `TypeName.annotated()` adds an annotation to a type.
+ * New: `TypeVariableName.withBounds()` adds bounds to a type variable.
+ * New: `TypeSpec.Builder.addInitializerBlock()` adds an instance initializer.
+ * Fix: Make `TypeSpec.Kind` enum public. This can be used to check if a `TypeSpec` is a class,
+ interface, enum, or annotation.
+ * Fix: Don’t break import resolution on annotated types.
+ * Fix: Forbid unexpected modifiers like `private` on annotation members.
+ * Fix: Deduplicate exceptions in `MethodSpec.Builder`.
+ * Fix: Treat `ErrorType` like a regular `DeclaredType` in `TypeName.get()`. This should make it
+ easier to write annotation processors.
+
+
+JavaPoet 1.5.1 *(2016-01-10)*
+-----------------------------
+
+ * Fix: Annotated `TypeName` instances are only equal if their annotations are equal.
+
+JavaPoet 1.5.0 *(2016-01-10)*
+-----------------------------
+
+ * New: `import static`! See `JavaFile.Builder.addStaticImport()` variants.
+ * New: Overload `NameAllocator.newName(String)` for creating a one-off name without a tag.
+ * Fix: AnnotationSpec escapes character literals properly.
+ * Fix: Don't stack overflow when `TypeVariableName` is part of `ParameterizedTypeName`.
+ * Fix: Reporting not used indexed arguments in like `add("$1S", "a", "b")`.
+ * Fix: Prevent import of types located in the default package, i.e. have no package name.
+
+
+JavaPoet 1.4.0 *(2015-11-13)*
+-----------------------------
+
+ * New: `AnnotationSpec.get(Annotation)`.
+ * New: Type annotations! `TypeName.annotated()` can attach annotations like `@Nullable` directly to
+ types. This works for both top-level types and type parameters as in `List<@Nullable String>`.
+ * New: `equals()` and `hashCode()` on `AnnotationSpec`, `CodeBlock`, `FieldSpec`, `JavaFile`,
+ `MethodSpec`, `ParameterSpec`, `TypeName`, and `TypeSpec`.
+ * New: `NameAllocator.clone()` to refine a NameAllocator for use in an inner scope code block.
+ * Fix: Don't stack overflow when `TypeVariableName` gets a self-referential type.
+ * Fix: Better handling of name collisions on imports. Previously JavaPoet did the wrong thing when
+ a referenced type and a nested types had the same name.
+
+
+JavaPoet 1.3.0 *(2015-09-20)*
+-----------------------------
+
+ * New: `NameAllocator` API makes it easy to declare non-conflicting names.
+ * New: Support annotations on enum values.
+ * Fix: Avoid infinite recursion in `TypeName.get(TypeMirror)`.
+ * Fix: Use qualified name for conflicting simple names in the same file.
+ * Fix: Better messages for parameter indexing errors.
+
+
+JavaPoet 1.2.0 *(2015-07-04)*
+-----------------------------
+
+ * New: Arguments may have positional indexes like `$1T` and `$2N`. Indexes can be used to refer to
+ the same argument multiple times in a single format string.
+ * New: Permit Javadoc on enum constants.
+ * New: Class initializer blocks with `addStaticBlock()`.
+ * Fix: `MethodSpec.overriding()` retains annotations.
+
+
+JavaPoet 1.1.0 *(2015-05-25)*
+-----------------------------
+
+ * New: Eager validation of argument types like `$T` and `$N`.
+ * New: `MethodSpec.varargs(boolean)` to generate varags methods.
+ * New: `AnnotationSpec.get()` and `MethodSpec.overriding()` to create annotations and methods from
+ the `javax.lang.model` API.
+ * New: `JavaFile.toJavaFileObject()`.
+ * New: Java 8 `DEFAULT` modifier.
+ * New: `toBuilder()` methods to build upon objects already constructed.
+ * New: Generate `@interface` annotation types.
+ * New: `TypeName.box()` and `TypeName.unbox()` convenience APIs.
+ * Fix: `nextControlFlow()` accepts arguments.
+ * Fix: Reject duplicate calls to set the superclass.
+ * Fix: `WildcardTypeName.get(WildcardType)` no longer throws a `NullPointerException`.
+ * Fix: Don't allow double field initialization.
+
+JavaPoet 1.0.0 *(2015-01-28)*
+-----------------------------
+
+ * This update is a complete rewrite. The project name is now `javapoet`. We renamed the it so you
+ can simultaneously use the old JavaWriter API and our new builder-based APIs in one project.
+ * Immutable value objects and builders. Instead of streaming the `.java` file from top to bottom,
+ you now define members in whatever way is convenient.
+ * We now use our own models for type names.
+ * Imports are now added automatically.
+
+
+JavaWriter 2.5.1 *(2014-12-03)*
+-------------------------------
+
+ * New: `StringLiteral` type which encapsulates the behavior of `stringLiteral`.
+ * Fix: Use canonical name when emitting a class import.
+ * Fix: Apply type compression to varargs and array types.
+ * Fix: Restore binary compatibility with pre-2.5 versions.
+
+
+JavaWriter 2.5.0 *(2014-04-18)*
+-------------------------------
+
+ * New: Methods in interfaces will always have no body declaration.
+ * New: Control flow begin declaration now supports String format arguments.
+ * Fix: Truncate any generic type when emitting constructors.
+ * Fix: Do not emit trailing whitespace after '=' at end-of-line.
+
+
+JavaWriter 2.4.0 *(2014-01-10)*
+-------------------------------
+
+ * New: Properly indent hanging lines in field initializers.
+ * New: `emitEnumValue` variant which exposes a boolean of whether the current value is the last.
+
+
+JavaWriter 2.3.1 *(2013-12-16)*
+-------------------------------
+
+ * Fix: Properly handle subpackages of `java.lang` in `compressType`.
+
+
+JavaWriter 2.3.0 *(2013-11-24)*
+-------------------------------
+
+ * New: Configurable indent level via `setIndent`.
+ * New: `beginConstructor` method is a semantically clearer alternative for constructors.
+ * New: `emitEnumValues` method emits a list of values followed by semicolon.
+ * `emitImports` now supports `Class` arguments directly.
+ * Previously-deprecated, `int`-based modifier methods have been removed.
+
+
+JavaWriter 2.2.1 *(2013-10-23)*
+-------------------------------
+
+ * Fix: Do not emit trailing whitespace for empty Javadoc lines.
+
+
+JavaWriter 2.2.0 *(2013-09-25)*
+-------------------------------
+
+ * `setCompressingTypes` controls whether types are emitted as fully-qualified or not.
+
+
+JavaWriter 2.1.2 *(2013-08-23)*
+-------------------------------
+
+ * Attempt to keep annotations on a single line.
+
+
+JavaWriter 2.1.1 *(2013-07-23)*
+-------------------------------
+
+ * Fix: `stringLiteral` now correctly handles escapes and control characters.
+
+
+JavaWriter 2.1.0 *(2013-07-15)*
+-------------------------------
+
+ * New: All methods now take a `Set` of `Modifier`s rather than an `int`. The `int` methods are
+ now deprecated for removal in JavaPoet 1.0.
+ * Annotations with a single "value" attribute will now omit the key.
+
+
+JavaWriter 2.0.1 *(2013-06-17)*
+-------------------------------
+
+ * Correct casing of `emitSingleLineComment`.
+
+
+JavaWriter 2.0.0 *(2013-06-06)*
+-------------------------------
+
+ * Package name is now `com.squareup.javawriter`.
+ * Support declaring `throws` clause on methods.
+
+
+JavaWriter 1.0.5 *(2013-05-08)*
+-------------------------------
+
+ * Fix: Fully qualify types whose simple name matches an import.
+
+
+JavaWriter 1.0.4 *(2013-03-15)*
+-------------------------------
+
+ * Fix: Static import emit now properly supports method imports.
+
+
+JavaWriter 1.0.3 *(2013-02-21)*
+-------------------------------
+
+ * Add support for emitting static imports.
+
+
+JavaWriter 1.0.2 *(2013-02-11)*
+-------------------------------
+
+ * Add `type` API for helping build generic types.
+ * Minor performance improvements.
+
+
+JavaWriter 1.0.1 *(2013-02-03)*
+-------------------------------
+
+ * Expose `compressType` API.
+
+
+JavaWriter 1.0.0 *(2013-02-01)*
+-------------------------------
+
+Initial release.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..8131805
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,17 @@
+Contributing
+============
+
+If you would like to contribute code you can do so through GitHub by forking
+the repository and sending a pull request.
+
+When submitting code, please make every effort to follow existing conventions
+and style in order to keep the code as readable as possible. Please also make
+sure your code compiles by running `mvn clean verify`. Checkstyle failures
+during compilation indicate errors in your style and can be viewed in the
+`checkstyle-result.xml` file.
+
+Before your code can be accepted into the project you must also sign the
+[Individual Contributor License Agreement (CLA)][1].
+
+
+ [1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1dffbad
--- /dev/null
+++ b/README.md
@@ -0,0 +1,889 @@
+JavaPoet
+========
+
+`JavaPoet` is a Java API for generating `.java` source files.
+
+Source file generation can be useful when doing things such as annotation processing or interacting
+with metadata files (e.g., database schemas, protocol formats). By generating code, you eliminate
+the need to write boilerplate while also keeping a single source of truth for the metadata.
+
+
+### Example
+
+Here's a (boring) `HelloWorld` class:
+
+```java
+package com.example.helloworld;
+
+public final class HelloWorld {
+ public static void main(String[] args) {
+ System.out.println("Hello, JavaPoet!");
+ }
+}
+```
+
+And this is the (exciting) code to generate it with JavaPoet:
+
+```java
+MethodSpec main = MethodSpec.methodBuilder("main")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .returns(void.class)
+ .addParameter(String[].class, "args")
+ .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
+ .build();
+
+TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .addMethod(main)
+ .build();
+
+JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
+ .build();
+
+javaFile.writeTo(System.out);
+```
+
+To declare the main method, we've created a `MethodSpec` "main" configured with modifiers, return
+type, parameters and code statements. We add the main method to a `HelloWorld` class, and then add
+that to a `HelloWorld.java` file.
+
+In this case we write the file to `System.out`, but we could also get it as a string
+(`JavaFile.toString()`) or write it to the file system (`JavaFile.writeTo()`).
+
+The [Javadoc][javadoc] catalogs the complete JavaPoet API, which we explore below.
+
+### Code & Control Flow
+
+Most of JavaPoet's API uses plain old immutable Java objects. There's also builders, method chaining
+and varargs to make the API friendly. JavaPoet offers models for classes & interfaces (`TypeSpec`),
+fields (`FieldSpec`), methods & constructors (`MethodSpec`), parameters (`ParameterSpec`) and
+annotations (`AnnotationSpec`).
+
+But the _body_ of methods and constructors is not modeled. There's no expression class, no
+statement class or syntax tree nodes. Instead, JavaPoet uses strings for code blocks:
+
+```java
+MethodSpec main = MethodSpec.methodBuilder("main")
+ .addCode(""
+ + "int total = 0;\n"
+ + "for (int i = 0; i < 10; i++) {\n"
+ + " total += i;\n"
+ + "}\n")
+ .build();
+```
+
+Which generates this:
+
+```java
+void main() {
+ int total = 0;
+ for (int i = 0; i < 10; i++) {
+ total += i;
+ }
+}
+```
+
+The manual semicolons, line wrapping, and indentation are tedious and so JavaPoet offers APIs to
+make it easier. There's `addStatement()` which takes care of semicolons and newline, and
+`beginControlFlow()` + `endControlFlow()` which are used together for braces, newlines, and
+indentation:
+
+```java
+MethodSpec main = MethodSpec.methodBuilder("main")
+ .addStatement("int total = 0")
+ .beginControlFlow("for (int i = 0; i < 10; i++)")
+ .addStatement("total += i")
+ .endControlFlow()
+ .build();
+```
+
+This example is lame because the generated code is constant! Suppose instead of just adding 0 to 10,
+we want to make the operation and range configurable. Here's a method that generates a method:
+
+```java
+private MethodSpec computeRange(String name, int from, int to, String op) {
+ return MethodSpec.methodBuilder(name)
+ .returns(int.class)
+ .addStatement("int result = 1")
+ .beginControlFlow("for (int i = " + from + "; i < " + to + "; i++)")
+ .addStatement("result = result " + op + " i")
+ .endControlFlow()
+ .addStatement("return result")
+ .build();
+}
+```
+
+And here's what we get when we call `computeRange("multiply10to20", 10, 20, "*")`:
+
+```java
+int multiply10to20() {
+ int result = 1;
+ for (int i = 10; i < 20; i++) {
+ result = result * i;
+ }
+ return result;
+}
+```
+
+Methods generating methods! And since JavaPoet generates source instead of bytecode, you can
+read through it to make sure it's right.
+
+
+### $L for Literals
+
+The string-concatenation in calls to `beginControlFlow()` and `addStatement` is distracting. Too
+many operators. To address this, JavaPoet offers a syntax inspired-by but incompatible-with
+[`String.format()`][formatter]. It accepts **`$L`** to emit a **literal** value in the output. This
+works just like `Formatter`'s `%s`:
+
+```java
+private MethodSpec computeRange(String name, int from, int to, String op) {
+ return MethodSpec.methodBuilder(name)
+ .returns(int.class)
+ .addStatement("int result = 0")
+ .beginControlFlow("for (int i = $L; i < $L; i++)", from, to)
+ .addStatement("result = result $L i", op)
+ .endControlFlow()
+ .addStatement("return result")
+ .build();
+}
+```
+
+Literals are emitted directly to the output code with no escaping. Arguments for literals may be
+strings, primitives, and a few JavaPoet types described below.
+
+### $S for Strings
+
+When emitting code that includes string literals, we can use **`$S`** to emit a **string**, complete
+with wrapping quotation marks and escaping. Here's a program that emits 3 methods, each of which
+returns its own name:
+
+```java
+public static void main(String[] args) throws Exception {
+ TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .addMethod(whatsMyName("slimShady"))
+ .addMethod(whatsMyName("eminem"))
+ .addMethod(whatsMyName("marshallMathers"))
+ .build();
+
+ JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
+ .build();
+
+ javaFile.writeTo(System.out);
+}
+
+private static MethodSpec whatsMyName(String name) {
+ return MethodSpec.methodBuilder(name)
+ .returns(String.class)
+ .addStatement("return $S", name)
+ .build();
+}
+```
+
+In this case, using `$S` gives us quotation marks:
+
+```java
+public final class HelloWorld {
+ String slimShady() {
+ return "slimShady";
+ }
+
+ String eminem() {
+ return "eminem";
+ }
+
+ String marshallMathers() {
+ return "marshallMathers";
+ }
+}
+```
+
+### $T for Types
+
+We Java programmers love our types: they make our code easier to understand. And JavaPoet is on
+board. It has rich built-in support for types, including automatic generation of `import`
+statements. Just use **`$T`** to reference **types**:
+
+```java
+MethodSpec today = MethodSpec.methodBuilder("today")
+ .returns(Date.class)
+ .addStatement("return new $T()", Date.class)
+ .build();
+
+TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .addMethod(today)
+ .build();
+
+JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
+ .build();
+
+javaFile.writeTo(System.out);
+```
+
+That generates the following `.java` file, complete with the necessary `import`:
+
+```java
+package com.example.helloworld;
+
+import java.util.Date;
+
+public final class HelloWorld {
+ Date today() {
+ return new Date();
+ }
+}
+```
+
+We passed `Date.class` to reference a class that just-so-happens to be available when we're
+generating code. This doesn't need to be the case. Here's a similar example, but this one
+references a class that doesn't exist (yet):
+
+```java
+ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
+
+MethodSpec today = MethodSpec.methodBuilder("tomorrow")
+ .returns(hoverboard)
+ .addStatement("return new $T()", hoverboard)
+ .build();
+```
+
+And that not-yet-existent class is imported as well:
+
+```java
+package com.example.helloworld;
+
+import com.mattel.Hoverboard;
+
+public final class HelloWorld {
+ Hoverboard tomorrow() {
+ return new Hoverboard();
+ }
+}
+```
+
+The `ClassName` type is very important, and you'll need it frequently when you're using JavaPoet.
+It can identify any _declared_ class. Declared types are just the beginning of Java's rich type
+system: we also have arrays, parameterized types, wildcard types, and type variables. JavaPoet has
+classes for building each of these:
+
+```java
+ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
+ClassName list = ClassName.get("java.util", "List");
+ClassName arrayList = ClassName.get("java.util", "ArrayList");
+TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);
+
+MethodSpec beyond = MethodSpec.methodBuilder("beyond")
+ .returns(listOfHoverboards)
+ .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
+ .addStatement("result.add(new $T())", hoverboard)
+ .addStatement("result.add(new $T())", hoverboard)
+ .addStatement("result.add(new $T())", hoverboard)
+ .addStatement("return result")
+ .build();
+```
+
+JavaPoet will decompose each type and import its components where possible.
+
+```java
+package com.example.helloworld;
+
+import com.mattel.Hoverboard;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class HelloWorld {
+ List<Hoverboard> beyond() {
+ List<Hoverboard> result = new ArrayList<>();
+ result.add(new Hoverboard());
+ result.add(new Hoverboard());
+ result.add(new Hoverboard());
+ return result;
+ }
+}
+```
+
+#### Import static
+
+JavaPoet supports `import static`. It does it via explicitly collecting type member names. Let's
+enhance the previous example with some static sugar:
+
+```java
+...
+ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards");
+
+MethodSpec beyond = MethodSpec.methodBuilder("beyond")
+ .returns(listOfHoverboards)
+ .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
+ .addStatement("result.add($T.createNimbus(2000))", hoverboard)
+ .addStatement("result.add($T.createNimbus(\"2001\"))", hoverboard)
+ .addStatement("result.add($T.createNimbus($T.THUNDERBOLT))", hoverboard, namedBoards)
+ .addStatement("$T.sort(result)", Collections.class)
+ .addStatement("return result.isEmpty() ? $T.emptyList() : result", Collections.class)
+ .build();
+
+TypeSpec hello = TypeSpec.classBuilder("HelloWorld")
+ .addMethod(beyond)
+ .build();
+
+JavaFile.builder("com.example.helloworld", hello)
+ .addStaticImport(hoverboard, "createNimbus")
+ .addStaticImport(namedBoards, "*")
+ .addStaticImport(Collections.class, "*")
+ .build();
+```
+
+JavaPoet will first add your `import static` block to the file as configured, match and mangle
+all calls accordingly and also import all other types as needed.
+
+```java
+package com.example.helloworld;
+
+import static com.mattel.Hoverboard.Boards.*;
+import static com.mattel.Hoverboard.createNimbus;
+import static java.util.Collections.*;
+
+import com.mattel.Hoverboard;
+import java.util.ArrayList;
+import java.util.List;
+
+class HelloWorld {
+ List<Hoverboard> beyond() {
+ List<Hoverboard> result = new ArrayList<>();
+ result.add(createNimbus(2000));
+ result.add(createNimbus("2001"));
+ result.add(createNimbus(THUNDERBOLT));
+ sort(result);
+ return result.isEmpty() ? emptyList() : result;
+ }
+}
+```
+
+### $N for Names
+
+Generated code is often self-referential. Use **`$N`** to refer to another generated declaration by
+its name. Here's a method that calls another:
+
+```java
+public String byteToHex(int b) {
+ char[] result = new char[2];
+ result[0] = hexDigit((b >>> 4) & 0xf);
+ result[1] = hexDigit(b & 0xf);
+ return new String(result);
+}
+
+public char hexDigit(int i) {
+ return (char) (i < 10 ? i + '0' : i - 10 + 'a');
+}
+```
+
+When generating the code above, we pass the `hexDigit()` method as an argument to the `byteToHex()`
+method using `$N`:
+
+```java
+MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
+ .addParameter(int.class, "i")
+ .returns(char.class)
+ .addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
+ .build();
+
+MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
+ .addParameter(int.class, "b")
+ .returns(String.class)
+ .addStatement("char[] result = new char[2]")
+ .addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
+ .addStatement("result[1] = $N(b & 0xf)", hexDigit)
+ .addStatement("return new String(result)")
+ .build();
+```
+
+### Code block format strings
+
+Code blocks may specify the values for their placeholders in a few ways. Only one style may be used
+for each operation on a code block.
+
+#### Relative Arguments
+
+Pass an argument value for each placeholder in the format string to `CodeBlock.add()`. In each
+example, we generate code to say "I ate 3 tacos"
+
+```java
+CodeBlock.builder().add("I ate $L $L", 3, "tacos")
+```
+
+#### Positional Arguments
+
+Place an integer index (1-based) before the placeholder in the format string to specify which
+ argument to use.
+
+```java
+CodeBlock.builder().add("I ate $2L $1L", "tacos", 3)
+```
+
+#### Named Arguments
+
+Use the syntax `$argumentName:X` where `X` is the format character and call `CodeBlock.addNamed()`
+with a map containing all argument keys in the format string. Argument names use characters in
+`a-z`, `A-Z`, `0-9`, and `_`, and must start with a lowercase character.
+
+```java
+Map<String, Object> map = new LinkedHashMap<>();
+map.put("food", "tacos");
+map.put("count", 3);
+CodeBlock.builder().addNamed("I ate $count:L $food:L", map)
+```
+
+### Methods
+
+All of the above methods have a code body. Use `Modifiers.ABSTRACT` to get a method without any
+body. This is only legal if the enclosing class is either abstract or an interface.
+
+```java
+MethodSpec flux = MethodSpec.methodBuilder("flux")
+ .addModifiers(Modifier.ABSTRACT, Modifier.PROTECTED)
+ .build();
+
+TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .addMethod(flux)
+ .build();
+```
+
+Which generates this:
+
+```java
+public abstract class HelloWorld {
+ protected abstract void flux();
+}
+```
+
+The other modifiers work where permitted. Note that when specifying modifiers, JavaPoet uses
+[`javax.lang.model.element.Modifier`][modifier], a class that is not available on Android. This
+limitation applies to code-generating-code only; the output code runs everywhere: JVMs, Android,
+and GWT.
+
+Methods also have parameters, exceptions, varargs, Javadoc, annotations, type variables, and a
+return type. All of these are configured with `MethodSpec.Builder`.
+
+### Constructors
+
+`MethodSpec` is a slight misnomer; it can also be used for constructors:
+
+```java
+MethodSpec flux = MethodSpec.constructorBuilder()
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(String.class, "greeting")
+ .addStatement("this.$N = $N", "greeting", "greeting")
+ .build();
+
+TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .addModifiers(Modifier.PUBLIC)
+ .addField(String.class, "greeting", Modifier.PRIVATE, Modifier.FINAL)
+ .addMethod(flux)
+ .build();
+```
+
+Which generates this:
+
+```java
+public class HelloWorld {
+ private final String greeting;
+
+ public HelloWorld(String greeting) {
+ this.greeting = greeting;
+ }
+}
+```
+
+For the most part, constructors work just like methods. When emitting code, JavaPoet will place
+constructors before methods in the output file.
+
+### Parameters
+
+Declare parameters on methods and constructors with either `ParameterSpec.builder()` or
+`MethodSpec`'s convenient `addParameter()` API:
+
+```java
+ParameterSpec android = ParameterSpec.builder(String.class, "android")
+ .addModifiers(Modifier.FINAL)
+ .build();
+
+MethodSpec welcomeOverlords = MethodSpec.methodBuilder("welcomeOverlords")
+ .addParameter(android)
+ .addParameter(String.class, "robot", Modifier.FINAL)
+ .build();
+```
+
+Though the code above to generate `android` and `robot` parameters is different, the output is the
+same:
+
+```java
+void welcomeOverlords(final String android, final String robot) {
+}
+```
+
+The extended `Builder` form is necessary when the parameter has annotations (such as `@Nullable`).
+
+### Fields
+
+Like parameters, fields can be created either with builders or by using convenient helper methods:
+
+```java
+FieldSpec android = FieldSpec.builder(String.class, "android")
+ .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
+ .build();
+
+TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .addModifiers(Modifier.PUBLIC)
+ .addField(android)
+ .addField(String.class, "robot", Modifier.PRIVATE, Modifier.FINAL)
+ .build();
+```
+
+Which generates:
+
+```java
+public class HelloWorld {
+ private final String android;
+
+ private final String robot;
+}
+```
+
+The extended `Builder` form is necessary when a field has Javadoc, annotations, or a field
+initializer. Field initializers use the same [`String.format()`][formatter]-like syntax as the code
+blocks above:
+
+```java
+FieldSpec android = FieldSpec.builder(String.class, "android")
+ .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
+ .initializer("$S + $L", "Lollipop v.", 5.0d)
+ .build();
+```
+
+Which generates:
+
+```java
+private final String android = "Lollipop v." + 5.0;
+```
+
+### Interfaces
+
+JavaPoet has no trouble with interfaces. Note that interface methods must always be `PUBLIC
+ABSTRACT` and interface fields must always be `PUBLIC STATIC FINAL`. These modifiers are necessary
+when defining the interface:
+
+```java
+TypeSpec helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
+ .addModifiers(Modifier.PUBLIC)
+ .addField(FieldSpec.builder(String.class, "ONLY_THING_THAT_IS_CONSTANT")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
+ .initializer("$S", "change")
+ .build())
+ .addMethod(MethodSpec.methodBuilder("beep")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .build())
+ .build();
+```
+
+But these modifiers are omitted when the code is generated. These are the defaults so we don't need
+to include them for `javac`'s benefit!
+
+```java
+public interface HelloWorld {
+ String ONLY_THING_THAT_IS_CONSTANT = "change";
+
+ void beep();
+}
+```
+
+### Enums
+
+Use `enumBuilder` to create the enum type, and `addEnumConstant()` for each value:
+
+```java
+TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
+ .addModifiers(Modifier.PUBLIC)
+ .addEnumConstant("ROCK")
+ .addEnumConstant("SCISSORS")
+ .addEnumConstant("PAPER")
+ .build();
+```
+
+To generate this:
+
+```java
+public enum Roshambo {
+ ROCK,
+
+ SCISSORS,
+
+ PAPER
+}
+```
+
+Fancy enums are supported, where the enum values override methods or call a superclass constructor.
+Here's a comprehensive example:
+
+```java
+TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
+ .addModifiers(Modifier.PUBLIC)
+ .addEnumConstant("ROCK", TypeSpec.anonymousClassBuilder("$S", "fist")
+ .addMethod(MethodSpec.methodBuilder("toString")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .addStatement("return $S", "avalanche!")
+ .returns(String.class)
+ .build())
+ .build())
+ .addEnumConstant("SCISSORS", TypeSpec.anonymousClassBuilder("$S", "peace")
+ .build())
+ .addEnumConstant("PAPER", TypeSpec.anonymousClassBuilder("$S", "flat")
+ .build())
+ .addField(String.class, "handsign", Modifier.PRIVATE, Modifier.FINAL)
+ .addMethod(MethodSpec.constructorBuilder()
+ .addParameter(String.class, "handsign")
+ .addStatement("this.$N = $N", "handsign", "handsign")
+ .build())
+ .build();
+```
+
+Which generates this:
+
+```java
+public enum Roshambo {
+ ROCK("fist") {
+ @Override
+ public String toString() {
+ return "avalanche!";
+ }
+ },
+
+ SCISSORS("peace"),
+
+ PAPER("flat");
+
+ private final String handsign;
+
+ Roshambo(String handsign) {
+ this.handsign = handsign;
+ }
+}
+```
+
+### Anonymous Inner Classes
+
+In the enum code, we used `Types.anonymousInnerClass()`. Anonymous inner classes can also be used in
+code blocks. They are values that can be referenced with `$L`:
+
+```java
+TypeSpec comparator = TypeSpec.anonymousClassBuilder("")
+ .addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class))
+ .addMethod(MethodSpec.methodBuilder("compare")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(String.class, "a")
+ .addParameter(String.class, "b")
+ .returns(int.class)
+ .addStatement("return $N.length() - $N.length()", "a", "b")
+ .build())
+ .build();
+
+TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .addMethod(MethodSpec.methodBuilder("sortByLength")
+ .addParameter(ParameterizedTypeName.get(List.class, String.class), "strings")
+ .addStatement("$T.sort($N, $L)", Collections.class, "strings", comparator)
+ .build())
+ .build();
+```
+
+This generates a method that contains a class that contains a method:
+
+```java
+void sortByLength(List<String> strings) {
+ Collections.sort(strings, new Comparator<String>() {
+ @Override
+ public int compare(String a, String b) {
+ return a.length() - b.length();
+ }
+ });
+}
+```
+
+One particularly tricky part of defining anonymous inner classes is the arguments to the superclass
+constructor. In the above code we're passing the empty string for no arguments:
+`TypeSpec.anonymousClassBuilder("")`. To pass different parameters use JavaPoet's code block
+syntax with commas to separate arguments.
+
+
+### Annotations
+
+Simple annotations are easy:
+
+```java
+MethodSpec toString = MethodSpec.methodBuilder("toString")
+ .addAnnotation(Override.class)
+ .returns(String.class)
+ .addModifiers(Modifier.PUBLIC)
+ .addStatement("return $S", "Hoverboard")
+ .build();
+```
+
+Which generates this method with an `@Override` annotation:
+
+```java
+ @Override
+ public String toString() {
+ return "Hoverboard";
+ }
+```
+
+Use `AnnotationSpec.builder()` to set properties on annotations:
+
+```java
+MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .addAnnotation(AnnotationSpec.builder(Headers.class)
+ .addMember("accept", "$S", "application/json; charset=utf-8")
+ .addMember("userAgent", "$S", "Square Cash")
+ .build())
+ .addParameter(LogRecord.class, "logRecord")
+ .returns(LogReceipt.class)
+ .build();
+```
+
+Which generates this annotation with `accept` and `userAgent` properties:
+
+```java
+@Headers(
+ accept = "application/json; charset=utf-8",
+ userAgent = "Square Cash"
+)
+LogReceipt recordEvent(LogRecord logRecord);
+```
+
+When you get fancy, annotation values can be annotations themselves. Use `$L` for embedded
+annotations:
+
+```java
+MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .addAnnotation(AnnotationSpec.builder(HeaderList.class)
+ .addMember("value", "$L", AnnotationSpec.builder(Header.class)
+ .addMember("name", "$S", "Accept")
+ .addMember("value", "$S", "application/json; charset=utf-8")
+ .build())
+ .addMember("value", "$L", AnnotationSpec.builder(Header.class)
+ .addMember("name", "$S", "User-Agent")
+ .addMember("value", "$S", "Square Cash")
+ .build())
+ .build())
+ .addParameter(LogRecord.class, "logRecord")
+ .returns(LogReceipt.class)
+ .build();
+```
+
+Which generates this:
+
+```java
+@HeaderList({
+ @Header(name = "Accept", value = "application/json; charset=utf-8"),
+ @Header(name = "User-Agent", value = "Square Cash")
+})
+LogReceipt recordEvent(LogRecord logRecord);
+```
+
+Note that you can call `addMember()` multiple times with the same property name to populate a list
+of values for that property.
+
+### Javadoc
+
+Fields, methods and types can be documented with Javadoc:
+
+```java
+MethodSpec dismiss = MethodSpec.methodBuilder("dismiss")
+ .addJavadoc("Hides {@code message} from the caller's history. Other\n"
+ + "participants in the conversation will continue to see the\n"
+ + "message in their own history unless they also delete it.\n")
+ .addJavadoc("\n")
+ .addJavadoc("<p>Use {@link #delete($T)} to delete the entire\n"
+ + "conversation for all participants.\n", Conversation.class)
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .addParameter(Message.class, "message")
+ .build();
+```
+
+Which generates this:
+
+```java
+ /**
+ * Hides {@code message} from the caller's history. Other
+ * participants in the conversation will continue to see the
+ * message in their own history unless they also delete it.
+ *
+ * <p>Use {@link #delete(Conversation)} to delete the entire
+ * conversation for all participants.
+ */
+ void dismiss(Message message);
+```
+
+Use `$T` when referencing types in Javadoc to get automatic imports.
+
+Download
+--------
+
+Download [the latest .jar][dl] or depend via Maven:
+```xml
+<dependency>
+ <groupId>com.squareup</groupId>
+ <artifactId>javapoet</artifactId>
+ <version>1.11.1</version>
+</dependency>
+```
+or Gradle:
+```groovy
+compile 'com.squareup:javapoet:1.11.1'
+```
+
+Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap].
+
+
+
+License
+-------
+
+ Copyright 2015 Square, Inc.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+
+JavaWriter
+==========
+
+JavaPoet is the successor to [JavaWriter][javawriter]. New projects should prefer JavaPoet because
+it has a stronger code model: it understands types and can manage imports automatically. JavaPoet is
+also better suited to composition: rather than streaming the contents of a `.java` file
+top-to-bottom in a single pass, a file can be assembled as a tree of declarations.
+
+JavaWriter continues to be available in [GitHub][javawriter] and [Maven Central][javawriter_maven].
+
+
+ [dl]: https://search.maven.org/remote_content?g=com.squareup&a=javapoet&v=LATEST
+ [snap]: https://oss.sonatype.org/content/repositories/snapshots/com/squareup/javapoet/
+ [javadoc]: https://square.github.io/javapoet/1.x/javapoet/
+ [javawriter]: https://github.com/square/javapoet/tree/javawriter_2
+ [javawriter_maven]: https://search.maven.org/#artifactdetails%7Ccom.squareup%7Cjavawriter%7C2.5.1%7Cjar
+ [formatter]: https://developer.android.com/reference/java/util/Formatter.html
+ [modifier]: https://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/Modifier.html
diff --git a/checkstyle.xml b/checkstyle.xml
new file mode 100644
index 0000000..bbf690e
--- /dev/null
+++ b/checkstyle.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0"?>
+<!DOCTYPE module PUBLIC
+ "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
+ "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
+
+<module name="Checker">
+ <module name="SuppressWarningsFilter"/>
+ <module name="NewlineAtEndOfFile"/>
+ <module name="FileLength"/>
+ <module name="FileTabCharacter"/>
+
+ <!-- Trailing spaces -->
+ <module name="RegexpSingleline">
+ <property name="format" value="\s+$"/>
+ <property name="message" value="Line has trailing spaces."/>
+ </module>
+
+ <!-- Space after 'for' and 'if' -->
+ <module name="RegexpSingleline">
+ <property name="format" value="^\s*(for|if)\b[^ ]"/>
+ <property name="message" value="Space needed before opening parenthesis."/>
+ </module>
+
+ <!-- For each spacing -->
+ <module name="RegexpSingleline">
+ <property name="format" value="^\s*for \(.*?([^ ]:|:[^ ])"/>
+ <property name="message" value="Space needed around ':' character."/>
+ </module>
+
+ <module name="TreeWalker">
+ <property name="cacheFile" value="${checkstyle.cache.file}"/>
+
+ <!-- Checks for Javadoc comments. -->
+ <!-- See http://checkstyle.sf.net/config_javadoc.html -->
+ <!--module name="JavadocMethod"/-->
+ <!--module name="JavadocType"/-->
+ <!--module name="JavadocVariable"/-->
+ <module name="JavadocStyle"/>
+
+
+ <!-- Checks for Naming Conventions. -->
+ <!-- See http://checkstyle.sf.net/config_naming.html -->
+ <module name="ConstantName"/>
+ <module name="LocalFinalVariableName"/>
+ <module name="LocalVariableName"/>
+ <module name="MemberName"/>
+ <module name="MethodName"/>
+ <module name="PackageName"/>
+ <module name="ParameterName"/>
+ <module name="StaticVariableName"/>
+ <module name="TypeName"/>
+
+
+ <!-- Checks for imports -->
+ <!-- See http://checkstyle.sf.net/config_import.html -->
+ <module name="AvoidStarImport"/>
+ <module name="IllegalImport"/>
+ <!-- defaults to sun.* packages -->
+ <module name="RedundantImport"/>
+ <module name="UnusedImports">
+ <property name="processJavadoc" value="true"/>
+ </module>
+
+
+ <!-- Checks for Size Violations. -->
+ <!-- See http://checkstyle.sf.net/config_sizes.html -->
+ <module name="LineLength">
+ <property name="max" value="100"/>
+ </module>
+ <module name="MethodLength">
+ <property name="max" value="160"/>
+ </module>
+ <module name="ParameterNumber"/>
+
+
+ <!-- Checks for whitespace -->
+ <!-- See http://checkstyle.sf.net/config_whitespace.html -->
+ <module name="GenericWhitespace"/>
+ <!--<module name="EmptyForIteratorPad"/>-->
+ <module name="MethodParamPad"/>
+ <module name="NoWhitespaceAfter"/>
+ <module name="NoWhitespaceBefore"/>
+ <module name="OperatorWrap"/>
+ <module name="ParenPad"/>
+ <module name="TypecastParenPad"/>
+ <module name="WhitespaceAfter"/>
+ <module name="WhitespaceAround">
+ <property name="tokens"
+ value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN,
+ COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAND, LCURLY, LE, LITERAL_CATCH,
+ LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
+ LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
+ MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, SL, SLIST,
+ SL_ASSIGN, SR, SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, TYPE_EXTENSION_AND"/>
+ </module>
+
+
+ <!-- Modifier Checks -->
+ <!-- See http://checkstyle.sf.net/config_modifiers.html -->
+ <module name="ModifierOrder"/>
+ <module name="RedundantModifier"/>
+
+
+ <!-- Checks for blocks. You know, those {}'s -->
+ <!-- See http://checkstyle.sf.net/config_blocks.html -->
+ <module name="AvoidNestedBlocks"/>
+ <!--module name="EmptyBlock"/-->
+ <module name="LeftCurly"/>
+ <!--<module name="NeedBraces"/>-->
+ <module name="RightCurly"/>
+
+
+ <!-- Checks for common coding problems -->
+ <!-- See http://checkstyle.sf.net/config_coding.html -->
+ <!--module name="AvoidInlineConditionals"/-->
+ <module name="CovariantEquals"/>
+ <module name="EmptyStatement"/>
+ <!--<module name="EqualsAvoidNull"/>-->
+ <module name="EqualsHashCode"/>
+ <!--module name="HiddenField"/-->
+ <module name="IllegalInstantiation"/>
+ <module name="InnerAssignment"/>
+ <!--<module name="MagicNumber"/>-->
+ <module name="MissingSwitchDefault"/>
+ <!--module name="RedundantThrows"/-->
+ <module name="SimplifyBooleanExpression"/>
+ <module name="SimplifyBooleanReturn"/>
+
+ <!-- Checks for class design -->
+ <!-- See http://checkstyle.sf.net/config_design.html -->
+ <!--module name="DesignForExtension"/-->
+ <module name="FinalClass"/>
+ <module name="HideUtilityClassConstructor"/>
+ <module name="InterfaceIsType"/>
+ <!--module name="VisibilityModifier"/-->
+
+
+ <!-- Miscellaneous other checks. -->
+ <!-- See http://checkstyle.sf.net/config_misc.html -->
+ <module name="ArrayTypeStyle"/>
+ <!--module name="FinalParameters"/-->
+ <module name="TodoComment"/>
+ <module name="UpperEll"/>
+
+ <!-- Make the @SuppressWarnings annotations available to Checkstyle -->
+ <module name="SuppressWarningsHolder"/>
+ </module>
+</module>
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..a531637
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.sonatype.oss</groupId>
+ <artifactId>oss-parent</artifactId>
+ <version>7</version>
+ </parent>
+
+ <groupId>com.squareup</groupId>
+ <artifactId>javapoet</artifactId>
+ <version>1.11.1</version>
+
+ <name>JavaPoet</name>
+ <description>Use beautiful Java code to generate beautiful Java code.</description>
+ <url>http://github.com/square/javapoet/</url>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+ <java.version>1.8</java.version>
+ <junit.version>4.12</junit.version>
+ <truth.version>0.39</truth.version>
+ <compile-testing.version>0.15</compile-testing.version>
+ </properties>
+
+ <scm>
+ <url>http://github.com/square/javapoet/</url>
+ <connection>scm:git:git://github.com/square/javapoet.git</connection>
+ <developerConnection>scm:git:ssh://git@github.com/square/javapoet.git</developerConnection>
+ <tag>HEAD</tag>
+ </scm>
+
+ <issueManagement>
+ <system>GitHub Issues</system>
+ <url>http://github.com/square/javapoet/issues</url>
+ </issueManagement>
+
+ <licenses>
+ <license>
+ <name>Apache 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ </license>
+ </licenses>
+
+ <organization>
+ <name>Square, Inc.</name>
+ <url>http://squareup.com</url>
+ </organization>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.truth</groupId>
+ <artifactId>truth</artifactId>
+ <version>${truth.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.testing.compile</groupId>
+ <artifactId>compile-testing</artifactId>
+ <version>${compile-testing.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.jimfs</groupId>
+ <artifactId>jimfs</artifactId>
+ <version>1.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>2.13.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jdt.core.compiler</groupId>
+ <artifactId>ecj</artifactId>
+ <version>4.6.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.7.0</version>
+ <configuration>
+ <compilerId>javac-with-errorprone</compilerId>
+ <forceJavacCompilerUse>true</forceJavacCompilerUse>
+ <source>${java.version}</source>
+ <target>${java.version}</target>
+ </configuration>
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-compiler-javac-errorprone</artifactId>
+ <version>2.8.2</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.errorprone</groupId>
+ <artifactId>error_prone_core</artifactId>
+ <version>2.3.1</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>2.17</version>
+ <dependencies>
+ <dependency>
+ <groupId>com.puppycrawl.tools</groupId>
+ <artifactId>checkstyle</artifactId>
+ <version>8.7</version>
+ </dependency>
+ </dependencies>
+ <configuration>
+ <failsOnError>true</failsOnError>
+ <configLocation>checkstyle.xml</configLocation>
+ <consoleOutput>true</consoleOutput>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>verify</phase>
+ <goals>
+ <goal>checkstyle</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>3.0.2</version>
+ <configuration>
+ <archive>
+ <manifestEntries>
+ <Automatic-Module-Name>com.squareup.javapoet</Automatic-Module-Name>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+
+ </plugins>
+ </build>
+</project>
diff --git a/src/main/java/com/squareup/javapoet/AnnotationSpec.java b/src/main/java/com/squareup/javapoet/AnnotationSpec.java
new file mode 100644
index 0000000..d1c5e53
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/AnnotationSpec.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.SimpleAnnotationValueVisitor8;
+
+import static com.squareup.javapoet.Util.characterLiteralWithoutSingleQuotes;
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+
+/** A generated annotation on a declaration. */
+public final class AnnotationSpec {
+ public final TypeName type;
+ public final Map<String, List<CodeBlock>> members;
+
+ private AnnotationSpec(Builder builder) {
+ this.type = builder.type;
+ this.members = Util.immutableMultimap(builder.members);
+ }
+
+ void emit(CodeWriter codeWriter, boolean inline) throws IOException {
+ String whitespace = inline ? "" : "\n";
+ String memberSeparator = inline ? ", " : ",\n";
+ if (members.isEmpty()) {
+ // @Singleton
+ codeWriter.emit("@$T", type);
+ } else if (members.size() == 1 && members.containsKey("value")) {
+ // @Named("foo")
+ codeWriter.emit("@$T(", type);
+ emitAnnotationValues(codeWriter, whitespace, memberSeparator, members.get("value"));
+ codeWriter.emit(")");
+ } else {
+ // Inline:
+ // @Column(name = "updated_at", nullable = false)
+ //
+ // Not inline:
+ // @Column(
+ // name = "updated_at",
+ // nullable = false
+ // )
+ codeWriter.emit("@$T(" + whitespace, type);
+ codeWriter.indent(2);
+ for (Iterator<Map.Entry<String, List<CodeBlock>>> i
+ = members.entrySet().iterator(); i.hasNext(); ) {
+ Map.Entry<String, List<CodeBlock>> entry = i.next();
+ codeWriter.emit("$L = ", entry.getKey());
+ emitAnnotationValues(codeWriter, whitespace, memberSeparator, entry.getValue());
+ if (i.hasNext()) codeWriter.emit(memberSeparator);
+ }
+ codeWriter.unindent(2);
+ codeWriter.emit(whitespace + ")");
+ }
+ }
+
+ private void emitAnnotationValues(CodeWriter codeWriter, String whitespace,
+ String memberSeparator, List<CodeBlock> values) throws IOException {
+ if (values.size() == 1) {
+ codeWriter.indent(2);
+ codeWriter.emit(values.get(0));
+ codeWriter.unindent(2);
+ return;
+ }
+
+ codeWriter.emit("{" + whitespace);
+ codeWriter.indent(2);
+ boolean first = true;
+ for (CodeBlock codeBlock : values) {
+ if (!first) codeWriter.emit(memberSeparator);
+ codeWriter.emit(codeBlock);
+ first = false;
+ }
+ codeWriter.unindent(2);
+ codeWriter.emit(whitespace + "}");
+ }
+
+ public static AnnotationSpec get(Annotation annotation) {
+ return get(annotation, false);
+ }
+
+ public static AnnotationSpec get(Annotation annotation, boolean includeDefaultValues) {
+ Builder builder = builder(annotation.annotationType());
+ try {
+ Method[] methods = annotation.annotationType().getDeclaredMethods();
+ Arrays.sort(methods, Comparator.comparing(Method::getName));
+ for (Method method : methods) {
+ Object value = method.invoke(annotation);
+ if (!includeDefaultValues) {
+ if (Objects.deepEquals(value, method.getDefaultValue())) {
+ continue;
+ }
+ }
+ if (value.getClass().isArray()) {
+ for (int i = 0; i < Array.getLength(value); i++) {
+ builder.addMemberForValue(method.getName(), Array.get(value, i));
+ }
+ continue;
+ }
+ if (value instanceof Annotation) {
+ builder.addMember(method.getName(), "$L", get((Annotation) value));
+ continue;
+ }
+ builder.addMemberForValue(method.getName(), value);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Reflecting " + annotation + " failed!", e);
+ }
+ return builder.build();
+ }
+
+ public static AnnotationSpec get(AnnotationMirror annotation) {
+ TypeElement element = (TypeElement) annotation.getAnnotationType().asElement();
+ AnnotationSpec.Builder builder = AnnotationSpec.builder(ClassName.get(element));
+ Visitor visitor = new Visitor(builder);
+ for (ExecutableElement executableElement : annotation.getElementValues().keySet()) {
+ String name = executableElement.getSimpleName().toString();
+ AnnotationValue value = annotation.getElementValues().get(executableElement);
+ value.accept(visitor, name);
+ }
+ return builder.build();
+ }
+
+ public static Builder builder(ClassName type) {
+ checkNotNull(type, "type == null");
+ return new Builder(type);
+ }
+
+ public static Builder builder(Class<?> type) {
+ return builder(ClassName.get(type));
+ }
+
+ public Builder toBuilder() {
+ Builder builder = new Builder(type);
+ for (Map.Entry<String, List<CodeBlock>> entry : members.entrySet()) {
+ builder.members.put(entry.getKey(), new ArrayList<>(entry.getValue()));
+ }
+ return builder;
+ }
+
+ @Override public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (getClass() != o.getClass()) return false;
+ return toString().equals(o.toString());
+ }
+
+ @Override public int hashCode() {
+ return toString().hashCode();
+ }
+
+ @Override public String toString() {
+ StringBuilder out = new StringBuilder();
+ try {
+ CodeWriter codeWriter = new CodeWriter(out);
+ codeWriter.emit("$L", this);
+ return out.toString();
+ } catch (IOException e) {
+ throw new AssertionError();
+ }
+ }
+
+ public static final class Builder {
+ private final TypeName type;
+ private final Map<String, List<CodeBlock>> members = new LinkedHashMap<>();
+
+ private Builder(TypeName type) {
+ this.type = type;
+ }
+
+ public Builder addMember(String name, String format, Object... args) {
+ return addMember(name, CodeBlock.of(format, args));
+ }
+
+ public Builder addMember(String name, CodeBlock codeBlock) {
+ checkNotNull(name, "name == null");
+ checkArgument(SourceVersion.isName(name), "not a valid name: %s", name);
+ List<CodeBlock> values = members.computeIfAbsent(name, k -> new ArrayList<>());
+ values.add(codeBlock);
+ return this;
+ }
+
+ /**
+ * Delegates to {@link #addMember(String, String, Object...)}, with parameter {@code format}
+ * depending on the given {@code value} object. Falls back to {@code "$L"} literal format if
+ * the class of the given {@code value} object is not supported.
+ */
+ Builder addMemberForValue(String memberName, Object value) {
+ checkNotNull(memberName, "memberName == null");
+ checkNotNull(value, "value == null, constant non-null value expected for %s", memberName);
+ checkArgument(SourceVersion.isName(memberName), "not a valid name: %s", memberName);
+ if (value instanceof Class<?>) {
+ return addMember(memberName, "$T.class", value);
+ }
+ if (value instanceof Enum) {
+ return addMember(memberName, "$T.$L", value.getClass(), ((Enum<?>) value).name());
+ }
+ if (value instanceof String) {
+ return addMember(memberName, "$S", value);
+ }
+ if (value instanceof Float) {
+ return addMember(memberName, "$Lf", value);
+ }
+ if (value instanceof Character) {
+ return addMember(memberName, "'$L'", characterLiteralWithoutSingleQuotes((char) value));
+ }
+ return addMember(memberName, "$L", value);
+ }
+
+ public AnnotationSpec build() {
+ return new AnnotationSpec(this);
+ }
+ }
+
+ /**
+ * Annotation value visitor adding members to the given builder instance.
+ */
+ private static class Visitor extends SimpleAnnotationValueVisitor8<Builder, String> {
+ final Builder builder;
+
+ Visitor(Builder builder) {
+ super(builder);
+ this.builder = builder;
+ }
+
+ @Override protected Builder defaultAction(Object o, String name) {
+ return builder.addMemberForValue(name, o);
+ }
+
+ @Override public Builder visitAnnotation(AnnotationMirror a, String name) {
+ return builder.addMember(name, "$L", get(a));
+ }
+
+ @Override public Builder visitEnumConstant(VariableElement c, String name) {
+ return builder.addMember(name, "$T.$L", c.asType(), c.getSimpleName());
+ }
+
+ @Override public Builder visitType(TypeMirror t, String name) {
+ return builder.addMember(name, "$T.class", t);
+ }
+
+ @Override public Builder visitArray(List<? extends AnnotationValue> values, String name) {
+ for (AnnotationValue value : values) {
+ value.accept(this, name);
+ }
+ return builder;
+ }
+ }
+}
diff --git a/src/main/java/com/squareup/javapoet/ArrayTypeName.java b/src/main/java/com/squareup/javapoet/ArrayTypeName.java
new file mode 100644
index 0000000..219c3f3
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/ArrayTypeName.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.ArrayType;
+
+import static com.squareup.javapoet.Util.checkNotNull;
+
+public final class ArrayTypeName extends TypeName {
+ public final TypeName componentType;
+
+ private ArrayTypeName(TypeName componentType) {
+ this(componentType, new ArrayList<>());
+ }
+
+ private ArrayTypeName(TypeName componentType, List<AnnotationSpec> annotations) {
+ super(annotations);
+ this.componentType = checkNotNull(componentType, "rawType == null");
+ }
+
+ @Override public ArrayTypeName annotated(List<AnnotationSpec> annotations) {
+ return new ArrayTypeName(componentType, concatAnnotations(annotations));
+ }
+
+ @Override public TypeName withoutAnnotations() {
+ return new ArrayTypeName(componentType);
+ }
+
+ @Override CodeWriter emit(CodeWriter out) throws IOException {
+ return emit(out, false);
+ }
+
+ CodeWriter emit(CodeWriter out, boolean varargs) throws IOException {
+ emitLeafType(out);
+ return emitBrackets(out, varargs);
+ }
+
+ private CodeWriter emitLeafType(CodeWriter out) throws IOException {
+ if (TypeName.asArray(componentType) != null) {
+ return TypeName.asArray(componentType).emitLeafType(out);
+ }
+ return componentType.emit(out);
+ }
+
+ private CodeWriter emitBrackets(CodeWriter out, boolean varargs) throws IOException {
+ if (isAnnotated()) {
+ out.emit(" ");
+ emitAnnotations(out);
+ }
+
+ if (TypeName.asArray(componentType) == null) {
+ // Last bracket.
+ return out.emit(varargs ? "..." : "[]");
+ }
+ out.emit("[]");
+ return TypeName.asArray(componentType) .emitBrackets(out, varargs);
+ }
+
+
+ /** Returns an array type whose elements are all instances of {@code componentType}. */
+ public static ArrayTypeName of(TypeName componentType) {
+ return new ArrayTypeName(componentType);
+ }
+
+ /** Returns an array type whose elements are all instances of {@code componentType}. */
+ public static ArrayTypeName of(Type componentType) {
+ return of(TypeName.get(componentType));
+ }
+
+ /** Returns an array type equivalent to {@code mirror}. */
+ public static ArrayTypeName get(ArrayType mirror) {
+ return get(mirror, new LinkedHashMap<>());
+ }
+
+ static ArrayTypeName get(
+ ArrayType mirror, Map<TypeParameterElement, TypeVariableName> typeVariables) {
+ return new ArrayTypeName(get(mirror.getComponentType(), typeVariables));
+ }
+
+ /** Returns an array type equivalent to {@code type}. */
+ public static ArrayTypeName get(GenericArrayType type) {
+ return get(type, new LinkedHashMap<>());
+ }
+
+ static ArrayTypeName get(GenericArrayType type, Map<Type, TypeVariableName> map) {
+ return ArrayTypeName.of(get(type.getGenericComponentType(), map));
+ }
+}
diff --git a/src/main/java/com/squareup/javapoet/ClassName.java b/src/main/java/com/squareup/javapoet/ClassName.java
new file mode 100644
index 0000000..99c4ed2
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/ClassName.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.SimpleElementVisitor8;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+
+/** A fully-qualified class name for top-level and member classes. */
+public final class ClassName extends TypeName implements Comparable<ClassName> {
+ public static final ClassName OBJECT = ClassName.get(Object.class);
+
+ /** The package name of this class, or "" if this is in the default package. */
+ final String packageName;
+
+ /** The enclosing class, or null if this is not enclosed in another class. */
+ final ClassName enclosingClassName;
+
+ /** This class name, like "Entry" for java.util.Map.Entry. */
+ final String simpleName;
+
+ /** The full class name like "java.util.Map.Entry". */
+ final String canonicalName;
+
+ private ClassName(String packageName, ClassName enclosingClassName, String simpleName) {
+ this(packageName, enclosingClassName, simpleName, Collections.emptyList());
+ }
+
+ private ClassName(String packageName, ClassName enclosingClassName, String simpleName,
+ List<AnnotationSpec> annotations) {
+ super(annotations);
+ this.packageName = packageName;
+ this.enclosingClassName = enclosingClassName;
+ this.simpleName = simpleName;
+ this.canonicalName = enclosingClassName != null
+ ? (enclosingClassName.canonicalName + '.' + simpleName)
+ : (packageName.isEmpty() ? simpleName : packageName + '.' + simpleName);
+ }
+
+ @Override public ClassName annotated(List<AnnotationSpec> annotations) {
+ return new ClassName(packageName, enclosingClassName, simpleName,
+ concatAnnotations(annotations));
+ }
+
+ @Override public ClassName withoutAnnotations() {
+ if (!isAnnotated()) return this;
+ ClassName resultEnclosingClassName = enclosingClassName != null
+ ? enclosingClassName.withoutAnnotations()
+ : null;
+ return new ClassName(packageName, resultEnclosingClassName, simpleName);
+ }
+
+ @Override public boolean isAnnotated() {
+ return super.isAnnotated() || (enclosingClassName != null && enclosingClassName.isAnnotated());
+ }
+
+ /**
+ * Returns the package name, like {@code "java.util"} for {@code Map.Entry}. Returns the empty
+ * string for the default package.
+ */
+ public String packageName() {
+ return packageName;
+ }
+
+ /**
+ * Returns the enclosing class, like {@link Map} for {@code Map.Entry}. Returns null if this class
+ * is not nested in another class.
+ */
+ public ClassName enclosingClassName() {
+ return enclosingClassName;
+ }
+
+ /**
+ * Returns the top class in this nesting group. Equivalent to chained calls to {@link
+ * #enclosingClassName()} until the result's enclosing class is null.
+ */
+ public ClassName topLevelClassName() {
+ return enclosingClassName != null ? enclosingClassName.topLevelClassName() : this;
+ }
+
+ /** Return the binary name of a class. */
+ public String reflectionName() {
+ return enclosingClassName != null
+ ? (enclosingClassName.reflectionName() + '$' + simpleName)
+ : (packageName.isEmpty() ? simpleName : packageName + '.' + simpleName);
+ }
+
+ public List<String> simpleNames() {
+ List<String> simpleNames = new ArrayList<>();
+ if (enclosingClassName != null) {
+ simpleNames.addAll(enclosingClassName().simpleNames());
+ }
+ simpleNames.add(simpleName);
+ return simpleNames;
+ }
+
+ /**
+ * Returns a class that shares the same enclosing package or class. If this class is enclosed by
+ * another class, this is equivalent to {@code enclosingClassName().nestedClass(name)}. Otherwise
+ * it is equivalent to {@code get(packageName(), name)}.
+ */
+ public ClassName peerClass(String name) {
+ return new ClassName(packageName, enclosingClassName, name);
+ }
+
+ /**
+ * Returns a new {@link ClassName} instance for the specified {@code name} as nested inside this
+ * class.
+ */
+ public ClassName nestedClass(String name) {
+ return new ClassName(packageName, this, name);
+ }
+
+ /** Returns the simple name of this class, like {@code "Entry"} for {@link Map.Entry}. */
+ public String simpleName() {
+ return simpleName;
+ }
+
+ public static ClassName get(Class<?> clazz) {
+ checkNotNull(clazz, "clazz == null");
+ checkArgument(!clazz.isPrimitive(), "primitive types cannot be represented as a ClassName");
+ checkArgument(!void.class.equals(clazz), "'void' type cannot be represented as a ClassName");
+ checkArgument(!clazz.isArray(), "array types cannot be represented as a ClassName");
+
+ String anonymousSuffix = "";
+ while (clazz.isAnonymousClass()) {
+ int lastDollar = clazz.getName().lastIndexOf('$');
+ anonymousSuffix = clazz.getName().substring(lastDollar) + anonymousSuffix;
+ clazz = clazz.getEnclosingClass();
+ }
+ String name = clazz.getSimpleName() + anonymousSuffix;
+
+ if (clazz.getEnclosingClass() == null) {
+ // Avoid unreliable Class.getPackage(). https://github.com/square/javapoet/issues/295
+ int lastDot = clazz.getName().lastIndexOf('.');
+ String packageName = (lastDot != -1) ? clazz.getName().substring(0, lastDot) : null;
+ return new ClassName(packageName, null, name);
+ }
+
+ return ClassName.get(clazz.getEnclosingClass()).nestedClass(name);
+ }
+
+ /**
+ * Returns a new {@link ClassName} instance for the given fully-qualified class name string. This
+ * method assumes that the input is ASCII and follows typical Java style (lowercase package
+ * names, UpperCamelCase class names) and may produce incorrect results or throw
+ * {@link IllegalArgumentException} otherwise. For that reason, {@link #get(Class)} and
+ * {@link #get(Class)} should be preferred as they can correctly create {@link ClassName}
+ * instances without such restrictions.
+ */
+ public static ClassName bestGuess(String classNameString) {
+ // Add the package name, like "java.util.concurrent", or "" for no package.
+ int p = 0;
+ while (p < classNameString.length() && Character.isLowerCase(classNameString.codePointAt(p))) {
+ p = classNameString.indexOf('.', p) + 1;
+ checkArgument(p != 0, "couldn't make a guess for %s", classNameString);
+ }
+ String packageName = p == 0 ? "" : classNameString.substring(0, p - 1);
+
+ // Add class names like "Map" and "Entry".
+ ClassName className = null;
+ for (String simpleName : classNameString.substring(p).split("\\.", -1)) {
+ checkArgument(!simpleName.isEmpty() && Character.isUpperCase(simpleName.codePointAt(0)),
+ "couldn't make a guess for %s", classNameString);
+ className = new ClassName(packageName, className, simpleName);
+ }
+
+ return className;
+ }
+
+ /**
+ * Returns a class name created from the given parts. For example, calling this with package name
+ * {@code "java.util"} and simple names {@code "Map"}, {@code "Entry"} yields {@link Map.Entry}.
+ */
+ public static ClassName get(String packageName, String simpleName, String... simpleNames) {
+ ClassName className = new ClassName(packageName, null, simpleName);
+ for (String name : simpleNames) {
+ className = className.nestedClass(name);
+ }
+ return className;
+ }
+
+ /** Returns the class name for {@code element}. */
+ public static ClassName get(TypeElement element) {
+ checkNotNull(element, "element == null");
+ String simpleName = element.getSimpleName().toString();
+
+ return element.getEnclosingElement().accept(new SimpleElementVisitor8<ClassName, Void>() {
+ @Override public ClassName visitPackage(PackageElement packageElement, Void p) {
+ return new ClassName(packageElement.getQualifiedName().toString(), null, simpleName);
+ }
+
+ @Override public ClassName visitType(TypeElement enclosingClass, Void p) {
+ return ClassName.get(enclosingClass).nestedClass(simpleName);
+ }
+
+ @Override public ClassName visitUnknown(Element unknown, Void p) {
+ return get("", simpleName);
+ }
+
+ @Override public ClassName defaultAction(Element enclosingElement, Void p) {
+ throw new IllegalArgumentException("Unexpected type nesting: " + element);
+ }
+ }, null);
+ }
+
+ @Override public int compareTo(ClassName o) {
+ return canonicalName.compareTo(o.canonicalName);
+ }
+
+ @Override CodeWriter emit(CodeWriter out) throws IOException {
+ boolean charsEmitted = false;
+ for (ClassName className : enclosingClasses()) {
+ String simpleName;
+ if (charsEmitted) {
+ // We've already emitted an enclosing class. Emit as we go.
+ out.emit(".");
+ simpleName = className.simpleName;
+
+ } else if (className.isAnnotated() || className == this) {
+ // We encountered the first enclosing class that must be emitted.
+ String qualifiedName = out.lookupName(className);
+ int dot = qualifiedName.lastIndexOf('.');
+ if (dot != -1) {
+ out.emitAndIndent(qualifiedName.substring(0, dot + 1));
+ simpleName = qualifiedName.substring(dot + 1);
+ charsEmitted = true;
+ } else {
+ simpleName = qualifiedName;
+ }
+
+ } else {
+ // Don't emit this enclosing type. Keep going so we can be more precise.
+ continue;
+ }
+
+ if (className.isAnnotated()) {
+ if (charsEmitted) out.emit(" ");
+ className.emitAnnotations(out);
+ }
+
+ out.emit(simpleName);
+ charsEmitted = true;
+ }
+
+ return out;
+ }
+
+ /** Returns all enclosing classes in this, outermost first. */
+ private List<ClassName> enclosingClasses() {
+ List<ClassName> result = new ArrayList<>();
+ for (ClassName c = this; c != null; c = c.enclosingClassName) {
+ result.add(c);
+ }
+ Collections.reverse(result);
+ return result;
+ }
+}
diff --git a/src/main/java/com/squareup/javapoet/CodeBlock.java b/src/main/java/com/squareup/javapoet/CodeBlock.java
new file mode 100644
index 0000000..33e3846
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/CodeBlock.java
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collector;
+import java.util.stream.StreamSupport;
+import javax.lang.model.element.Element;
+import javax.lang.model.type.TypeMirror;
+
+import static com.squareup.javapoet.Util.checkArgument;
+
+/**
+ * A fragment of a .java file, potentially containing declarations, statements, and documentation.
+ * Code blocks are not necessarily well-formed Java code, and are not validated. This class assumes
+ * javac will check correctness later!
+ *
+ * <p>Code blocks support placeholders like {@link java.text.Format}. Where {@link String#format}
+ * uses percent {@code %} to reference target values, this class uses dollar sign {@code $} and has
+ * its own set of permitted placeholders:
+ *
+ * <ul>
+ * <li>{@code $L} emits a <em>literal</em> value with no escaping. Arguments for literals may be
+ * strings, primitives, {@linkplain TypeSpec type declarations}, {@linkplain AnnotationSpec
+ * annotations} and even other code blocks.
+ * <li>{@code $N} emits a <em>name</em>, using name collision avoidance where necessary. Arguments
+ * for names may be strings (actually any {@linkplain CharSequence character sequence}),
+ * {@linkplain ParameterSpec parameters}, {@linkplain FieldSpec fields}, {@linkplain
+ * MethodSpec methods}, and {@linkplain TypeSpec types}.
+ * <li>{@code $S} escapes the value as a <em>string</em>, wraps it with double quotes, and emits
+ * that. For example, {@code 6" sandwich} is emitted {@code "6\" sandwich"}.
+ * <li>{@code $T} emits a <em>type</em> reference. Types will be imported if possible. Arguments
+ * for types may be {@linkplain Class classes}, {@linkplain javax.lang.model.type.TypeMirror
+,* type mirrors}, and {@linkplain javax.lang.model.element.Element elements}.
+ * <li>{@code $$} emits a dollar sign.
+ * <li>{@code $W} emits a space or a newline, depending on its position on the line. This prefers
+ * to wrap lines before 100 columns.
+ * <li>{@code $Z} acts as a zero-width space. This prefers to wrap lines before 100 columns.
+ * <li>{@code $>} increases the indentation level.
+ * <li>{@code $<} decreases the indentation level.
+ * <li>{@code $[} begins a statement. For multiline statements, every line after the first line
+ * is double-indented.
+ * <li>{@code $]} ends a statement.
+ * </ul>
+ */
+public final class CodeBlock {
+ private static final Pattern NAMED_ARGUMENT =
+ Pattern.compile("\\$(?<argumentName>[\\w_]+):(?<typeChar>[\\w]).*");
+ private static final Pattern LOWERCASE = Pattern.compile("[a-z]+[\\w_]*");
+
+ /** A heterogeneous list containing string literals and value placeholders. */
+ final List<String> formatParts;
+ final List<Object> args;
+
+ private CodeBlock(Builder builder) {
+ this.formatParts = Util.immutableList(builder.formatParts);
+ this.args = Util.immutableList(builder.args);
+ }
+
+ public boolean isEmpty() {
+ return formatParts.isEmpty();
+ }
+
+ @Override public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (getClass() != o.getClass()) return false;
+ return toString().equals(o.toString());
+ }
+
+ @Override public int hashCode() {
+ return toString().hashCode();
+ }
+
+ @Override public String toString() {
+ StringBuilder out = new StringBuilder();
+ try {
+ new CodeWriter(out).emit(this);
+ return out.toString();
+ } catch (IOException e) {
+ throw new AssertionError();
+ }
+ }
+
+ public static CodeBlock of(String format, Object... args) {
+ return new Builder().add(format, args).build();
+ }
+
+ /**
+ * Joins {@code codeBlocks} into a single {@link CodeBlock}, each separated by {@code separator}.
+ * For example, joining {@code String s}, {@code Object o} and {@code int i} using {@code ", "}
+ * would produce {@code String s, Object o, int i}.
+ */
+ public static CodeBlock join(Iterable<CodeBlock> codeBlocks, String separator) {
+ return StreamSupport.stream(codeBlocks.spliterator(), false).collect(joining(separator));
+ }
+
+ /**
+ * A {@link Collector} implementation that joins {@link CodeBlock} instances together into one
+ * separated by {@code separator}. For example, joining {@code String s}, {@code Object o} and
+ * {@code int i} using {@code ", "} would produce {@code String s, Object o, int i}.
+ */
+ public static Collector<CodeBlock, ?, CodeBlock> joining(String separator) {
+ return Collector.of(
+ () -> new CodeBlockJoiner(separator, builder()),
+ CodeBlockJoiner::add,
+ CodeBlockJoiner::merge,
+ CodeBlockJoiner::join);
+ }
+
+ /**
+ * A {@link Collector} implementation that joins {@link CodeBlock} instances together into one
+ * separated by {@code separator}. For example, joining {@code String s}, {@code Object o} and
+ * {@code int i} using {@code ", "} would produce {@code String s, Object o, int i}.
+ */
+ public static Collector<CodeBlock, ?, CodeBlock> joining(
+ String separator, String prefix, String suffix) {
+ Builder builder = builder().add("$N", prefix);
+ return Collector.of(
+ () -> new CodeBlockJoiner(separator, builder),
+ CodeBlockJoiner::add,
+ CodeBlockJoiner::merge,
+ joiner -> {
+ builder.add(CodeBlock.of("$N", suffix));
+ return joiner.join();
+ });
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public Builder toBuilder() {
+ Builder builder = new Builder();
+ builder.formatParts.addAll(formatParts);
+ builder.args.addAll(args);
+ return builder;
+ }
+
+ public static final class Builder {
+ final List<String> formatParts = new ArrayList<>();
+ final List<Object> args = new ArrayList<>();
+
+ private Builder() {
+ }
+
+ public boolean isEmpty() {
+ return formatParts.isEmpty();
+ }
+
+ /**
+ * Adds code using named arguments.
+ *
+ * <p>Named arguments specify their name after the '$' followed by : and the corresponding type
+ * character. Argument names consist of characters in {@code a-z, A-Z, 0-9, and _} and must
+ * start with a lowercase character.
+ *
+ * <p>For example, to refer to the type {@link java.lang.Integer} with the argument name {@code
+ * clazz} use a format string containing {@code $clazz:T} and include the key {@code clazz} with
+ * value {@code java.lang.Integer.class} in the argument map.
+ */
+ public Builder addNamed(String format, Map<String, ?> arguments) {
+ int p = 0;
+
+ for (String argument : arguments.keySet()) {
+ checkArgument(LOWERCASE.matcher(argument).matches(),
+ "argument '%s' must start with a lowercase character", argument);
+ }
+
+ while (p < format.length()) {
+ int nextP = format.indexOf("$", p);
+ if (nextP == -1) {
+ formatParts.add(format.substring(p, format.length()));
+ break;
+ }
+
+ if (p != nextP) {
+ formatParts.add(format.substring(p, nextP));
+ p = nextP;
+ }
+
+ Matcher matcher = null;
+ int colon = format.indexOf(':', p);
+ if (colon != -1) {
+ int endIndex = Math.min(colon + 2, format.length());
+ matcher = NAMED_ARGUMENT.matcher(format.substring(p, endIndex));
+ }
+ if (matcher != null && matcher.lookingAt()) {
+ String argumentName = matcher.group("argumentName");
+ checkArgument(arguments.containsKey(argumentName), "Missing named argument for $%s",
+ argumentName);
+ char formatChar = matcher.group("typeChar").charAt(0);
+ addArgument(format, formatChar, arguments.get(argumentName));
+ formatParts.add("$" + formatChar);
+ p += matcher.regionEnd();
+ } else {
+ checkArgument(p < format.length() - 1, "dangling $ at end");
+ checkArgument(isNoArgPlaceholder(format.charAt(p + 1)),
+ "unknown format $%s at %s in '%s'", format.charAt(p + 1), p + 1, format);
+ formatParts.add(format.substring(p, p + 2));
+ p += 2;
+ }
+ }
+
+ return this;
+ }
+
+ /**
+ * Add code with positional or relative arguments.
+ *
+ * <p>Relative arguments map 1:1 with the placeholders in the format string.
+ *
+ * <p>Positional arguments use an index after the placeholder to identify which argument index
+ * to use. For example, for a literal to reference the 3rd argument: "$3L" (1 based index)
+ *
+ * <p>Mixing relative and positional arguments in a call to add is invalid and will result in an
+ * error.
+ */
+ public Builder add(String format, Object... args) {
+ boolean hasRelative = false;
+ boolean hasIndexed = false;
+
+ int relativeParameterCount = 0;
+ int[] indexedParameterCount = new int[args.length];
+
+ for (int p = 0; p < format.length(); ) {
+ if (format.charAt(p) != '$') {
+ int nextP = format.indexOf('$', p + 1);
+ if (nextP == -1) nextP = format.length();
+ formatParts.add(format.substring(p, nextP));
+ p = nextP;
+ continue;
+ }
+
+ p++; // '$'.
+
+ // Consume zero or more digits, leaving 'c' as the first non-digit char after the '$'.
+ int indexStart = p;
+ char c;
+ do {
+ checkArgument(p < format.length(), "dangling format characters in '%s'", format);
+ c = format.charAt(p++);
+ } while (c >= '0' && c <= '9');
+ int indexEnd = p - 1;
+
+ // If 'c' doesn't take an argument, we're done.
+ if (isNoArgPlaceholder(c)) {
+ checkArgument(
+ indexStart == indexEnd, "$$, $>, $<, $[, $], $W, and $Z may not have an index");
+ formatParts.add("$" + c);
+ continue;
+ }
+
+ // Find either the indexed argument, or the relative argument. (0-based).
+ int index;
+ if (indexStart < indexEnd) {
+ index = Integer.parseInt(format.substring(indexStart, indexEnd)) - 1;
+ hasIndexed = true;
+ if (args.length > 0) {
+ indexedParameterCount[index % args.length]++; // modulo is needed, checked below anyway
+ }
+ } else {
+ index = relativeParameterCount;
+ hasRelative = true;
+ relativeParameterCount++;
+ }
+
+ checkArgument(index >= 0 && index < args.length,
+ "index %d for '%s' not in range (received %s arguments)",
+ index + 1, format.substring(indexStart - 1, indexEnd + 1), args.length);
+ checkArgument(!hasIndexed || !hasRelative, "cannot mix indexed and positional parameters");
+
+ addArgument(format, c, args[index]);
+
+ formatParts.add("$" + c);
+ }
+
+ if (hasRelative) {
+ checkArgument(relativeParameterCount >= args.length,
+ "unused arguments: expected %s, received %s", relativeParameterCount, args.length);
+ }
+ if (hasIndexed) {
+ List<String> unused = new ArrayList<>();
+ for (int i = 0; i < args.length; i++) {
+ if (indexedParameterCount[i] == 0) {
+ unused.add("$" + (i + 1));
+ }
+ }
+ String s = unused.size() == 1 ? "" : "s";
+ checkArgument(unused.isEmpty(), "unused argument%s: %s", s, String.join(", ", unused));
+ }
+ return this;
+ }
+
+ private boolean isNoArgPlaceholder(char c) {
+ return c == '$' || c == '>' || c == '<' || c == '[' || c == ']' || c == 'W' || c == 'Z';
+ }
+
+ private void addArgument(String format, char c, Object arg) {
+ switch (c) {
+ case 'N':
+ this.args.add(argToName(arg));
+ break;
+ case 'L':
+ this.args.add(argToLiteral(arg));
+ break;
+ case 'S':
+ this.args.add(argToString(arg));
+ break;
+ case 'T':
+ this.args.add(argToType(arg));
+ break;
+ default:
+ throw new IllegalArgumentException(
+ String.format("invalid format string: '%s'", format));
+ }
+ }
+
+ private String argToName(Object o) {
+ if (o instanceof CharSequence) return o.toString();
+ if (o instanceof ParameterSpec) return ((ParameterSpec) o).name;
+ if (o instanceof FieldSpec) return ((FieldSpec) o).name;
+ if (o instanceof MethodSpec) return ((MethodSpec) o).name;
+ if (o instanceof TypeSpec) return ((TypeSpec) o).name;
+ throw new IllegalArgumentException("expected name but was " + o);
+ }
+
+ private Object argToLiteral(Object o) {
+ return o;
+ }
+
+ private String argToString(Object o) {
+ return o != null ? String.valueOf(o) : null;
+ }
+
+ private TypeName argToType(Object o) {
+ if (o instanceof TypeName) return (TypeName) o;
+ if (o instanceof TypeMirror) return TypeName.get((TypeMirror) o);
+ if (o instanceof Element) return TypeName.get(((Element) o).asType());
+ if (o instanceof Type) return TypeName.get((Type) o);
+ throw new IllegalArgumentException("expected type but was " + o);
+ }
+
+ /**
+ * @param controlFlow the control flow construct and its code, such as "if (foo == 5)".
+ * Shouldn't contain braces or newline characters.
+ */
+ public Builder beginControlFlow(String controlFlow, Object... args) {
+ add(controlFlow + " {\n", args);
+ indent();
+ return this;
+ }
+
+ /**
+ * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
+ * Shouldn't contain braces or newline characters.
+ */
+ public Builder nextControlFlow(String controlFlow, Object... args) {
+ unindent();
+ add("} " + controlFlow + " {\n", args);
+ indent();
+ return this;
+ }
+
+ public Builder endControlFlow() {
+ unindent();
+ add("}\n");
+ return this;
+ }
+
+ /**
+ * @param controlFlow the optional control flow construct and its code, such as
+ * "while(foo == 20)". Only used for "do/while" control flows.
+ */
+ public Builder endControlFlow(String controlFlow, Object... args) {
+ unindent();
+ add("} " + controlFlow + ";\n", args);
+ return this;
+ }
+
+ public Builder addStatement(String format, Object... args) {
+ add("$[");
+ add(format, args);
+ add(";\n$]");
+ return this;
+ }
+
+ public Builder addStatement(CodeBlock codeBlock) {
+ return addStatement("$L", codeBlock);
+ }
+
+ public Builder add(CodeBlock codeBlock) {
+ formatParts.addAll(codeBlock.formatParts);
+ args.addAll(codeBlock.args);
+ return this;
+ }
+
+ public Builder indent() {
+ this.formatParts.add("$>");
+ return this;
+ }
+
+ public Builder unindent() {
+ this.formatParts.add("$<");
+ return this;
+ }
+
+ public CodeBlock build() {
+ return new CodeBlock(this);
+ }
+ }
+
+ private static final class CodeBlockJoiner {
+ private final String delimiter;
+ private final Builder builder;
+ private boolean first = true;
+
+ CodeBlockJoiner(String delimiter, Builder builder) {
+ this.delimiter = delimiter;
+ this.builder = builder;
+ }
+
+ CodeBlockJoiner add(CodeBlock codeBlock) {
+ if (!first) {
+ builder.add(delimiter);
+ }
+ first = false;
+
+ builder.add(codeBlock);
+ return this;
+ }
+
+ CodeBlockJoiner merge(CodeBlockJoiner other) {
+ CodeBlock otherBlock = other.builder.build();
+ if (!otherBlock.isEmpty()) {
+ add(otherBlock);
+ }
+ return this;
+ }
+
+ CodeBlock join() {
+ return builder.build();
+ }
+ }
+}
diff --git a/src/main/java/com/squareup/javapoet/CodeWriter.java b/src/main/java/com/squareup/javapoet/CodeWriter.java
new file mode 100644
index 0000000..542f434
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/CodeWriter.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Modifier;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+import static com.squareup.javapoet.Util.checkState;
+import static com.squareup.javapoet.Util.stringLiteralWithDoubleQuotes;
+import static java.lang.String.join;
+
+/**
+ * Converts a {@link JavaFile} to a string suitable to both human- and javac-consumption. This
+ * honors imports, indentation, and deferred variable names.
+ */
+final class CodeWriter {
+ /** Sentinel value that indicates that no user-provided package has been set. */
+ private static final String NO_PACKAGE = new String();
+
+ private final String indent;
+ private final LineWrapper out;
+ private int indentLevel;
+
+ private boolean javadoc = false;
+ private boolean comment = false;
+ private String packageName = NO_PACKAGE;
+ private final List<TypeSpec> typeSpecStack = new ArrayList<>();
+ private final Set<String> staticImportClassNames;
+ private final Set<String> staticImports;
+ private final Map<String, ClassName> importedTypes;
+ private final Map<String, ClassName> importableTypes = new LinkedHashMap<>();
+ private final Set<String> referencedNames = new LinkedHashSet<>();
+ private boolean trailingNewline;
+
+ /**
+ * When emitting a statement, this is the line of the statement currently being written. The first
+ * line of a statement is indented normally and subsequent wrapped lines are double-indented. This
+ * is -1 when the currently-written line isn't part of a statement.
+ */
+ int statementLine = -1;
+
+ CodeWriter(Appendable out) {
+ this(out, " ", Collections.emptySet());
+ }
+
+ CodeWriter(Appendable out, String indent, Set<String> staticImports) {
+ this(out, indent, Collections.emptyMap(), staticImports);
+ }
+
+ CodeWriter(Appendable out, String indent, Map<String, ClassName> importedTypes,
+ Set<String> staticImports) {
+ this.out = new LineWrapper(out, indent, 100);
+ this.indent = checkNotNull(indent, "indent == null");
+ this.importedTypes = checkNotNull(importedTypes, "importedTypes == null");
+ this.staticImports = checkNotNull(staticImports, "staticImports == null");
+ this.staticImportClassNames = new LinkedHashSet<>();
+ for (String signature : staticImports) {
+ staticImportClassNames.add(signature.substring(0, signature.lastIndexOf('.')));
+ }
+ }
+
+ public Map<String, ClassName> importedTypes() {
+ return importedTypes;
+ }
+
+ public CodeWriter indent() {
+ return indent(1);
+ }
+
+ public CodeWriter indent(int levels) {
+ indentLevel += levels;
+ return this;
+ }
+
+ public CodeWriter unindent() {
+ return unindent(1);
+ }
+
+ public CodeWriter unindent(int levels) {
+ checkArgument(indentLevel - levels >= 0, "cannot unindent %s from %s", levels, indentLevel);
+ indentLevel -= levels;
+ return this;
+ }
+
+ public CodeWriter pushPackage(String packageName) {
+ checkState(this.packageName == NO_PACKAGE, "package already set: %s", this.packageName);
+ this.packageName = checkNotNull(packageName, "packageName == null");
+ return this;
+ }
+
+ public CodeWriter popPackage() {
+ checkState(this.packageName != NO_PACKAGE, "package not set");
+ this.packageName = NO_PACKAGE;
+ return this;
+ }
+
+ public CodeWriter pushType(TypeSpec type) {
+ this.typeSpecStack.add(type);
+ return this;
+ }
+
+ public CodeWriter popType() {
+ this.typeSpecStack.remove(typeSpecStack.size() - 1);
+ return this;
+ }
+
+ public void emitComment(CodeBlock codeBlock) throws IOException {
+ trailingNewline = true; // Force the '//' prefix for the comment.
+ comment = true;
+ try {
+ emit(codeBlock);
+ emit("\n");
+ } finally {
+ comment = false;
+ }
+ }
+
+ public void emitJavadoc(CodeBlock javadocCodeBlock) throws IOException {
+ if (javadocCodeBlock.isEmpty()) return;
+
+ emit("/**\n");
+ javadoc = true;
+ try {
+ emit(javadocCodeBlock);
+ } finally {
+ javadoc = false;
+ }
+ emit(" */\n");
+ }
+
+ public void emitAnnotations(List<AnnotationSpec> annotations, boolean inline) throws IOException {
+ for (AnnotationSpec annotationSpec : annotations) {
+ annotationSpec.emit(this, inline);
+ emit(inline ? " " : "\n");
+ }
+ }
+
+ /**
+ * Emits {@code modifiers} in the standard order. Modifiers in {@code implicitModifiers} will not
+ * be emitted.
+ */
+ public void emitModifiers(Set<Modifier> modifiers, Set<Modifier> implicitModifiers)
+ throws IOException {
+ if (modifiers.isEmpty()) return;
+ for (Modifier modifier : EnumSet.copyOf(modifiers)) {
+ if (implicitModifiers.contains(modifier)) continue;
+ emitAndIndent(modifier.name().toLowerCase(Locale.US));
+ emitAndIndent(" ");
+ }
+ }
+
+ public void emitModifiers(Set<Modifier> modifiers) throws IOException {
+ emitModifiers(modifiers, Collections.emptySet());
+ }
+
+ /**
+ * Emit type variables with their bounds. This should only be used when declaring type variables;
+ * everywhere else bounds are omitted.
+ */
+ public void emitTypeVariables(List<TypeVariableName> typeVariables) throws IOException {
+ if (typeVariables.isEmpty()) return;
+
+ emit("<");
+ boolean firstTypeVariable = true;
+ for (TypeVariableName typeVariable : typeVariables) {
+ if (!firstTypeVariable) emit(", ");
+ emitAnnotations(typeVariable.annotations, true);
+ emit("$L", typeVariable.name);
+ boolean firstBound = true;
+ for (TypeName bound : typeVariable.bounds) {
+ emit(firstBound ? " extends $T" : " & $T", bound);
+ firstBound = false;
+ }
+ firstTypeVariable = false;
+ }
+ emit(">");
+ }
+
+ public CodeWriter emit(String s) throws IOException {
+ return emitAndIndent(s);
+ }
+
+ public CodeWriter emit(String format, Object... args) throws IOException {
+ return emit(CodeBlock.of(format, args));
+ }
+
+ public CodeWriter emit(CodeBlock codeBlock) throws IOException {
+ int a = 0;
+ ClassName deferredTypeName = null; // used by "import static" logic
+ ListIterator<String> partIterator = codeBlock.formatParts.listIterator();
+ while (partIterator.hasNext()) {
+ String part = partIterator.next();
+ switch (part) {
+ case "$L":
+ emitLiteral(codeBlock.args.get(a++));
+ break;
+
+ case "$N":
+ emitAndIndent((String) codeBlock.args.get(a++));
+ break;
+
+ case "$S":
+ String string = (String) codeBlock.args.get(a++);
+ // Emit null as a literal null: no quotes.
+ emitAndIndent(string != null
+ ? stringLiteralWithDoubleQuotes(string, indent)
+ : "null");
+ break;
+
+ case "$T":
+ TypeName typeName = (TypeName) codeBlock.args.get(a++);
+ // defer "typeName.emit(this)" if next format part will be handled by the default case
+ if (typeName instanceof ClassName && partIterator.hasNext()) {
+ if (!codeBlock.formatParts.get(partIterator.nextIndex()).startsWith("$")) {
+ ClassName candidate = (ClassName) typeName;
+ if (staticImportClassNames.contains(candidate.canonicalName)) {
+ checkState(deferredTypeName == null, "pending type for static import?!");
+ deferredTypeName = candidate;
+ break;
+ }
+ }
+ }
+ typeName.emit(this);
+ break;
+
+ case "$$":
+ emitAndIndent("$");
+ break;
+
+ case "$>":
+ indent();
+ break;
+
+ case "$<":
+ unindent();
+ break;
+
+ case "$[":
+ checkState(statementLine == -1, "statement enter $[ followed by statement enter $[");
+ statementLine = 0;
+ break;
+
+ case "$]":
+ checkState(statementLine != -1, "statement exit $] has no matching statement enter $[");
+ if (statementLine > 0) {
+ unindent(2); // End a multi-line statement. Decrease the indentation level.
+ }
+ statementLine = -1;
+ break;
+
+ case "$W":
+ out.wrappingSpace(indentLevel + 2);
+ break;
+
+ case "$Z":
+ out.zeroWidthSpace(indentLevel + 2);
+ break;
+
+ default:
+ // handle deferred type
+ if (deferredTypeName != null) {
+ if (part.startsWith(".")) {
+ if (emitStaticImportMember(deferredTypeName.canonicalName, part)) {
+ // okay, static import hit and all was emitted, so clean-up and jump to next part
+ deferredTypeName = null;
+ break;
+ }
+ }
+ deferredTypeName.emit(this);
+ deferredTypeName = null;
+ }
+ emitAndIndent(part);
+ break;
+ }
+ }
+ return this;
+ }
+
+ public CodeWriter emitWrappingSpace() throws IOException {
+ out.wrappingSpace(indentLevel + 2);
+ return this;
+ }
+
+ private static String extractMemberName(String part) {
+ checkArgument(Character.isJavaIdentifierStart(part.charAt(0)), "not an identifier: %s", part);
+ for (int i = 1; i <= part.length(); i++) {
+ if (!SourceVersion.isIdentifier(part.substring(0, i))) {
+ return part.substring(0, i - 1);
+ }
+ }
+ return part;
+ }
+
+ private boolean emitStaticImportMember(String canonical, String part) throws IOException {
+ String partWithoutLeadingDot = part.substring(1);
+ if (partWithoutLeadingDot.isEmpty()) return false;
+ char first = partWithoutLeadingDot.charAt(0);
+ if (!Character.isJavaIdentifierStart(first)) return false;
+ String explicit = canonical + "." + extractMemberName(partWithoutLeadingDot);
+ String wildcard = canonical + ".*";
+ if (staticImports.contains(explicit) || staticImports.contains(wildcard)) {
+ emitAndIndent(partWithoutLeadingDot);
+ return true;
+ }
+ return false;
+ }
+
+ private void emitLiteral(Object o) throws IOException {
+ if (o instanceof TypeSpec) {
+ TypeSpec typeSpec = (TypeSpec) o;
+ typeSpec.emit(this, null, Collections.emptySet());
+ } else if (o instanceof AnnotationSpec) {
+ AnnotationSpec annotationSpec = (AnnotationSpec) o;
+ annotationSpec.emit(this, true);
+ } else if (o instanceof CodeBlock) {
+ CodeBlock codeBlock = (CodeBlock) o;
+ emit(codeBlock);
+ } else {
+ emitAndIndent(String.valueOf(o));
+ }
+ }
+
+ /**
+ * Returns the best name to identify {@code className} with in the current context. This uses the
+ * available imports and the current scope to find the shortest name available. It does not honor
+ * names visible due to inheritance.
+ */
+ String lookupName(ClassName className) {
+ // Find the shortest suffix of className that resolves to className. This uses both local type
+ // names (so `Entry` in `Map` refers to `Map.Entry`). Also uses imports.
+ boolean nameResolved = false;
+ for (ClassName c = className; c != null; c = c.enclosingClassName()) {
+ ClassName resolved = resolve(c.simpleName());
+ nameResolved = resolved != null;
+
+ if (resolved != null && Objects.equals(resolved.canonicalName, c.canonicalName)) {
+ int suffixOffset = c.simpleNames().size() - 1;
+ return join(".", className.simpleNames().subList(
+ suffixOffset, className.simpleNames().size()));
+ }
+ }
+
+ // If the name resolved but wasn't a match, we're stuck with the fully qualified name.
+ if (nameResolved) {
+ return className.canonicalName;
+ }
+
+ // If the class is in the same package, we're done.
+ if (Objects.equals(packageName, className.packageName())) {
+ referencedNames.add(className.topLevelClassName().simpleName());
+ return join(".", className.simpleNames());
+ }
+
+ // We'll have to use the fully-qualified name. Mark the type as importable for a future pass.
+ if (!javadoc) {
+ importableType(className);
+ }
+
+ return className.canonicalName;
+ }
+
+ private void importableType(ClassName className) {
+ if (className.packageName().isEmpty()) {
+ return;
+ }
+ ClassName topLevelClassName = className.topLevelClassName();
+ String simpleName = topLevelClassName.simpleName();
+ ClassName replaced = importableTypes.put(simpleName, topLevelClassName);
+ if (replaced != null) {
+ importableTypes.put(simpleName, replaced); // On collision, prefer the first inserted.
+ }
+ }
+
+ /**
+ * Returns the class referenced by {@code simpleName}, using the current nesting context and
+ * imports.
+ */
+ // TODO(jwilson): also honor superclass members when resolving names.
+ private ClassName resolve(String simpleName) {
+ // Match a child of the current (potentially nested) class.
+ for (int i = typeSpecStack.size() - 1; i >= 0; i--) {
+ TypeSpec typeSpec = typeSpecStack.get(i);
+ for (TypeSpec visibleChild : typeSpec.typeSpecs) {
+ if (Objects.equals(visibleChild.name, simpleName)) {
+ return stackClassName(i, simpleName);
+ }
+ }
+ }
+
+ // Match the top-level class.
+ if (typeSpecStack.size() > 0 && Objects.equals(typeSpecStack.get(0).name, simpleName)) {
+ return ClassName.get(packageName, simpleName);
+ }
+
+ // Match an imported type.
+ ClassName importedType = importedTypes.get(simpleName);
+ if (importedType != null) return importedType;
+
+ // No match.
+ return null;
+ }
+
+ /** Returns the class named {@code simpleName} when nested in the class at {@code stackDepth}. */
+ private ClassName stackClassName(int stackDepth, String simpleName) {
+ ClassName className = ClassName.get(packageName, typeSpecStack.get(0).name);
+ for (int i = 1; i <= stackDepth; i++) {
+ className = className.nestedClass(typeSpecStack.get(i).name);
+ }
+ return className.nestedClass(simpleName);
+ }
+
+ /**
+ * Emits {@code s} with indentation as required. It's important that all code that writes to
+ * {@link #out} does it through here, since we emit indentation lazily in order to avoid
+ * unnecessary trailing whitespace.
+ */
+ CodeWriter emitAndIndent(String s) throws IOException {
+ boolean first = true;
+ for (String line : s.split("\n", -1)) {
+ // Emit a newline character. Make sure blank lines in Javadoc & comments look good.
+ if (!first) {
+ if ((javadoc || comment) && trailingNewline) {
+ emitIndentation();
+ out.append(javadoc ? " *" : "//");
+ }
+ out.append("\n");
+ trailingNewline = true;
+ if (statementLine != -1) {
+ if (statementLine == 0) {
+ indent(2); // Begin multiple-line statement. Increase the indentation level.
+ }
+ statementLine++;
+ }
+ }
+
+ first = false;
+ if (line.isEmpty()) continue; // Don't indent empty lines.
+
+ // Emit indentation and comment prefix if necessary.
+ if (trailingNewline) {
+ emitIndentation();
+ if (javadoc) {
+ out.append(" * ");
+ } else if (comment) {
+ out.append("// ");
+ }
+ }
+
+ out.append(line);
+ trailingNewline = false;
+ }
+ return this;
+ }
+
+ private void emitIndentation() throws IOException {
+ for (int j = 0; j < indentLevel; j++) {
+ out.append(indent);
+ }
+ }
+
+ /**
+ * Returns the types that should have been imported for this code. If there were any simple name
+ * collisions, that type's first use is imported.
+ */
+ Map<String, ClassName> suggestedImports() {
+ Map<String, ClassName> result = new LinkedHashMap<>(importableTypes);
+ result.keySet().removeAll(referencedNames);
+ return result;
+ }
+}
diff --git a/src/main/java/com/squareup/javapoet/FieldSpec.java b/src/main/java/com/squareup/javapoet/FieldSpec.java
new file mode 100644
index 0000000..851b36d
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/FieldSpec.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Modifier;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+import static com.squareup.javapoet.Util.checkState;
+
+/** A generated field declaration. */
+public final class FieldSpec {
+ public final TypeName type;
+ public final String name;
+ public final CodeBlock javadoc;
+ public final List<AnnotationSpec> annotations;
+ public final Set<Modifier> modifiers;
+ public final CodeBlock initializer;
+
+ private FieldSpec(Builder builder) {
+ this.type = checkNotNull(builder.type, "type == null");
+ this.name = checkNotNull(builder.name, "name == null");
+ this.javadoc = builder.javadoc.build();
+ this.annotations = Util.immutableList(builder.annotations);
+ this.modifiers = Util.immutableSet(builder.modifiers);
+ this.initializer = (builder.initializer == null)
+ ? CodeBlock.builder().build()
+ : builder.initializer;
+ }
+
+ public boolean hasModifier(Modifier modifier) {
+ return modifiers.contains(modifier);
+ }
+
+ void emit(CodeWriter codeWriter, Set<Modifier> implicitModifiers) throws IOException {
+ codeWriter.emitJavadoc(javadoc);
+ codeWriter.emitAnnotations(annotations, false);
+ codeWriter.emitModifiers(modifiers, implicitModifiers);
+ codeWriter.emit("$T $L", type, name);
+ if (!initializer.isEmpty()) {
+ codeWriter.emit(" = ");
+ codeWriter.emit(initializer);
+ }
+ codeWriter.emit(";\n");
+ }
+
+ @Override public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (getClass() != o.getClass()) return false;
+ return toString().equals(o.toString());
+ }
+
+ @Override public int hashCode() {
+ return toString().hashCode();
+ }
+
+ @Override public String toString() {
+ StringBuilder out = new StringBuilder();
+ try {
+ CodeWriter codeWriter = new CodeWriter(out);
+ emit(codeWriter, Collections.emptySet());
+ return out.toString();
+ } catch (IOException e) {
+ throw new AssertionError();
+ }
+ }
+
+ public static Builder builder(TypeName type, String name, Modifier... modifiers) {
+ checkNotNull(type, "type == null");
+ checkArgument(SourceVersion.isName(name), "not a valid name: %s", name);
+ return new Builder(type, name)
+ .addModifiers(modifiers);
+ }
+
+ public static Builder builder(Type type, String name, Modifier... modifiers) {
+ return builder(TypeName.get(type), name, modifiers);
+ }
+
+ public Builder toBuilder() {
+ Builder builder = new Builder(type, name);
+ builder.javadoc.add(javadoc);
+ builder.annotations.addAll(annotations);
+ builder.modifiers.addAll(modifiers);
+ builder.initializer = initializer.isEmpty() ? null : initializer;
+ return builder;
+ }
+
+ public static final class Builder {
+ private final TypeName type;
+ private final String name;
+
+ private final CodeBlock.Builder javadoc = CodeBlock.builder();
+ private final List<AnnotationSpec> annotations = new ArrayList<>();
+ private final List<Modifier> modifiers = new ArrayList<>();
+ private CodeBlock initializer = null;
+
+ private Builder(TypeName type, String name) {
+ this.type = type;
+ this.name = name;
+ }
+
+ public Builder addJavadoc(String format, Object... args) {
+ javadoc.add(format, args);
+ return this;
+ }
+
+ public Builder addJavadoc(CodeBlock block) {
+ javadoc.add(block);
+ return this;
+ }
+
+ public Builder addAnnotations(Iterable<AnnotationSpec> annotationSpecs) {
+ checkArgument(annotationSpecs != null, "annotationSpecs == null");
+ for (AnnotationSpec annotationSpec : annotationSpecs) {
+ this.annotations.add(annotationSpec);
+ }
+ return this;
+ }
+
+ public Builder addAnnotation(AnnotationSpec annotationSpec) {
+ this.annotations.add(annotationSpec);
+ return this;
+ }
+
+ public Builder addAnnotation(ClassName annotation) {
+ this.annotations.add(AnnotationSpec.builder(annotation).build());
+ return this;
+ }
+
+ public Builder addAnnotation(Class<?> annotation) {
+ return addAnnotation(ClassName.get(annotation));
+ }
+
+ public Builder addModifiers(Modifier... modifiers) {
+ Collections.addAll(this.modifiers, modifiers);
+ return this;
+ }
+
+ public Builder initializer(String format, Object... args) {
+ return initializer(CodeBlock.of(format, args));
+ }
+
+ public Builder initializer(CodeBlock codeBlock) {
+ checkState(this.initializer == null, "initializer was already set");
+ this.initializer = checkNotNull(codeBlock, "codeBlock == null");
+ return this;
+ }
+
+ public FieldSpec build() {
+ return new FieldSpec(this);
+ }
+ }
+}
diff --git a/src/main/java/com/squareup/javapoet/JavaFile.java b/src/main/java/com/squareup/javapoet/JavaFile.java
new file mode 100644
index 0000000..e7662dd
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/JavaFile.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.annotation.processing.Filer;
+import javax.lang.model.element.Element;
+import javax.tools.JavaFileObject;
+import javax.tools.JavaFileObject.Kind;
+import javax.tools.SimpleJavaFileObject;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/** A Java file containing a single top level class. */
+public final class JavaFile {
+ private static final Appendable NULL_APPENDABLE = new Appendable() {
+ @Override public Appendable append(CharSequence charSequence) {
+ return this;
+ }
+ @Override public Appendable append(CharSequence charSequence, int start, int end) {
+ return this;
+ }
+ @Override public Appendable append(char c) {
+ return this;
+ }
+ };
+
+ public final CodeBlock fileComment;
+ public final String packageName;
+ public final TypeSpec typeSpec;
+ public final boolean skipJavaLangImports;
+ private final Set<String> staticImports;
+ private final String indent;
+
+ private JavaFile(Builder builder) {
+ this.fileComment = builder.fileComment.build();
+ this.packageName = builder.packageName;
+ this.typeSpec = builder.typeSpec;
+ this.skipJavaLangImports = builder.skipJavaLangImports;
+ this.staticImports = Util.immutableSet(builder.staticImports);
+ this.indent = builder.indent;
+ }
+
+ public void writeTo(Appendable out) throws IOException {
+ // First pass: emit the entire class, just to collect the types we'll need to import.
+ CodeWriter importsCollector = new CodeWriter(NULL_APPENDABLE, indent, staticImports);
+ emit(importsCollector);
+ Map<String, ClassName> suggestedImports = importsCollector.suggestedImports();
+
+ // Second pass: write the code, taking advantage of the imports.
+ CodeWriter codeWriter = new CodeWriter(out, indent, suggestedImports, staticImports);
+ emit(codeWriter);
+ }
+
+ /** Writes this to {@code directory} as UTF-8 using the standard directory structure. */
+ public void writeTo(Path directory) throws IOException {
+ checkArgument(Files.notExists(directory) || Files.isDirectory(directory),
+ "path %s exists but is not a directory.", directory);
+ Path outputDirectory = directory;
+ if (!packageName.isEmpty()) {
+ for (String packageComponent : packageName.split("\\.")) {
+ outputDirectory = outputDirectory.resolve(packageComponent);
+ }
+ Files.createDirectories(outputDirectory);
+ }
+
+ Path outputPath = outputDirectory.resolve(typeSpec.name + ".java");
+ try (Writer writer = new OutputStreamWriter(Files.newOutputStream(outputPath), UTF_8)) {
+ writeTo(writer);
+ }
+ }
+
+ /** Writes this to {@code directory} as UTF-8 using the standard directory structure. */
+ public void writeTo(File directory) throws IOException {
+ writeTo(directory.toPath());
+ }
+
+ /** Writes this to {@code filer}. */
+ public void writeTo(Filer filer) throws IOException {
+ String fileName = packageName.isEmpty()
+ ? typeSpec.name
+ : packageName + "." + typeSpec.name;
+ List<Element> originatingElements = typeSpec.originatingElements;
+ JavaFileObject filerSourceFile = filer.createSourceFile(fileName,
+ originatingElements.toArray(new Element[originatingElements.size()]));
+ try (Writer writer = filerSourceFile.openWriter()) {
+ writeTo(writer);
+ } catch (Exception e) {
+ try {
+ filerSourceFile.delete();
+ } catch (Exception ignored) {
+ }
+ throw e;
+ }
+ }
+
+ private void emit(CodeWriter codeWriter) throws IOException {
+ codeWriter.pushPackage(packageName);
+
+ if (!fileComment.isEmpty()) {
+ codeWriter.emitComment(fileComment);
+ }
+
+ if (!packageName.isEmpty()) {
+ codeWriter.emit("package $L;\n", packageName);
+ codeWriter.emit("\n");
+ }
+
+ if (!staticImports.isEmpty()) {
+ for (String signature : staticImports) {
+ codeWriter.emit("import static $L;\n", signature);
+ }
+ codeWriter.emit("\n");
+ }
+
+ int importedTypesCount = 0;
+ for (ClassName className : new TreeSet<>(codeWriter.importedTypes().values())) {
+ if (skipJavaLangImports && className.packageName().equals("java.lang")) continue;
+ codeWriter.emit("import $L;\n", className.withoutAnnotations());
+ importedTypesCount++;
+ }
+
+ if (importedTypesCount > 0) {
+ codeWriter.emit("\n");
+ }
+
+ typeSpec.emit(codeWriter, null, Collections.emptySet());
+
+ codeWriter.popPackage();
+ }
+
+ @Override public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (getClass() != o.getClass()) return false;
+ return toString().equals(o.toString());
+ }
+
+ @Override public int hashCode() {
+ return toString().hashCode();
+ }
+
+ @Override public String toString() {
+ try {
+ StringBuilder result = new StringBuilder();
+ writeTo(result);
+ return result.toString();
+ } catch (IOException e) {
+ throw new AssertionError();
+ }
+ }
+
+ public JavaFileObject toJavaFileObject() {
+ URI uri = URI.create((packageName.isEmpty()
+ ? typeSpec.name
+ : packageName.replace('.', '/') + '/' + typeSpec.name)
+ + Kind.SOURCE.extension);
+ return new SimpleJavaFileObject(uri, Kind.SOURCE) {
+ private final long lastModified = System.currentTimeMillis();
+ @Override public String getCharContent(boolean ignoreEncodingErrors) {
+ return JavaFile.this.toString();
+ }
+ @Override public InputStream openInputStream() throws IOException {
+ return new ByteArrayInputStream(getCharContent(true).getBytes(UTF_8));
+ }
+ @Override public long getLastModified() {
+ return lastModified;
+ }
+ };
+ }
+
+ public static Builder builder(String packageName, TypeSpec typeSpec) {
+ checkNotNull(packageName, "packageName == null");
+ checkNotNull(typeSpec, "typeSpec == null");
+ return new Builder(packageName, typeSpec);
+ }
+
+ public Builder toBuilder() {
+ Builder builder = new Builder(packageName, typeSpec);
+ builder.fileComment.add(fileComment);
+ builder.skipJavaLangImports = skipJavaLangImports;
+ builder.indent = indent;
+ return builder;
+ }
+
+ public static final class Builder {
+ private final String packageName;
+ private final TypeSpec typeSpec;
+ private final CodeBlock.Builder fileComment = CodeBlock.builder();
+ private final Set<String> staticImports = new TreeSet<>();
+ private boolean skipJavaLangImports;
+ private String indent = " ";
+
+ private Builder(String packageName, TypeSpec typeSpec) {
+ this.packageName = packageName;
+ this.typeSpec = typeSpec;
+ }
+
+ public Builder addFileComment(String format, Object... args) {
+ this.fileComment.add(format, args);
+ return this;
+ }
+
+ public Builder addStaticImport(Enum<?> constant) {
+ return addStaticImport(ClassName.get(constant.getDeclaringClass()), constant.name());
+ }
+
+ public Builder addStaticImport(Class<?> clazz, String... names) {
+ return addStaticImport(ClassName.get(clazz), names);
+ }
+
+ public Builder addStaticImport(ClassName className, String... names) {
+ checkArgument(className != null, "className == null");
+ checkArgument(names != null, "names == null");
+ checkArgument(names.length > 0, "names array is empty");
+ for (String name : names) {
+ checkArgument(name != null, "null entry in names array: %s", Arrays.toString(names));
+ staticImports.add(className.canonicalName + "." + name);
+ }
+ return this;
+ }
+
+ /**
+ * Call this to omit imports for classes in {@code java.lang}, such as {@code java.lang.String}.
+ *
+ * <p>By default, JavaPoet explicitly imports types in {@code java.lang} to defend against
+ * naming conflicts. Suppose an (ill-advised) class is named {@code com.example.String}. When
+ * {@code java.lang} imports are skipped, generated code in {@code com.example} that references
+ * {@code java.lang.String} will get {@code com.example.String} instead.
+ */
+ public Builder skipJavaLangImports(boolean skipJavaLangImports) {
+ this.skipJavaLangImports = skipJavaLangImports;
+ return this;
+ }
+
+ public Builder indent(String indent) {
+ this.indent = indent;
+ return this;
+ }
+
+ public JavaFile build() {
+ return new JavaFile(this);
+ }
+ }
+}
diff --git a/src/main/java/com/squareup/javapoet/LineWrapper.java b/src/main/java/com/squareup/javapoet/LineWrapper.java
new file mode 100644
index 0000000..6aa3131
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/LineWrapper.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+
+import static com.squareup.javapoet.Util.checkNotNull;
+
+/**
+ * Implements soft line wrapping on an appendable. To use, append characters using {@link #append}
+ * or soft-wrapping spaces using {@link #wrappingSpace}.
+ */
+final class LineWrapper {
+ private final Appendable out;
+ private final String indent;
+ private final int columnLimit;
+ private boolean closed;
+
+ /** Characters written since the last wrapping space that haven't yet been flushed. */
+ private final StringBuilder buffer = new StringBuilder();
+
+ /** The number of characters since the most recent newline. Includes both out and the buffer. */
+ private int column = 0;
+
+ /**
+ * -1 if we have no buffering; otherwise the number of {@code indent}s to write after wrapping.
+ */
+ private int indentLevel = -1;
+
+ /**
+ * Null if we have no buffering; otherwise the type to pass to the next call to {@link #flush}.
+ */
+ private FlushType nextFlush;
+
+ LineWrapper(Appendable out, String indent, int columnLimit) {
+ checkNotNull(out, "out == null");
+ this.out = out;
+ this.indent = indent;
+ this.columnLimit = columnLimit;
+ }
+
+ /** Emit {@code s}. This may be buffered to permit line wraps to be inserted. */
+ void append(String s) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+
+ if (nextFlush != null) {
+ int nextNewline = s.indexOf('\n');
+
+ // If s doesn't cause the current line to cross the limit, buffer it and return. We'll decide
+ // whether or not we have to wrap it later.
+ if (nextNewline == -1 && column + s.length() <= columnLimit) {
+ buffer.append(s);
+ column += s.length();
+ return;
+ }
+
+ // Wrap if appending s would overflow the current line.
+ boolean wrap = nextNewline == -1 || column + nextNewline > columnLimit;
+ flush(wrap ? FlushType.WRAP : nextFlush);
+ }
+
+ out.append(s);
+ int lastNewline = s.lastIndexOf('\n');
+ column = lastNewline != -1
+ ? s.length() - lastNewline - 1
+ : column + s.length();
+ }
+
+ /** Emit either a space or a newline character. */
+ void wrappingSpace(int indentLevel) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+
+ if (this.nextFlush != null) flush(nextFlush);
+ column++; // Increment the column even though the space is deferred to next call to flush().
+ this.nextFlush = FlushType.SPACE;
+ this.indentLevel = indentLevel;
+ }
+
+ /** Emit a newline character if the line will exceed it's limit, otherwise do nothing. */
+ void zeroWidthSpace(int indentLevel) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+
+ if (column == 0) return;
+ if (this.nextFlush != null) flush(nextFlush);
+ this.nextFlush = FlushType.EMPTY;
+ this.indentLevel = indentLevel;
+ }
+
+ /** Flush any outstanding text and forbid future writes to this line wrapper. */
+ void close() throws IOException {
+ if (nextFlush != null) flush(nextFlush);
+ closed = true;
+ }
+
+ /** Write the space followed by any buffered text that follows it. */
+ private void flush(FlushType flushType) throws IOException {
+ switch (flushType) {
+ case WRAP:
+ out.append('\n');
+ for (int i = 0; i < indentLevel; i++) {
+ out.append(indent);
+ }
+ column = indentLevel * indent.length();
+ column += buffer.length();
+ break;
+ case SPACE:
+ out.append(' ');
+ break;
+ case EMPTY:
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown FlushType: " + flushType);
+ }
+
+ out.append(buffer);
+ buffer.delete(0, buffer.length());
+ indentLevel = -1;
+ nextFlush = null;
+ }
+
+ private enum FlushType {
+ WRAP, SPACE, EMPTY;
+ }
+}
diff --git a/src/main/java/com/squareup/javapoet/MethodSpec.java b/src/main/java/com/squareup/javapoet/MethodSpec.java
new file mode 100644
index 0000000..a2c7c43
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/MethodSpec.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.util.Types;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+import static com.squareup.javapoet.Util.checkState;
+
+/** A generated constructor or method declaration. */
+public final class MethodSpec {
+ static final String CONSTRUCTOR = "<init>";
+
+ public final String name;
+ public final CodeBlock javadoc;
+ public final List<AnnotationSpec> annotations;
+ public final Set<Modifier> modifiers;
+ public final List<TypeVariableName> typeVariables;
+ public final TypeName returnType;
+ public final List<ParameterSpec> parameters;
+ public final boolean varargs;
+ public final List<TypeName> exceptions;
+ public final CodeBlock code;
+ public final CodeBlock defaultValue;
+
+ private MethodSpec(Builder builder) {
+ CodeBlock code = builder.code.build();
+ checkArgument(code.isEmpty() || !builder.modifiers.contains(Modifier.ABSTRACT),
+ "abstract method %s cannot have code", builder.name);
+ checkArgument(!builder.varargs || lastParameterIsArray(builder.parameters),
+ "last parameter of varargs method %s must be an array", builder.name);
+
+ this.name = checkNotNull(builder.name, "name == null");
+ this.javadoc = builder.javadoc.build();
+ this.annotations = Util.immutableList(builder.annotations);
+ this.modifiers = Util.immutableSet(builder.modifiers);
+ this.typeVariables = Util.immutableList(builder.typeVariables);
+ this.returnType = builder.returnType;
+ this.parameters = Util.immutableList(builder.parameters);
+ this.varargs = builder.varargs;
+ this.exceptions = Util.immutableList(builder.exceptions);
+ this.defaultValue = builder.defaultValue;
+ this.code = code;
+ }
+
+ private boolean lastParameterIsArray(List<ParameterSpec> parameters) {
+ return !parameters.isEmpty()
+ && TypeName.asArray((parameters.get(parameters.size() - 1).type)) != null;
+ }
+
+ void emit(CodeWriter codeWriter, String enclosingName, Set<Modifier> implicitModifiers)
+ throws IOException {
+ codeWriter.emitJavadoc(javadoc);
+ codeWriter.emitAnnotations(annotations, false);
+ codeWriter.emitModifiers(modifiers, implicitModifiers);
+
+ if (!typeVariables.isEmpty()) {
+ codeWriter.emitTypeVariables(typeVariables);
+ codeWriter.emit(" ");
+ }
+
+ if (isConstructor()) {
+ codeWriter.emit("$L($Z", enclosingName);
+ } else {
+ codeWriter.emit("$T $L($Z", returnType, name);
+ }
+
+ boolean firstParameter = true;
+ for (Iterator<ParameterSpec> i = parameters.iterator(); i.hasNext(); ) {
+ ParameterSpec parameter = i.next();
+ if (!firstParameter) codeWriter.emit(",").emitWrappingSpace();
+ parameter.emit(codeWriter, !i.hasNext() && varargs);
+ firstParameter = false;
+ }
+
+ codeWriter.emit(")");
+
+ if (defaultValue != null && !defaultValue.isEmpty()) {
+ codeWriter.emit(" default ");
+ codeWriter.emit(defaultValue);
+ }
+
+ if (!exceptions.isEmpty()) {
+ codeWriter.emitWrappingSpace().emit("throws");
+ boolean firstException = true;
+ for (TypeName exception : exceptions) {
+ if (!firstException) codeWriter.emit(",");
+ codeWriter.emitWrappingSpace().emit("$T", exception);
+ firstException = false;
+ }
+ }
+
+ if (hasModifier(Modifier.ABSTRACT)) {
+ codeWriter.emit(";\n");
+ } else if (hasModifier(Modifier.NATIVE)) {
+ // Code is allowed to support stuff like GWT JSNI.
+ codeWriter.emit(code);
+ codeWriter.emit(";\n");
+ } else {
+ codeWriter.emit(" {\n");
+
+ codeWriter.indent();
+ codeWriter.emit(code);
+ codeWriter.unindent();
+
+ codeWriter.emit("}\n");
+ }
+ }
+
+ public boolean hasModifier(Modifier modifier) {
+ return modifiers.contains(modifier);
+ }
+
+ public boolean isConstructor() {
+ return name.equals(CONSTRUCTOR);
+ }
+
+ @Override public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (getClass() != o.getClass()) return false;
+ return toString().equals(o.toString());
+ }
+
+ @Override public int hashCode() {
+ return toString().hashCode();
+ }
+
+ @Override public String toString() {
+ StringBuilder out = new StringBuilder();
+ try {
+ CodeWriter codeWriter = new CodeWriter(out);
+ emit(codeWriter, "Constructor", Collections.emptySet());
+ return out.toString();
+ } catch (IOException e) {
+ throw new AssertionError();
+ }
+ }
+
+ public static Builder methodBuilder(String name) {
+ return new Builder(name);
+ }
+
+ public static Builder constructorBuilder() {
+ return new Builder(CONSTRUCTOR);
+ }
+
+ /**
+ * Returns a new method spec builder that overrides {@code method}.
+ *
+ * <p>This will copy its visibility modifiers, type parameters, return type, name, parameters, and
+ * throws declarations. An {@link Override} annotation will be added.
+ *
+ * <p>Note that in JavaPoet 1.2 through 1.7 this method retained annotations from the method and
+ * parameters of the overridden method. Since JavaPoet 1.8 annotations must be added separately.
+ */
+ public static Builder overriding(ExecutableElement method) {
+ checkNotNull(method, "method == null");
+
+ Element enclosingClass = method.getEnclosingElement();
+ if (enclosingClass.getModifiers().contains(Modifier.FINAL)) {
+ throw new IllegalArgumentException("Cannot override method on final class " + enclosingClass);
+ }
+
+ Set<Modifier> modifiers = method.getModifiers();
+ if (modifiers.contains(Modifier.PRIVATE)
+ || modifiers.contains(Modifier.FINAL)
+ || modifiers.contains(Modifier.STATIC)) {
+ throw new IllegalArgumentException("cannot override method with modifiers: " + modifiers);
+ }
+
+ String methodName = method.getSimpleName().toString();
+ MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(methodName);
+
+ methodBuilder.addAnnotation(Override.class);
+
+ modifiers = new LinkedHashSet<>(modifiers);
+ modifiers.remove(Modifier.ABSTRACT);
+ modifiers.remove(Modifier.DEFAULT);
+ methodBuilder.addModifiers(modifiers);
+
+ for (TypeParameterElement typeParameterElement : method.getTypeParameters()) {
+ TypeVariable var = (TypeVariable) typeParameterElement.asType();
+ methodBuilder.addTypeVariable(TypeVariableName.get(var));
+ }
+
+ methodBuilder.returns(TypeName.get(method.getReturnType()));
+ methodBuilder.addParameters(ParameterSpec.parametersOf(method));
+ methodBuilder.varargs(method.isVarArgs());
+
+ for (TypeMirror thrownType : method.getThrownTypes()) {
+ methodBuilder.addException(TypeName.get(thrownType));
+ }
+
+ return methodBuilder;
+ }
+
+ /**
+ * Returns a new method spec builder that overrides {@code method} as a member of {@code
+ * enclosing}. This will resolve type parameters: for example overriding {@link
+ * Comparable#compareTo} in a type that implements {@code Comparable<Movie>}, the {@code T}
+ * parameter will be resolved to {@code Movie}.
+ *
+ * <p>This will copy its visibility modifiers, type parameters, return type, name, parameters, and
+ * throws declarations. An {@link Override} annotation will be added.
+ *
+ * <p>Note that in JavaPoet 1.2 through 1.7 this method retained annotations from the method and
+ * parameters of the overridden method. Since JavaPoet 1.8 annotations must be added separately.
+ */
+ public static Builder overriding(
+ ExecutableElement method, DeclaredType enclosing, Types types) {
+ ExecutableType executableType = (ExecutableType) types.asMemberOf(enclosing, method);
+ List<? extends TypeMirror> resolvedParameterTypes = executableType.getParameterTypes();
+ List<? extends TypeMirror> resolvedThrownTypes = executableType.getThrownTypes();
+ TypeMirror resolvedReturnType = executableType.getReturnType();
+
+ Builder builder = overriding(method);
+ builder.returns(TypeName.get(resolvedReturnType));
+ for (int i = 0, size = builder.parameters.size(); i < size; i++) {
+ ParameterSpec parameter = builder.parameters.get(i);
+ TypeName type = TypeName.get(resolvedParameterTypes.get(i));
+ builder.parameters.set(i, parameter.toBuilder(type, parameter.name).build());
+ }
+ builder.exceptions.clear();
+ for (int i = 0, size = resolvedThrownTypes.size(); i < size; i++) {
+ builder.addException(TypeName.get(resolvedThrownTypes.get(i)));
+ }
+
+ return builder;
+ }
+
+ public Builder toBuilder() {
+ Builder builder = new Builder(name);
+ builder.javadoc.add(javadoc);
+ builder.annotations.addAll(annotations);
+ builder.modifiers.addAll(modifiers);
+ builder.typeVariables.addAll(typeVariables);
+ builder.returnType = returnType;
+ builder.parameters.addAll(parameters);
+ builder.exceptions.addAll(exceptions);
+ builder.code.add(code);
+ builder.varargs = varargs;
+ builder.defaultValue = defaultValue;
+ return builder;
+ }
+
+ public static final class Builder {
+ private final String name;
+
+ private final CodeBlock.Builder javadoc = CodeBlock.builder();
+ private final List<AnnotationSpec> annotations = new ArrayList<>();
+ private final List<Modifier> modifiers = new ArrayList<>();
+ private List<TypeVariableName> typeVariables = new ArrayList<>();
+ private TypeName returnType;
+ private final List<ParameterSpec> parameters = new ArrayList<>();
+ private final Set<TypeName> exceptions = new LinkedHashSet<>();
+ private final CodeBlock.Builder code = CodeBlock.builder();
+ private boolean varargs;
+ private CodeBlock defaultValue;
+
+ private Builder(String name) {
+ checkNotNull(name, "name == null");
+ checkArgument(name.equals(CONSTRUCTOR) || SourceVersion.isName(name),
+ "not a valid name: %s", name);
+ this.name = name;
+ this.returnType = name.equals(CONSTRUCTOR) ? null : TypeName.VOID;
+ }
+
+ public Builder addJavadoc(String format, Object... args) {
+ javadoc.add(format, args);
+ return this;
+ }
+
+ public Builder addJavadoc(CodeBlock block) {
+ javadoc.add(block);
+ return this;
+ }
+
+ public Builder addAnnotations(Iterable<AnnotationSpec> annotationSpecs) {
+ checkArgument(annotationSpecs != null, "annotationSpecs == null");
+ for (AnnotationSpec annotationSpec : annotationSpecs) {
+ this.annotations.add(annotationSpec);
+ }
+ return this;
+ }
+
+ public Builder addAnnotation(AnnotationSpec annotationSpec) {
+ this.annotations.add(annotationSpec);
+ return this;
+ }
+
+ public Builder addAnnotation(ClassName annotation) {
+ this.annotations.add(AnnotationSpec.builder(annotation).build());
+ return this;
+ }
+
+ public Builder addAnnotation(Class<?> annotation) {
+ return addAnnotation(ClassName.get(annotation));
+ }
+
+ public Builder addModifiers(Modifier... modifiers) {
+ checkNotNull(modifiers, "modifiers == null");
+ Collections.addAll(this.modifiers, modifiers);
+ return this;
+ }
+
+ public Builder addModifiers(Iterable<Modifier> modifiers) {
+ checkNotNull(modifiers, "modifiers == null");
+ for (Modifier modifier : modifiers) {
+ this.modifiers.add(modifier);
+ }
+ return this;
+ }
+
+ public Builder addTypeVariables(Iterable<TypeVariableName> typeVariables) {
+ checkArgument(typeVariables != null, "typeVariables == null");
+ for (TypeVariableName typeVariable : typeVariables) {
+ this.typeVariables.add(typeVariable);
+ }
+ return this;
+ }
+
+ public Builder addTypeVariable(TypeVariableName typeVariable) {
+ typeVariables.add(typeVariable);
+ return this;
+ }
+
+ public Builder returns(TypeName returnType) {
+ checkState(!name.equals(CONSTRUCTOR), "constructor cannot have return type.");
+ this.returnType = returnType;
+ return this;
+ }
+
+ public Builder returns(Type returnType) {
+ return returns(TypeName.get(returnType));
+ }
+
+ public Builder addParameters(Iterable<ParameterSpec> parameterSpecs) {
+ checkArgument(parameterSpecs != null, "parameterSpecs == null");
+ for (ParameterSpec parameterSpec : parameterSpecs) {
+ this.parameters.add(parameterSpec);
+ }
+ return this;
+ }
+
+ public Builder addParameter(ParameterSpec parameterSpec) {
+ this.parameters.add(parameterSpec);
+ return this;
+ }
+
+ public Builder addParameter(TypeName type, String name, Modifier... modifiers) {
+ return addParameter(ParameterSpec.builder(type, name, modifiers).build());
+ }
+
+ public Builder addParameter(Type type, String name, Modifier... modifiers) {
+ return addParameter(TypeName.get(type), name, modifiers);
+ }
+
+ public Builder varargs() {
+ return varargs(true);
+ }
+
+ public Builder varargs(boolean varargs) {
+ this.varargs = varargs;
+ return this;
+ }
+
+ public Builder addExceptions(Iterable<? extends TypeName> exceptions) {
+ checkArgument(exceptions != null, "exceptions == null");
+ for (TypeName exception : exceptions) {
+ this.exceptions.add(exception);
+ }
+ return this;
+ }
+
+ public Builder addException(TypeName exception) {
+ this.exceptions.add(exception);
+ return this;
+ }
+
+ public Builder addException(Type exception) {
+ return addException(TypeName.get(exception));
+ }
+
+ public Builder addCode(String format, Object... args) {
+ code.add(format, args);
+ return this;
+ }
+
+ public Builder addNamedCode(String format, Map<String, ?> args) {
+ code.addNamed(format, args);
+ return this;
+ }
+
+ public Builder addCode(CodeBlock codeBlock) {
+ code.add(codeBlock);
+ return this;
+ }
+
+ public Builder addComment(String format, Object... args) {
+ code.add("// " + format + "\n", args);
+ return this;
+ }
+
+ public Builder defaultValue(String format, Object... args) {
+ return defaultValue(CodeBlock.of(format, args));
+ }
+
+ public Builder defaultValue(CodeBlock codeBlock) {
+ checkState(this.defaultValue == null, "defaultValue was already set");
+ this.defaultValue = checkNotNull(codeBlock, "codeBlock == null");
+ return this;
+ }
+
+ /**
+ * @param controlFlow the control flow construct and its code, such as "if (foo == 5)".
+ * Shouldn't contain braces or newline characters.
+ */
+ public Builder beginControlFlow(String controlFlow, Object... args) {
+ code.beginControlFlow(controlFlow, args);
+ return this;
+ }
+
+ /**
+ * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
+ * Shouldn't contain braces or newline characters.
+ */
+ public Builder nextControlFlow(String controlFlow, Object... args) {
+ code.nextControlFlow(controlFlow, args);
+ return this;
+ }
+
+ public Builder endControlFlow() {
+ code.endControlFlow();
+ return this;
+ }
+
+ /**
+ * @param controlFlow the optional control flow construct and its code, such as
+ * "while(foo == 20)". Only used for "do/while" control flows.
+ */
+ public Builder endControlFlow(String controlFlow, Object... args) {
+ code.endControlFlow(controlFlow, args);
+ return this;
+ }
+
+ public Builder addStatement(String format, Object... args) {
+ code.addStatement(format, args);
+ return this;
+ }
+
+ public Builder addStatement(CodeBlock codeBlock) {
+ code.addStatement(codeBlock);
+ return this;
+ }
+
+ public MethodSpec build() {
+ return new MethodSpec(this);
+ }
+ }
+}
diff --git a/src/main/java/com/squareup/javapoet/NameAllocator.java b/src/main/java/com/squareup/javapoet/NameAllocator.java
new file mode 100644
index 0000000..8269664
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/NameAllocator.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import javax.lang.model.SourceVersion;
+
+import static com.squareup.javapoet.Util.checkNotNull;
+
+/**
+ * Assigns Java identifier names to avoid collisions, keywords, and invalid characters. To use,
+ * first create an instance and allocate all of the names that you need. Typically this is a
+ * mix of user-supplied names and constants: <pre> {@code
+ *
+ * NameAllocator nameAllocator = new NameAllocator();
+ * for (MyProperty property : properties) {
+ * nameAllocator.newName(property.name(), property);
+ * }
+ * nameAllocator.newName("sb", "string builder");
+ * }</pre>
+ *
+ * Pass a unique tag object to each allocation. The tag scopes the name, and can be used to look up
+ * the allocated name later. Typically the tag is the object that is being named. In the above
+ * example we use {@code property} for the user-supplied property names, and {@code "string
+ * builder"} for our constant string builder.
+ *
+ * <p>Once we've allocated names we can use them when generating code: <pre> {@code
+ *
+ * MethodSpec.Builder builder = MethodSpec.methodBuilder("toString")
+ * .addAnnotation(Override.class)
+ * .addModifiers(Modifier.PUBLIC)
+ * .returns(String.class);
+ *
+ * builder.addStatement("$1T $2N = new $1T()",
+ * StringBuilder.class, nameAllocator.get("string builder"));
+ * for (MyProperty property : properties) {
+ * builder.addStatement("$N.append($N)",
+ * nameAllocator.get("string builder"), nameAllocator.get(property));
+ * }
+ * builder.addStatement("return $N", nameAllocator.get("string builder"));
+ * return builder.build();
+ * }</pre>
+ *
+ * The above code generates unique names if presented with conflicts. Given user-supplied properties
+ * with names {@code ab} and {@code sb} this generates the following: <pre> {@code
+ *
+ * &#64;Override
+ * public String toString() {
+ * StringBuilder sb_ = new StringBuilder();
+ * sb_.append(ab);
+ * sb_.append(sb);
+ * return sb_.toString();
+ * }
+ * }</pre>
+ *
+ * The underscore is appended to {@code sb} to avoid conflicting with the user-supplied {@code sb}
+ * property. Underscores are also prefixed for names that start with a digit, and used to replace
+ * name-unsafe characters like space or dash.
+ *
+ * <p>When dealing with multiple independent inner scopes, use a {@link #clone()} of the
+ * NameAllocator used for the outer scope to further refine name allocation for a specific inner
+ * scope.
+ */
+public final class NameAllocator implements Cloneable {
+ private final Set<String> allocatedNames;
+ private final Map<Object, String> tagToName;
+
+ public NameAllocator() {
+ this(new LinkedHashSet<>(), new LinkedHashMap<>());
+ }
+
+ private NameAllocator(LinkedHashSet<String> allocatedNames,
+ LinkedHashMap<Object, String> tagToName) {
+ this.allocatedNames = allocatedNames;
+ this.tagToName = tagToName;
+ }
+
+ /**
+ * Return a new name using {@code suggestion} that will not be a Java identifier or clash with
+ * other names.
+ */
+ public String newName(String suggestion) {
+ return newName(suggestion, UUID.randomUUID().toString());
+ }
+
+ /**
+ * Return a new name using {@code suggestion} that will not be a Java identifier or clash with
+ * other names. The returned value can be queried multiple times by passing {@code tag} to
+ * {@link #get(Object)}.
+ */
+ public String newName(String suggestion, Object tag) {
+ checkNotNull(suggestion, "suggestion");
+ checkNotNull(tag, "tag");
+
+ suggestion = toJavaIdentifier(suggestion);
+
+ while (SourceVersion.isKeyword(suggestion) || !allocatedNames.add(suggestion)) {
+ suggestion = suggestion + "_";
+ }
+
+ String replaced = tagToName.put(tag, suggestion);
+ if (replaced != null) {
+ tagToName.put(tag, replaced); // Put things back as they were!
+ throw new IllegalArgumentException("tag " + tag + " cannot be used for both '" + replaced
+ + "' and '" + suggestion + "'");
+ }
+
+ return suggestion;
+ }
+
+ public static String toJavaIdentifier(String suggestion) {
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < suggestion.length(); ) {
+ int codePoint = suggestion.codePointAt(i);
+ if (i == 0
+ && !Character.isJavaIdentifierStart(codePoint)
+ && Character.isJavaIdentifierPart(codePoint)) {
+ result.append("_");
+ }
+
+ int validCodePoint = Character.isJavaIdentifierPart(codePoint) ? codePoint : '_';
+ result.appendCodePoint(validCodePoint);
+ i += Character.charCount(codePoint);
+ }
+ return result.toString();
+ }
+
+ /** Retrieve a name created with {@link #newName(String, Object)}. */
+ public String get(Object tag) {
+ String result = tagToName.get(tag);
+ if (result == null) {
+ throw new IllegalArgumentException("unknown tag: " + tag);
+ }
+ return result;
+ }
+
+ /**
+ * Create a deep copy of this NameAllocator. Useful to create multiple independent refinements
+ * of a NameAllocator to be used in the respective definition of multiples, independently-scoped,
+ * inner code blocks.
+ *
+ * @return A deep copy of this NameAllocator.
+ */
+ @Override
+ public NameAllocator clone() {
+ return new NameAllocator(
+ new LinkedHashSet<>(this.allocatedNames),
+ new LinkedHashMap<>(this.tagToName));
+ }
+
+}
diff --git a/src/main/java/com/squareup/javapoet/ParameterSpec.java b/src/main/java/com/squareup/javapoet/ParameterSpec.java
new file mode 100644
index 0000000..63da3f2
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/ParameterSpec.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.VariableElement;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+
+/** A generated parameter declaration. */
+public final class ParameterSpec {
+ public final String name;
+ public final List<AnnotationSpec> annotations;
+ public final Set<Modifier> modifiers;
+ public final TypeName type;
+
+ private ParameterSpec(Builder builder) {
+ this.name = checkNotNull(builder.name, "name == null");
+ this.annotations = Util.immutableList(builder.annotations);
+ this.modifiers = Util.immutableSet(builder.modifiers);
+ this.type = checkNotNull(builder.type, "type == null");
+ }
+
+ public boolean hasModifier(Modifier modifier) {
+ return modifiers.contains(modifier);
+ }
+
+ void emit(CodeWriter codeWriter, boolean varargs) throws IOException {
+ codeWriter.emitAnnotations(annotations, true);
+ codeWriter.emitModifiers(modifiers);
+ if (varargs) {
+ TypeName.asArray(type).emit(codeWriter, true);
+ } else {
+ type.emit(codeWriter);
+ }
+ codeWriter.emit(" $L", name);
+ }
+
+ @Override public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (getClass() != o.getClass()) return false;
+ return toString().equals(o.toString());
+ }
+
+ @Override public int hashCode() {
+ return toString().hashCode();
+ }
+
+ @Override public String toString() {
+ StringBuilder out = new StringBuilder();
+ try {
+ CodeWriter codeWriter = new CodeWriter(out);
+ emit(codeWriter, false);
+ return out.toString();
+ } catch (IOException e) {
+ throw new AssertionError();
+ }
+ }
+
+ public static ParameterSpec get(VariableElement element) {
+ TypeName type = TypeName.get(element.asType());
+ String name = element.getSimpleName().toString();
+ return ParameterSpec.builder(type, name)
+ .addModifiers(element.getModifiers())
+ .build();
+ }
+
+ static List<ParameterSpec> parametersOf(ExecutableElement method) {
+ List<ParameterSpec> result = new ArrayList<>();
+ for (VariableElement parameter : method.getParameters()) {
+ result.add(ParameterSpec.get(parameter));
+ }
+ return result;
+ }
+
+ public static Builder builder(TypeName type, String name, Modifier... modifiers) {
+ checkNotNull(type, "type == null");
+ checkArgument(SourceVersion.isName(name), "not a valid name: %s", name);
+ return new Builder(type, name)
+ .addModifiers(modifiers);
+ }
+
+ public static Builder builder(Type type, String name, Modifier... modifiers) {
+ return builder(TypeName.get(type), name, modifiers);
+ }
+
+ public Builder toBuilder() {
+ return toBuilder(type, name);
+ }
+
+ Builder toBuilder(TypeName type, String name) {
+ Builder builder = new Builder(type, name);
+ builder.annotations.addAll(annotations);
+ builder.modifiers.addAll(modifiers);
+ return builder;
+ }
+
+ public static final class Builder {
+ private final TypeName type;
+ private final String name;
+
+ private final List<AnnotationSpec> annotations = new ArrayList<>();
+ private final List<Modifier> modifiers = new ArrayList<>();
+
+ private Builder(TypeName type, String name) {
+ this.type = type;
+ this.name = name;
+ }
+
+ public Builder addAnnotations(Iterable<AnnotationSpec> annotationSpecs) {
+ checkArgument(annotationSpecs != null, "annotationSpecs == null");
+ for (AnnotationSpec annotationSpec : annotationSpecs) {
+ this.annotations.add(annotationSpec);
+ }
+ return this;
+ }
+
+ public Builder addAnnotation(AnnotationSpec annotationSpec) {
+ this.annotations.add(annotationSpec);
+ return this;
+ }
+
+ public Builder addAnnotation(ClassName annotation) {
+ this.annotations.add(AnnotationSpec.builder(annotation).build());
+ return this;
+ }
+
+ public Builder addAnnotation(Class<?> annotation) {
+ return addAnnotation(ClassName.get(annotation));
+ }
+
+ public Builder addModifiers(Modifier... modifiers) {
+ Collections.addAll(this.modifiers, modifiers);
+ return this;
+ }
+
+ public Builder addModifiers(Iterable<Modifier> modifiers) {
+ checkNotNull(modifiers, "modifiers == null");
+ for (Modifier modifier : modifiers) {
+ this.modifiers.add(modifier);
+ }
+ return this;
+ }
+
+ public ParameterSpec build() {
+ return new ParameterSpec(this);
+ }
+ }
+}
diff --git a/src/main/java/com/squareup/javapoet/ParameterizedTypeName.java b/src/main/java/com/squareup/javapoet/ParameterizedTypeName.java
new file mode 100644
index 0000000..3a8bf62
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/ParameterizedTypeName.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+
+public final class ParameterizedTypeName extends TypeName {
+ private final ParameterizedTypeName enclosingType;
+ public final ClassName rawType;
+ public final List<TypeName> typeArguments;
+
+ ParameterizedTypeName(ParameterizedTypeName enclosingType, ClassName rawType,
+ List<TypeName> typeArguments) {
+ this(enclosingType, rawType, typeArguments, new ArrayList<>());
+ }
+
+ private ParameterizedTypeName(ParameterizedTypeName enclosingType, ClassName rawType,
+ List<TypeName> typeArguments, List<AnnotationSpec> annotations) {
+ super(annotations);
+ this.rawType = checkNotNull(rawType, "rawType == null").annotated(annotations);
+ this.enclosingType = enclosingType;
+ this.typeArguments = Util.immutableList(typeArguments);
+
+ checkArgument(!this.typeArguments.isEmpty() || enclosingType != null,
+ "no type arguments: %s", rawType);
+ for (TypeName typeArgument : this.typeArguments) {
+ checkArgument(!typeArgument.isPrimitive() && typeArgument != VOID,
+ "invalid type parameter: %s", typeArgument);
+ }
+ }
+
+ @Override public ParameterizedTypeName annotated(List<AnnotationSpec> annotations) {
+ return new ParameterizedTypeName(
+ enclosingType, rawType, typeArguments, concatAnnotations(annotations));
+ }
+
+ @Override
+ public TypeName withoutAnnotations() {
+ return new ParameterizedTypeName(
+ enclosingType, rawType.withoutAnnotations(), typeArguments, new ArrayList<>());
+ }
+
+ @Override CodeWriter emit(CodeWriter out) throws IOException {
+ if (enclosingType != null) {
+ enclosingType.emit(out);
+ out.emit(".");
+ if (isAnnotated()) {
+ out.emit(" ");
+ emitAnnotations(out);
+ }
+ out.emit(rawType.simpleName());
+ } else {
+ rawType.emit(out);
+ }
+ if (!typeArguments.isEmpty()) {
+ out.emitAndIndent("<");
+ boolean firstParameter = true;
+ for (TypeName parameter : typeArguments) {
+ if (!firstParameter) out.emitAndIndent(", ");
+ parameter.emit(out);
+ firstParameter = false;
+ }
+ out.emitAndIndent(">");
+ }
+ return out;
+ }
+
+ /**
+ * Returns a new {@link ParameterizedTypeName} instance for the specified {@code name} as nested
+ * inside this class.
+ */
+ public ParameterizedTypeName nestedClass(String name) {
+ checkNotNull(name, "name == null");
+ return new ParameterizedTypeName(this, rawType.nestedClass(name), new ArrayList<>(),
+ new ArrayList<>());
+ }
+
+ /**
+ * Returns a new {@link ParameterizedTypeName} instance for the specified {@code name} as nested
+ * inside this class, with the specified {@code typeArguments}.
+ */
+ public ParameterizedTypeName nestedClass(String name, List<TypeName> typeArguments) {
+ checkNotNull(name, "name == null");
+ return new ParameterizedTypeName(this, rawType.nestedClass(name), typeArguments,
+ new ArrayList<>());
+ }
+
+ /** Returns a parameterized type, applying {@code typeArguments} to {@code rawType}. */
+ public static ParameterizedTypeName get(ClassName rawType, TypeName... typeArguments) {
+ return new ParameterizedTypeName(null, rawType, Arrays.asList(typeArguments));
+ }
+
+ /** Returns a parameterized type, applying {@code typeArguments} to {@code rawType}. */
+ public static ParameterizedTypeName get(Class<?> rawType, Type... typeArguments) {
+ return new ParameterizedTypeName(null, ClassName.get(rawType), list(typeArguments));
+ }
+
+ /** Returns a parameterized type equivalent to {@code type}. */
+ public static ParameterizedTypeName get(ParameterizedType type) {
+ return get(type, new LinkedHashMap<>());
+ }
+
+ /** Returns a parameterized type equivalent to {@code type}. */
+ static ParameterizedTypeName get(ParameterizedType type, Map<Type, TypeVariableName> map) {
+ ClassName rawType = ClassName.get((Class<?>) type.getRawType());
+ ParameterizedType ownerType = (type.getOwnerType() instanceof ParameterizedType)
+ && !Modifier.isStatic(((Class<?>) type.getRawType()).getModifiers())
+ ? (ParameterizedType) type.getOwnerType() : null;
+ List<TypeName> typeArguments = TypeName.list(type.getActualTypeArguments(), map);
+ return (ownerType != null)
+ ? get(ownerType, map).nestedClass(rawType.simpleName(), typeArguments)
+ : new ParameterizedTypeName(null, rawType, typeArguments);
+ }
+}
diff --git a/src/main/java/com/squareup/javapoet/TypeName.java b/src/main/java/com/squareup/javapoet/TypeName.java
new file mode 100644
index 0000000..38877f7
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/TypeName.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.type.NoType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.SimpleTypeVisitor8;
+
+/**
+ * Any type in Java's type system, plus {@code void}. This class is an identifier for primitive
+ * types like {@code int} and raw reference types like {@code String} and {@code List}. It also
+ * identifies composite types like {@code char[]} and {@code Set<Long>}.
+ *
+ * <p>Type names are dumb identifiers only and do not model the values they name. For example, the
+ * type name for {@code java.lang.List} doesn't know about the {@code size()} method, the fact that
+ * lists are collections, or even that it accepts a single type parameter.
+ *
+ * <p>Instances of this class are immutable value objects that implement {@code equals()} and {@code
+ * hashCode()} properly.
+ *
+ * <h3>Referencing existing types</h3>
+ *
+ * <p>Primitives and void are constants that you can reference directly: see {@link #INT}, {@link
+ * #DOUBLE}, and {@link #VOID}.
+ *
+ * <p>In an annotation processor you can get a type name instance for a type mirror by calling
+ * {@link #get(TypeMirror)}. In reflection code, you can use {@link #get(Type)}.
+ *
+ * <h3>Defining new types</h3>
+ *
+ * <p>Create new reference types like {@code com.example.HelloWorld} with {@link
+ * ClassName#get(String, String, String...)}. To build composite types like {@code char[]} and
+ * {@code Set<Long>}, use the factory methods on {@link ArrayTypeName}, {@link
+ * ParameterizedTypeName}, {@link TypeVariableName}, and {@link WildcardTypeName}.
+ */
+public class TypeName {
+ public static final TypeName VOID = new TypeName("void");
+ public static final TypeName BOOLEAN = new TypeName("boolean");
+ public static final TypeName BYTE = new TypeName("byte");
+ public static final TypeName SHORT = new TypeName("short");
+ public static final TypeName INT = new TypeName("int");
+ public static final TypeName LONG = new TypeName("long");
+ public static final TypeName CHAR = new TypeName("char");
+ public static final TypeName FLOAT = new TypeName("float");
+ public static final TypeName DOUBLE = new TypeName("double");
+ public static final ClassName OBJECT = ClassName.get("java.lang", "Object");
+
+ private static final ClassName BOXED_VOID = ClassName.get("java.lang", "Void");
+ private static final ClassName BOXED_BOOLEAN = ClassName.get("java.lang", "Boolean");
+ private static final ClassName BOXED_BYTE = ClassName.get("java.lang", "Byte");
+ private static final ClassName BOXED_SHORT = ClassName.get("java.lang", "Short");
+ private static final ClassName BOXED_INT = ClassName.get("java.lang", "Integer");
+ private static final ClassName BOXED_LONG = ClassName.get("java.lang", "Long");
+ private static final ClassName BOXED_CHAR = ClassName.get("java.lang", "Character");
+ private static final ClassName BOXED_FLOAT = ClassName.get("java.lang", "Float");
+ private static final ClassName BOXED_DOUBLE = ClassName.get("java.lang", "Double");
+
+ /** The name of this type if it is a keyword, or null. */
+ private final String keyword;
+ public final List<AnnotationSpec> annotations;
+
+ /** Lazily-initialized toString of this type name. */
+ private String cachedString;
+
+ private TypeName(String keyword) {
+ this(keyword, new ArrayList<>());
+ }
+
+ private TypeName(String keyword, List<AnnotationSpec> annotations) {
+ this.keyword = keyword;
+ this.annotations = Util.immutableList(annotations);
+ }
+
+ // Package-private constructor to prevent third-party subclasses.
+ TypeName(List<AnnotationSpec> annotations) {
+ this(null, annotations);
+ }
+
+ public final TypeName annotated(AnnotationSpec... annotations) {
+ return annotated(Arrays.asList(annotations));
+ }
+
+ public TypeName annotated(List<AnnotationSpec> annotations) {
+ Util.checkNotNull(annotations, "annotations == null");
+ return new TypeName(keyword, concatAnnotations(annotations));
+ }
+
+ public TypeName withoutAnnotations() {
+ return new TypeName(keyword);
+ }
+
+ protected final List<AnnotationSpec> concatAnnotations(List<AnnotationSpec> annotations) {
+ List<AnnotationSpec> allAnnotations = new ArrayList<>(this.annotations);
+ allAnnotations.addAll(annotations);
+ return allAnnotations;
+ }
+
+ public boolean isAnnotated() {
+ return !annotations.isEmpty();
+ }
+
+ /**
+ * Returns true if this is a primitive type like {@code int}. Returns false for all other types
+ * types including boxed primitives and {@code void}.
+ */
+ public boolean isPrimitive() {
+ return keyword != null && this != VOID;
+ }
+
+ /**
+ * Returns true if this is a boxed primitive type like {@code Integer}. Returns false for all
+ * other types types including unboxed primitives and {@code java.lang.Void}.
+ */
+ public boolean isBoxedPrimitive() {
+ return this.equals(BOXED_BOOLEAN)
+ || this.equals(BOXED_BYTE)
+ || this.equals(BOXED_SHORT)
+ || this.equals(BOXED_INT)
+ || this.equals(BOXED_LONG)
+ || this.equals(BOXED_CHAR)
+ || this.equals(BOXED_FLOAT)
+ || this.equals(BOXED_DOUBLE);
+ }
+
+ /**
+ * Returns a boxed type if this is a primitive type (like {@code Integer} for {@code int}) or
+ * {@code void}. Returns this type if boxing doesn't apply.
+ */
+ public TypeName box() {
+ if (keyword == null) return this; // Doesn't need boxing.
+ if (this == VOID) return BOXED_VOID;
+ if (this == BOOLEAN) return BOXED_BOOLEAN;
+ if (this == BYTE) return BOXED_BYTE;
+ if (this == SHORT) return BOXED_SHORT;
+ if (this == INT) return BOXED_INT;
+ if (this == LONG) return BOXED_LONG;
+ if (this == CHAR) return BOXED_CHAR;
+ if (this == FLOAT) return BOXED_FLOAT;
+ if (this == DOUBLE) return BOXED_DOUBLE;
+ throw new AssertionError(keyword);
+ }
+
+ /**
+ * Returns an unboxed type if this is a boxed primitive type (like {@code int} for {@code
+ * Integer}) or {@code Void}. Returns this type if it is already unboxed.
+ *
+ * @throws UnsupportedOperationException if this type isn't eligible for unboxing.
+ */
+ public TypeName unbox() {
+ if (keyword != null) return this; // Already unboxed.
+ if (this.equals(BOXED_VOID)) return VOID;
+ if (this.equals(BOXED_BOOLEAN)) return BOOLEAN;
+ if (this.equals(BOXED_BYTE)) return BYTE;
+ if (this.equals(BOXED_SHORT)) return SHORT;
+ if (this.equals(BOXED_INT)) return INT;
+ if (this.equals(BOXED_LONG)) return LONG;
+ if (this.equals(BOXED_CHAR)) return CHAR;
+ if (this.equals(BOXED_FLOAT)) return FLOAT;
+ if (this.equals(BOXED_DOUBLE)) return DOUBLE;
+ throw new UnsupportedOperationException("cannot unbox " + this);
+ }
+
+ @Override public final boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (getClass() != o.getClass()) return false;
+ return toString().equals(o.toString());
+ }
+
+ @Override public final int hashCode() {
+ return toString().hashCode();
+ }
+
+ @Override public final String toString() {
+ String result = cachedString;
+ if (result == null) {
+ try {
+ StringBuilder resultBuilder = new StringBuilder();
+ CodeWriter codeWriter = new CodeWriter(resultBuilder);
+ emit(codeWriter);
+ result = resultBuilder.toString();
+ cachedString = result;
+ } catch (IOException e) {
+ throw new AssertionError();
+ }
+ }
+ return result;
+ }
+
+ CodeWriter emit(CodeWriter out) throws IOException {
+ if (keyword == null) throw new AssertionError();
+
+ if (isAnnotated()) {
+ out.emit("");
+ emitAnnotations(out);
+ }
+ return out.emitAndIndent(keyword);
+ }
+
+ CodeWriter emitAnnotations(CodeWriter out) throws IOException {
+ for (AnnotationSpec annotation : annotations) {
+ annotation.emit(out, true);
+ out.emit(" ");
+ }
+ return out;
+ }
+
+
+ /** Returns a type name equivalent to {@code mirror}. */
+ public static TypeName get(TypeMirror mirror) {
+ return get(mirror, new LinkedHashMap<>());
+ }
+
+ static TypeName get(TypeMirror mirror,
+ final Map<TypeParameterElement, TypeVariableName> typeVariables) {
+ return mirror.accept(new SimpleTypeVisitor8<TypeName, Void>() {
+ @Override public TypeName visitPrimitive(PrimitiveType t, Void p) {
+ switch (t.getKind()) {
+ case BOOLEAN:
+ return TypeName.BOOLEAN;
+ case BYTE:
+ return TypeName.BYTE;
+ case SHORT:
+ return TypeName.SHORT;
+ case INT:
+ return TypeName.INT;
+ case LONG:
+ return TypeName.LONG;
+ case CHAR:
+ return TypeName.CHAR;
+ case FLOAT:
+ return TypeName.FLOAT;
+ case DOUBLE:
+ return TypeName.DOUBLE;
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ @Override public TypeName visitDeclared(DeclaredType t, Void p) {
+ ClassName rawType = ClassName.get((TypeElement) t.asElement());
+ TypeMirror enclosingType = t.getEnclosingType();
+ TypeName enclosing =
+ (enclosingType.getKind() != TypeKind.NONE)
+ && !t.asElement().getModifiers().contains(Modifier.STATIC)
+ ? enclosingType.accept(this, null)
+ : null;
+ if (t.getTypeArguments().isEmpty() && !(enclosing instanceof ParameterizedTypeName)) {
+ return rawType;
+ }
+
+ List<TypeName> typeArgumentNames = new ArrayList<>();
+ for (TypeMirror mirror : t.getTypeArguments()) {
+ typeArgumentNames.add(get(mirror, typeVariables));
+ }
+ return enclosing instanceof ParameterizedTypeName
+ ? ((ParameterizedTypeName) enclosing).nestedClass(
+ rawType.simpleName(), typeArgumentNames)
+ : new ParameterizedTypeName(null, rawType, typeArgumentNames);
+ }
+
+ @Override public TypeName visitError(ErrorType t, Void p) {
+ return visitDeclared(t, p);
+ }
+
+ @Override public ArrayTypeName visitArray(ArrayType t, Void p) {
+ return ArrayTypeName.get(t, typeVariables);
+ }
+
+ @Override public TypeName visitTypeVariable(javax.lang.model.type.TypeVariable t, Void p) {
+ return TypeVariableName.get(t, typeVariables);
+ }
+
+ @Override public TypeName visitWildcard(javax.lang.model.type.WildcardType t, Void p) {
+ return WildcardTypeName.get(t, typeVariables);
+ }
+
+ @Override public TypeName visitNoType(NoType t, Void p) {
+ if (t.getKind() == TypeKind.VOID) return TypeName.VOID;
+ return super.visitUnknown(t, p);
+ }
+
+ @Override protected TypeName defaultAction(TypeMirror e, Void p) {
+ throw new IllegalArgumentException("Unexpected type mirror: " + e);
+ }
+ }, null);
+ }
+
+ /** Returns a type name equivalent to {@code type}. */
+ public static TypeName get(Type type) {
+ return get(type, new LinkedHashMap<>());
+ }
+
+ static TypeName get(Type type, Map<Type, TypeVariableName> map) {
+ if (type instanceof Class<?>) {
+ Class<?> classType = (Class<?>) type;
+ if (type == void.class) return VOID;
+ if (type == boolean.class) return BOOLEAN;
+ if (type == byte.class) return BYTE;
+ if (type == short.class) return SHORT;
+ if (type == int.class) return INT;
+ if (type == long.class) return LONG;
+ if (type == char.class) return CHAR;
+ if (type == float.class) return FLOAT;
+ if (type == double.class) return DOUBLE;
+ if (classType.isArray()) return ArrayTypeName.of(get(classType.getComponentType(), map));
+ return ClassName.get(classType);
+
+ } else if (type instanceof ParameterizedType) {
+ return ParameterizedTypeName.get((ParameterizedType) type, map);
+
+ } else if (type instanceof WildcardType) {
+ return WildcardTypeName.get((WildcardType) type, map);
+
+ } else if (type instanceof TypeVariable<?>) {
+ return TypeVariableName.get((TypeVariable<?>) type, map);
+
+ } else if (type instanceof GenericArrayType) {
+ return ArrayTypeName.get((GenericArrayType) type, map);
+
+ } else {
+ throw new IllegalArgumentException("unexpected type: " + type);
+ }
+ }
+
+ /** Converts an array of types to a list of type names. */
+ static List<TypeName> list(Type[] types) {
+ return list(types, new LinkedHashMap<>());
+ }
+
+ static List<TypeName> list(Type[] types, Map<Type, TypeVariableName> map) {
+ List<TypeName> result = new ArrayList<>(types.length);
+ for (Type type : types) {
+ result.add(get(type, map));
+ }
+ return result;
+ }
+
+ /** Returns the array component of {@code type}, or null if {@code type} is not an array. */
+ static TypeName arrayComponent(TypeName type) {
+ return type instanceof ArrayTypeName
+ ? ((ArrayTypeName) type).componentType
+ : null;
+ }
+
+ /** Returns {@code type} as an array, or null if {@code type} is not an array. */
+ static ArrayTypeName asArray(TypeName type) {
+ return type instanceof ArrayTypeName
+ ? ((ArrayTypeName) type)
+ : null;
+ }
+
+}
diff --git a/src/main/java/com/squareup/javapoet/TypeSpec.java b/src/main/java/com/squareup/javapoet/TypeSpec.java
new file mode 100644
index 0000000..46de3a5
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/TypeSpec.java
@@ -0,0 +1,632 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Modifier;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+import static com.squareup.javapoet.Util.checkState;
+import static com.squareup.javapoet.Util.requireExactlyOneOf;
+
+/** A generated class, interface, or enum declaration. */
+public final class TypeSpec {
+ public final Kind kind;
+ public final String name;
+ public final CodeBlock anonymousTypeArguments;
+ public final CodeBlock javadoc;
+ public final List<AnnotationSpec> annotations;
+ public final Set<Modifier> modifiers;
+ public final List<TypeVariableName> typeVariables;
+ public final TypeName superclass;
+ public final List<TypeName> superinterfaces;
+ public final Map<String, TypeSpec> enumConstants;
+ public final List<FieldSpec> fieldSpecs;
+ public final CodeBlock staticBlock;
+ public final CodeBlock initializerBlock;
+ public final List<MethodSpec> methodSpecs;
+ public final List<TypeSpec> typeSpecs;
+ public final List<Element> originatingElements;
+
+ private TypeSpec(Builder builder) {
+ this.kind = builder.kind;
+ this.name = builder.name;
+ this.anonymousTypeArguments = builder.anonymousTypeArguments;
+ this.javadoc = builder.javadoc.build();
+ this.annotations = Util.immutableList(builder.annotations);
+ this.modifiers = Util.immutableSet(builder.modifiers);
+ this.typeVariables = Util.immutableList(builder.typeVariables);
+ this.superclass = builder.superclass;
+ this.superinterfaces = Util.immutableList(builder.superinterfaces);
+ this.enumConstants = Util.immutableMap(builder.enumConstants);
+ this.fieldSpecs = Util.immutableList(builder.fieldSpecs);
+ this.staticBlock = builder.staticBlock.build();
+ this.initializerBlock = builder.initializerBlock.build();
+ this.methodSpecs = Util.immutableList(builder.methodSpecs);
+ this.typeSpecs = Util.immutableList(builder.typeSpecs);
+
+ List<Element> originatingElementsMutable = new ArrayList<>();
+ originatingElementsMutable.addAll(builder.originatingElements);
+ for (TypeSpec typeSpec : builder.typeSpecs) {
+ originatingElementsMutable.addAll(typeSpec.originatingElements);
+ }
+ this.originatingElements = Util.immutableList(originatingElementsMutable);
+ }
+
+ /**
+ * Creates a dummy type spec for type-resolution only (in CodeWriter)
+ * while emitting the type declaration but before entering the type body.
+ */
+ private TypeSpec(TypeSpec type) {
+ assert type.anonymousTypeArguments == null;
+ this.kind = type.kind;
+ this.name = type.name;
+ this.anonymousTypeArguments = null;
+ this.javadoc = type.javadoc;
+ this.annotations = Collections.emptyList();
+ this.modifiers = Collections.emptySet();
+ this.typeVariables = Collections.emptyList();
+ this.superclass = null;
+ this.superinterfaces = Collections.emptyList();
+ this.enumConstants = Collections.emptyMap();
+ this.fieldSpecs = Collections.emptyList();
+ this.staticBlock = type.staticBlock;
+ this.initializerBlock = type.initializerBlock;
+ this.methodSpecs = Collections.emptyList();
+ this.typeSpecs = Collections.emptyList();
+ this.originatingElements = Collections.emptyList();
+ }
+
+ public boolean hasModifier(Modifier modifier) {
+ return modifiers.contains(modifier);
+ }
+
+ public static Builder classBuilder(String name) {
+ return new Builder(Kind.CLASS, checkNotNull(name, "name == null"), null);
+ }
+
+ public static Builder classBuilder(ClassName className) {
+ return classBuilder(checkNotNull(className, "className == null").simpleName());
+ }
+
+ public static Builder interfaceBuilder(String name) {
+ return new Builder(Kind.INTERFACE, checkNotNull(name, "name == null"), null);
+ }
+
+ public static Builder interfaceBuilder(ClassName className) {
+ return interfaceBuilder(checkNotNull(className, "className == null").simpleName());
+ }
+
+ public static Builder enumBuilder(String name) {
+ return new Builder(Kind.ENUM, checkNotNull(name, "name == null"), null);
+ }
+
+ public static Builder enumBuilder(ClassName className) {
+ return enumBuilder(checkNotNull(className, "className == null").simpleName());
+ }
+
+ public static Builder anonymousClassBuilder(String typeArgumentsFormat, Object... args) {
+ return anonymousClassBuilder(CodeBlock.builder()
+ .add(typeArgumentsFormat, args)
+ .build());
+ }
+
+ public static Builder anonymousClassBuilder(CodeBlock typeArguments) {
+ return new Builder(Kind.CLASS, null, typeArguments);
+ }
+
+ public static Builder annotationBuilder(String name) {
+ return new Builder(Kind.ANNOTATION, checkNotNull(name, "name == null"), null);
+ }
+
+ public static Builder annotationBuilder(ClassName className) {
+ return annotationBuilder(checkNotNull(className, "className == null").simpleName());
+ }
+
+ public Builder toBuilder() {
+ Builder builder = new Builder(kind, name, anonymousTypeArguments);
+ builder.javadoc.add(javadoc);
+ builder.annotations.addAll(annotations);
+ builder.modifiers.addAll(modifiers);
+ builder.typeVariables.addAll(typeVariables);
+ builder.superclass = superclass;
+ builder.superinterfaces.addAll(superinterfaces);
+ builder.enumConstants.putAll(enumConstants);
+ builder.fieldSpecs.addAll(fieldSpecs);
+ builder.methodSpecs.addAll(methodSpecs);
+ builder.typeSpecs.addAll(typeSpecs);
+ builder.initializerBlock.add(initializerBlock);
+ builder.staticBlock.add(staticBlock);
+ return builder;
+ }
+
+ void emit(CodeWriter codeWriter, String enumName, Set<Modifier> implicitModifiers)
+ throws IOException {
+ // Nested classes interrupt wrapped line indentation. Stash the current wrapping state and put
+ // it back afterwards when this type is complete.
+ int previousStatementLine = codeWriter.statementLine;
+ codeWriter.statementLine = -1;
+
+ try {
+ if (enumName != null) {
+ codeWriter.emitJavadoc(javadoc);
+ codeWriter.emitAnnotations(annotations, false);
+ codeWriter.emit("$L", enumName);
+ if (!anonymousTypeArguments.formatParts.isEmpty()) {
+ codeWriter.emit("(");
+ codeWriter.emit(anonymousTypeArguments);
+ codeWriter.emit(")");
+ }
+ if (fieldSpecs.isEmpty() && methodSpecs.isEmpty() && typeSpecs.isEmpty()) {
+ return; // Avoid unnecessary braces "{}".
+ }
+ codeWriter.emit(" {\n");
+ } else if (anonymousTypeArguments != null) {
+ TypeName supertype = !superinterfaces.isEmpty() ? superinterfaces.get(0) : superclass;
+ codeWriter.emit("new $T(", supertype);
+ codeWriter.emit(anonymousTypeArguments);
+ codeWriter.emit(") {\n");
+ } else {
+ // Push an empty type (specifically without nested types) for type-resolution.
+ codeWriter.pushType(new TypeSpec(this));
+
+ codeWriter.emitJavadoc(javadoc);
+ codeWriter.emitAnnotations(annotations, false);
+ codeWriter.emitModifiers(modifiers, Util.union(implicitModifiers, kind.asMemberModifiers));
+ if (kind == Kind.ANNOTATION) {
+ codeWriter.emit("$L $L", "@interface", name);
+ } else {
+ codeWriter.emit("$L $L", kind.name().toLowerCase(Locale.US), name);
+ }
+ codeWriter.emitTypeVariables(typeVariables);
+
+ List<TypeName> extendsTypes;
+ List<TypeName> implementsTypes;
+ if (kind == Kind.INTERFACE) {
+ extendsTypes = superinterfaces;
+ implementsTypes = Collections.emptyList();
+ } else {
+ extendsTypes = superclass.equals(ClassName.OBJECT)
+ ? Collections.emptyList()
+ : Collections.singletonList(superclass);
+ implementsTypes = superinterfaces;
+ }
+
+ if (!extendsTypes.isEmpty()) {
+ codeWriter.emit(" extends");
+ boolean firstType = true;
+ for (TypeName type : extendsTypes) {
+ if (!firstType) codeWriter.emit(",");
+ codeWriter.emit(" $T", type);
+ firstType = false;
+ }
+ }
+
+ if (!implementsTypes.isEmpty()) {
+ codeWriter.emit(" implements");
+ boolean firstType = true;
+ for (TypeName type : implementsTypes) {
+ if (!firstType) codeWriter.emit(",");
+ codeWriter.emit(" $T", type);
+ firstType = false;
+ }
+ }
+
+ codeWriter.popType();
+
+ codeWriter.emit(" {\n");
+ }
+
+ codeWriter.pushType(this);
+ codeWriter.indent();
+ boolean firstMember = true;
+ for (Iterator<Map.Entry<String, TypeSpec>> i = enumConstants.entrySet().iterator();
+ i.hasNext(); ) {
+ Map.Entry<String, TypeSpec> enumConstant = i.next();
+ if (!firstMember) codeWriter.emit("\n");
+ enumConstant.getValue().emit(codeWriter, enumConstant.getKey(), Collections.emptySet());
+ firstMember = false;
+ if (i.hasNext()) {
+ codeWriter.emit(",\n");
+ } else if (!fieldSpecs.isEmpty() || !methodSpecs.isEmpty() || !typeSpecs.isEmpty()) {
+ codeWriter.emit(";\n");
+ } else {
+ codeWriter.emit("\n");
+ }
+ }
+
+ // Static fields.
+ for (FieldSpec fieldSpec : fieldSpecs) {
+ if (!fieldSpec.hasModifier(Modifier.STATIC)) continue;
+ if (!firstMember) codeWriter.emit("\n");
+ fieldSpec.emit(codeWriter, kind.implicitFieldModifiers);
+ firstMember = false;
+ }
+
+ if (!staticBlock.isEmpty()) {
+ if (!firstMember) codeWriter.emit("\n");
+ codeWriter.emit(staticBlock);
+ firstMember = false;
+ }
+
+ // Non-static fields.
+ for (FieldSpec fieldSpec : fieldSpecs) {
+ if (fieldSpec.hasModifier(Modifier.STATIC)) continue;
+ if (!firstMember) codeWriter.emit("\n");
+ fieldSpec.emit(codeWriter, kind.implicitFieldModifiers);
+ firstMember = false;
+ }
+
+ // Initializer block.
+ if (!initializerBlock.isEmpty()) {
+ if (!firstMember) codeWriter.emit("\n");
+ codeWriter.emit(initializerBlock);
+ firstMember = false;
+ }
+
+ // Constructors.
+ for (MethodSpec methodSpec : methodSpecs) {
+ if (!methodSpec.isConstructor()) continue;
+ if (!firstMember) codeWriter.emit("\n");
+ methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers);
+ firstMember = false;
+ }
+
+ // Methods (static and non-static).
+ for (MethodSpec methodSpec : methodSpecs) {
+ if (methodSpec.isConstructor()) continue;
+ if (!firstMember) codeWriter.emit("\n");
+ methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers);
+ firstMember = false;
+ }
+
+ // Types.
+ for (TypeSpec typeSpec : typeSpecs) {
+ if (!firstMember) codeWriter.emit("\n");
+ typeSpec.emit(codeWriter, null, kind.implicitTypeModifiers);
+ firstMember = false;
+ }
+
+ codeWriter.unindent();
+ codeWriter.popType();
+
+ codeWriter.emit("}");
+ if (enumName == null && anonymousTypeArguments == null) {
+ codeWriter.emit("\n"); // If this type isn't also a value, include a trailing newline.
+ }
+ } finally {
+ codeWriter.statementLine = previousStatementLine;
+ }
+ }
+
+ @Override public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (getClass() != o.getClass()) return false;
+ return toString().equals(o.toString());
+ }
+
+ @Override public int hashCode() {
+ return toString().hashCode();
+ }
+
+ @Override public String toString() {
+ StringBuilder out = new StringBuilder();
+ try {
+ CodeWriter codeWriter = new CodeWriter(out);
+ emit(codeWriter, null, Collections.emptySet());
+ return out.toString();
+ } catch (IOException e) {
+ throw new AssertionError();
+ }
+ }
+
+ public enum Kind {
+ CLASS(
+ Collections.emptySet(),
+ Collections.emptySet(),
+ Collections.emptySet(),
+ Collections.emptySet()),
+
+ INTERFACE(
+ Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)),
+ Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.ABSTRACT)),
+ Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC)),
+ Util.immutableSet(Collections.singletonList(Modifier.STATIC))),
+
+ ENUM(
+ Collections.emptySet(),
+ Collections.emptySet(),
+ Collections.emptySet(),
+ Collections.singleton(Modifier.STATIC)),
+
+ ANNOTATION(
+ Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)),
+ Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.ABSTRACT)),
+ Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC)),
+ Util.immutableSet(Collections.singletonList(Modifier.STATIC)));
+
+ private final Set<Modifier> implicitFieldModifiers;
+ private final Set<Modifier> implicitMethodModifiers;
+ private final Set<Modifier> implicitTypeModifiers;
+ private final Set<Modifier> asMemberModifiers;
+
+ Kind(Set<Modifier> implicitFieldModifiers,
+ Set<Modifier> implicitMethodModifiers,
+ Set<Modifier> implicitTypeModifiers,
+ Set<Modifier> asMemberModifiers) {
+ this.implicitFieldModifiers = implicitFieldModifiers;
+ this.implicitMethodModifiers = implicitMethodModifiers;
+ this.implicitTypeModifiers = implicitTypeModifiers;
+ this.asMemberModifiers = asMemberModifiers;
+ }
+ }
+
+ public static final class Builder {
+ private final Kind kind;
+ private final String name;
+ private final CodeBlock anonymousTypeArguments;
+
+ private final CodeBlock.Builder javadoc = CodeBlock.builder();
+ private final List<AnnotationSpec> annotations = new ArrayList<>();
+ private final List<Modifier> modifiers = new ArrayList<>();
+ private final List<TypeVariableName> typeVariables = new ArrayList<>();
+ private TypeName superclass = ClassName.OBJECT;
+ private final List<TypeName> superinterfaces = new ArrayList<>();
+ private final Map<String, TypeSpec> enumConstants = new LinkedHashMap<>();
+ private final List<FieldSpec> fieldSpecs = new ArrayList<>();
+ private final CodeBlock.Builder staticBlock = CodeBlock.builder();
+ private final CodeBlock.Builder initializerBlock = CodeBlock.builder();
+ private final List<MethodSpec> methodSpecs = new ArrayList<>();
+ private final List<TypeSpec> typeSpecs = new ArrayList<>();
+ private final List<Element> originatingElements = new ArrayList<>();
+
+ private Builder(Kind kind, String name,
+ CodeBlock anonymousTypeArguments) {
+ checkArgument(name == null || SourceVersion.isName(name), "not a valid name: %s", name);
+ this.kind = kind;
+ this.name = name;
+ this.anonymousTypeArguments = anonymousTypeArguments;
+ }
+
+ public Builder addJavadoc(String format, Object... args) {
+ javadoc.add(format, args);
+ return this;
+ }
+
+ public Builder addJavadoc(CodeBlock block) {
+ javadoc.add(block);
+ return this;
+ }
+
+ public Builder addAnnotations(Iterable<AnnotationSpec> annotationSpecs) {
+ checkArgument(annotationSpecs != null, "annotationSpecs == null");
+ for (AnnotationSpec annotationSpec : annotationSpecs) {
+ this.annotations.add(annotationSpec);
+ }
+ return this;
+ }
+
+ public Builder addAnnotation(AnnotationSpec annotationSpec) {
+ checkNotNull(annotationSpec, "annotationSpec == null");
+ this.annotations.add(annotationSpec);
+ return this;
+ }
+
+ public Builder addAnnotation(ClassName annotation) {
+ return addAnnotation(AnnotationSpec.builder(annotation).build());
+ }
+
+ public Builder addAnnotation(Class<?> annotation) {
+ return addAnnotation(ClassName.get(annotation));
+ }
+
+ public Builder addModifiers(Modifier... modifiers) {
+ checkState(anonymousTypeArguments == null, "forbidden on anonymous types.");
+ for (Modifier modifier : modifiers) {
+ checkArgument(modifier != null, "modifiers contain null");
+ this.modifiers.add(modifier);
+ }
+ return this;
+ }
+
+ public Builder addTypeVariables(Iterable<TypeVariableName> typeVariables) {
+ checkState(anonymousTypeArguments == null, "forbidden on anonymous types.");
+ checkArgument(typeVariables != null, "typeVariables == null");
+ for (TypeVariableName typeVariable : typeVariables) {
+ this.typeVariables.add(typeVariable);
+ }
+ return this;
+ }
+
+ public Builder addTypeVariable(TypeVariableName typeVariable) {
+ checkState(anonymousTypeArguments == null, "forbidden on anonymous types.");
+ typeVariables.add(typeVariable);
+ return this;
+ }
+
+ public Builder superclass(TypeName superclass) {
+ checkState(this.kind == Kind.CLASS, "only classes have super classes, not " + this.kind);
+ checkState(this.superclass == ClassName.OBJECT,
+ "superclass already set to " + this.superclass);
+ checkArgument(!superclass.isPrimitive(), "superclass may not be a primitive");
+ this.superclass = superclass;
+ return this;
+ }
+
+ public Builder superclass(Type superclass) {
+ return superclass(TypeName.get(superclass));
+ }
+
+ public Builder addSuperinterfaces(Iterable<? extends TypeName> superinterfaces) {
+ checkArgument(superinterfaces != null, "superinterfaces == null");
+ for (TypeName superinterface : superinterfaces) {
+ addSuperinterface(superinterface);
+ }
+ return this;
+ }
+
+ public Builder addSuperinterface(TypeName superinterface) {
+ checkArgument(superinterface != null, "superinterface == null");
+ this.superinterfaces.add(superinterface);
+ return this;
+ }
+
+ public Builder addSuperinterface(Type superinterface) {
+ return addSuperinterface(TypeName.get(superinterface));
+ }
+
+ public Builder addEnumConstant(String name) {
+ return addEnumConstant(name, anonymousClassBuilder("").build());
+ }
+
+ public Builder addEnumConstant(String name, TypeSpec typeSpec) {
+ checkState(kind == Kind.ENUM, "%s is not enum", this.name);
+ checkArgument(typeSpec.anonymousTypeArguments != null,
+ "enum constants must have anonymous type arguments");
+ checkArgument(SourceVersion.isName(name), "not a valid enum constant: %s", name);
+ enumConstants.put(name, typeSpec);
+ return this;
+ }
+
+ public Builder addFields(Iterable<FieldSpec> fieldSpecs) {
+ checkArgument(fieldSpecs != null, "fieldSpecs == null");
+ for (FieldSpec fieldSpec : fieldSpecs) {
+ addField(fieldSpec);
+ }
+ return this;
+ }
+
+ public Builder addField(FieldSpec fieldSpec) {
+ if (kind == Kind.INTERFACE || kind == Kind.ANNOTATION) {
+ requireExactlyOneOf(fieldSpec.modifiers, Modifier.PUBLIC, Modifier.PRIVATE);
+ Set<Modifier> check = EnumSet.of(Modifier.STATIC, Modifier.FINAL);
+ checkState(fieldSpec.modifiers.containsAll(check), "%s %s.%s requires modifiers %s",
+ kind, name, fieldSpec.name, check);
+ }
+ fieldSpecs.add(fieldSpec);
+ return this;
+ }
+
+ public Builder addField(TypeName type, String name, Modifier... modifiers) {
+ return addField(FieldSpec.builder(type, name, modifiers).build());
+ }
+
+ public Builder addField(Type type, String name, Modifier... modifiers) {
+ return addField(TypeName.get(type), name, modifiers);
+ }
+
+ public Builder addStaticBlock(CodeBlock block) {
+ staticBlock.beginControlFlow("static").add(block).endControlFlow();
+ return this;
+ }
+
+ public Builder addInitializerBlock(CodeBlock block) {
+ if ((kind != Kind.CLASS && kind != Kind.ENUM)) {
+ throw new UnsupportedOperationException(kind + " can't have initializer blocks");
+ }
+ initializerBlock.add("{\n")
+ .indent()
+ .add(block)
+ .unindent()
+ .add("}\n");
+ return this;
+ }
+
+ public Builder addMethods(Iterable<MethodSpec> methodSpecs) {
+ checkArgument(methodSpecs != null, "methodSpecs == null");
+ for (MethodSpec methodSpec : methodSpecs) {
+ addMethod(methodSpec);
+ }
+ return this;
+ }
+
+ public Builder addMethod(MethodSpec methodSpec) {
+ if (kind == Kind.INTERFACE) {
+ requireExactlyOneOf(methodSpec.modifiers, Modifier.ABSTRACT, Modifier.STATIC,
+ Modifier.DEFAULT);
+ requireExactlyOneOf(methodSpec.modifiers, Modifier.PUBLIC, Modifier.PRIVATE);
+ } else if (kind == Kind.ANNOTATION) {
+ checkState(methodSpec.modifiers.equals(kind.implicitMethodModifiers),
+ "%s %s.%s requires modifiers %s",
+ kind, name, methodSpec.name, kind.implicitMethodModifiers);
+ }
+ if (kind != Kind.ANNOTATION) {
+ checkState(methodSpec.defaultValue == null, "%s %s.%s cannot have a default value",
+ kind, name, methodSpec.name);
+ }
+ if (kind != Kind.INTERFACE) {
+ checkState(!methodSpec.hasModifier(Modifier.DEFAULT), "%s %s.%s cannot be default",
+ kind, name, methodSpec.name);
+ }
+ methodSpecs.add(methodSpec);
+ return this;
+ }
+
+ public Builder addTypes(Iterable<TypeSpec> typeSpecs) {
+ checkArgument(typeSpecs != null, "typeSpecs == null");
+ for (TypeSpec typeSpec : typeSpecs) {
+ addType(typeSpec);
+ }
+ return this;
+ }
+
+ public Builder addType(TypeSpec typeSpec) {
+ checkArgument(typeSpec.modifiers.containsAll(kind.implicitTypeModifiers),
+ "%s %s.%s requires modifiers %s", kind, name, typeSpec.name,
+ kind.implicitTypeModifiers);
+ typeSpecs.add(typeSpec);
+ return this;
+ }
+
+ public Builder addOriginatingElement(Element originatingElement) {
+ originatingElements.add(originatingElement);
+ return this;
+ }
+
+ public TypeSpec build() {
+ checkArgument(kind != Kind.ENUM || !enumConstants.isEmpty(),
+ "at least one enum constant is required for %s", name);
+
+ boolean isAbstract = modifiers.contains(Modifier.ABSTRACT) || kind != Kind.CLASS;
+ for (MethodSpec methodSpec : methodSpecs) {
+ checkArgument(isAbstract || !methodSpec.hasModifier(Modifier.ABSTRACT),
+ "non-abstract type %s cannot declare abstract method %s", name, methodSpec.name);
+ }
+
+ boolean superclassIsObject = superclass.equals(ClassName.OBJECT);
+ int interestingSupertypeCount = (superclassIsObject ? 0 : 1) + superinterfaces.size();
+ checkArgument(anonymousTypeArguments == null || interestingSupertypeCount <= 1,
+ "anonymous type has too many supertypes");
+
+ return new TypeSpec(this);
+ }
+ }
+}
diff --git a/src/main/java/com/squareup/javapoet/TypeVariableName.java b/src/main/java/com/squareup/javapoet/TypeVariableName.java
new file mode 100644
index 0000000..54c2fa5
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/TypeVariableName.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+
+import static com.squareup.javapoet.Util.checkArgument;
+import static com.squareup.javapoet.Util.checkNotNull;
+
+public final class TypeVariableName extends TypeName {
+ public final String name;
+ public final List<TypeName> bounds;
+
+ private TypeVariableName(String name, List<TypeName> bounds) {
+ this(name, bounds, new ArrayList<>());
+ }
+
+ private TypeVariableName(String name, List<TypeName> bounds, List<AnnotationSpec> annotations) {
+ super(annotations);
+ this.name = checkNotNull(name, "name == null");
+ this.bounds = bounds;
+
+ for (TypeName bound : this.bounds) {
+ checkArgument(!bound.isPrimitive() && bound != VOID, "invalid bound: %s", bound);
+ }
+ }
+
+ @Override public TypeVariableName annotated(List<AnnotationSpec> annotations) {
+ return new TypeVariableName(name, bounds, annotations);
+ }
+
+ @Override public TypeName withoutAnnotations() {
+ return new TypeVariableName(name, bounds);
+ }
+
+ public TypeVariableName withBounds(Type... bounds) {
+ return withBounds(TypeName.list(bounds));
+ }
+
+ public TypeVariableName withBounds(TypeName... bounds) {
+ return withBounds(Arrays.asList(bounds));
+ }
+
+ public TypeVariableName withBounds(List<? extends TypeName> bounds) {
+ ArrayList<TypeName> newBounds = new ArrayList<>();
+ newBounds.addAll(this.bounds);
+ newBounds.addAll(bounds);
+ return new TypeVariableName(name, newBounds, annotations);
+ }
+
+ private static TypeVariableName of(String name, List<TypeName> bounds) {
+ // Strip java.lang.Object from bounds if it is present.
+ List<TypeName> boundsNoObject = new ArrayList<>(bounds);
+ boundsNoObject.remove(OBJECT);
+ return new TypeVariableName(name, Collections.unmodifiableList(boundsNoObject));
+ }
+
+ @Override CodeWriter emit(CodeWriter out) throws IOException {
+ emitAnnotations(out);
+ return out.emitAndIndent(name);
+ }
+
+ /** Returns type variable named {@code name} without bounds. */
+ public static TypeVariableName get(String name) {
+ return TypeVariableName.of(name, Collections.emptyList());
+ }
+
+ /** Returns type variable named {@code name} with {@code bounds}. */
+ public static TypeVariableName get(String name, TypeName... bounds) {
+ return TypeVariableName.of(name, Arrays.asList(bounds));
+ }
+
+ /** Returns type variable named {@code name} with {@code bounds}. */
+ public static TypeVariableName get(String name, Type... bounds) {
+ return TypeVariableName.of(name, TypeName.list(bounds));
+ }
+
+ /** Returns type variable equivalent to {@code mirror}. */
+ public static TypeVariableName get(TypeVariable mirror) {
+ return get((TypeParameterElement) mirror.asElement());
+ }
+
+ /**
+ * Make a TypeVariableName for the given TypeMirror. This form is used internally to avoid
+ * infinite recursion in cases like {@code Enum<E extends Enum<E>>}. When we encounter such a
+ * thing, we will make a TypeVariableName without bounds and add that to the {@code typeVariables}
+ * map before looking up the bounds. Then if we encounter this TypeVariable again while
+ * constructing the bounds, we can just return it from the map. And, the code that put the entry
+ * in {@code variables} will make sure that the bounds are filled in before returning.
+ */
+ static TypeVariableName get(
+ TypeVariable mirror, Map<TypeParameterElement, TypeVariableName> typeVariables) {
+ TypeParameterElement element = (TypeParameterElement) mirror.asElement();
+ TypeVariableName typeVariableName = typeVariables.get(element);
+ if (typeVariableName == null) {
+ // Since the bounds field is public, we need to make it an unmodifiableList. But we control
+ // the List that that wraps, which means we can change it before returning.
+ List<TypeName> bounds = new ArrayList<>();
+ List<TypeName> visibleBounds = Collections.unmodifiableList(bounds);
+ typeVariableName = new TypeVariableName(element.getSimpleName().toString(), visibleBounds);
+ typeVariables.put(element, typeVariableName);
+ for (TypeMirror typeMirror : element.getBounds()) {
+ bounds.add(TypeName.get(typeMirror, typeVariables));
+ }
+ bounds.remove(OBJECT);
+ }
+ return typeVariableName;
+ }
+
+ /** Returns type variable equivalent to {@code element}. */
+ public static TypeVariableName get(TypeParameterElement element) {
+ String name = element.getSimpleName().toString();
+ List<? extends TypeMirror> boundsMirrors = element.getBounds();
+
+ List<TypeName> boundsTypeNames = new ArrayList<>();
+ for (TypeMirror typeMirror : boundsMirrors) {
+ boundsTypeNames.add(TypeName.get(typeMirror));
+ }
+
+ return TypeVariableName.of(name, boundsTypeNames);
+ }
+
+ /** Returns type variable equivalent to {@code type}. */
+ public static TypeVariableName get(java.lang.reflect.TypeVariable<?> type) {
+ return get(type, new LinkedHashMap<>());
+ }
+
+ /** @see #get(java.lang.reflect.TypeVariable, Map) */
+ static TypeVariableName get(java.lang.reflect.TypeVariable<?> type,
+ Map<Type, TypeVariableName> map) {
+ TypeVariableName result = map.get(type);
+ if (result == null) {
+ List<TypeName> bounds = new ArrayList<>();
+ List<TypeName> visibleBounds = Collections.unmodifiableList(bounds);
+ result = new TypeVariableName(type.getName(), visibleBounds);
+ map.put(type, result);
+ for (Type bound : type.getBounds()) {
+ bounds.add(TypeName.get(bound, map));
+ }
+ bounds.remove(OBJECT);
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/squareup/javapoet/Util.java b/src/main/java/com/squareup/javapoet/Util.java
new file mode 100644
index 0000000..e0eabad
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/Util.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.Modifier;
+
+import static java.lang.Character.isISOControl;
+
+/**
+ * Like Guava, but worse and standalone. This makes it easier to mix JavaPoet with libraries that
+ * bring their own version of Guava.
+ */
+final class Util {
+ private Util() {
+ }
+
+ static <K, V> Map<K, List<V>> immutableMultimap(Map<K, List<V>> multimap) {
+ LinkedHashMap<K, List<V>> result = new LinkedHashMap<>();
+ for (Map.Entry<K, List<V>> entry : multimap.entrySet()) {
+ if (entry.getValue().isEmpty()) continue;
+ result.put(entry.getKey(), immutableList(entry.getValue()));
+ }
+ return Collections.unmodifiableMap(result);
+ }
+
+ static <K, V> Map<K, V> immutableMap(Map<K, V> map) {
+ return Collections.unmodifiableMap(new LinkedHashMap<>(map));
+ }
+
+ static void checkArgument(boolean condition, String format, Object... args) {
+ if (!condition) throw new IllegalArgumentException(String.format(format, args));
+ }
+
+ static <T> T checkNotNull(T reference, String format, Object... args) {
+ if (reference == null) throw new NullPointerException(String.format(format, args));
+ return reference;
+ }
+
+ static void checkState(boolean condition, String format, Object... args) {
+ if (!condition) throw new IllegalStateException(String.format(format, args));
+ }
+
+ static <T> List<T> immutableList(Collection<T> collection) {
+ return Collections.unmodifiableList(new ArrayList<>(collection));
+ }
+
+ static <T> Set<T> immutableSet(Collection<T> set) {
+ return Collections.unmodifiableSet(new LinkedHashSet<>(set));
+ }
+
+ static <T> Set<T> union(Set<T> a, Set<T> b) {
+ Set<T> result = new LinkedHashSet<>();
+ result.addAll(a);
+ result.addAll(b);
+ return result;
+ }
+
+ static void requireExactlyOneOf(Set<Modifier> modifiers, Modifier... mutuallyExclusive) {
+ int count = 0;
+ for (Modifier modifier : mutuallyExclusive) {
+ if (modifiers.contains(modifier)) count++;
+ }
+ checkArgument(count == 1, "modifiers %s must contain one of %s",
+ modifiers, Arrays.toString(mutuallyExclusive));
+ }
+
+ static String characterLiteralWithoutSingleQuotes(char c) {
+ // see https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6
+ switch (c) {
+ case '\b': return "\\b"; /* \u0008: backspace (BS) */
+ case '\t': return "\\t"; /* \u0009: horizontal tab (HT) */
+ case '\n': return "\\n"; /* \u000a: linefeed (LF) */
+ case '\f': return "\\f"; /* \u000c: form feed (FF) */
+ case '\r': return "\\r"; /* \u000d: carriage return (CR) */
+ case '\"': return "\""; /* \u0022: double quote (") */
+ case '\'': return "\\'"; /* \u0027: single quote (') */
+ case '\\': return "\\\\"; /* \u005c: backslash (\) */
+ default:
+ return isISOControl(c) ? String.format("\\u%04x", (int) c) : Character.toString(c);
+ }
+ }
+
+ /** Returns the string literal representing {@code value}, including wrapping double quotes. */
+ static String stringLiteralWithDoubleQuotes(String value, String indent) {
+ StringBuilder result = new StringBuilder(value.length() + 2);
+ result.append('"');
+ for (int i = 0; i < value.length(); i++) {
+ char c = value.charAt(i);
+ // trivial case: single quote must not be escaped
+ if (c == '\'') {
+ result.append("'");
+ continue;
+ }
+ // trivial case: double quotes must be escaped
+ if (c == '\"') {
+ result.append("\\\"");
+ continue;
+ }
+ // default case: just let character literal do its work
+ result.append(characterLiteralWithoutSingleQuotes(c));
+ // need to append indent after linefeed?
+ if (c == '\n' && i + 1 < value.length()) {
+ result.append("\"\n").append(indent).append(indent).append("+ \"");
+ }
+ }
+ result.append('"');
+ return result.toString();
+ }
+}
diff --git a/src/main/java/com/squareup/javapoet/WildcardTypeName.java b/src/main/java/com/squareup/javapoet/WildcardTypeName.java
new file mode 100644
index 0000000..17cb73f
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/WildcardTypeName.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.TypeMirror;
+
+import static com.squareup.javapoet.Util.checkArgument;
+
+public final class WildcardTypeName extends TypeName {
+ public final List<TypeName> upperBounds;
+ public final List<TypeName> lowerBounds;
+
+ private WildcardTypeName(List<TypeName> upperBounds, List<TypeName> lowerBounds) {
+ this(upperBounds, lowerBounds, new ArrayList<>());
+ }
+
+ private WildcardTypeName(List<TypeName> upperBounds, List<TypeName> lowerBounds,
+ List<AnnotationSpec> annotations) {
+ super(annotations);
+ this.upperBounds = Util.immutableList(upperBounds);
+ this.lowerBounds = Util.immutableList(lowerBounds);
+
+ checkArgument(this.upperBounds.size() == 1, "unexpected extends bounds: %s", upperBounds);
+ for (TypeName upperBound : this.upperBounds) {
+ checkArgument(!upperBound.isPrimitive() && upperBound != VOID,
+ "invalid upper bound: %s", upperBound);
+ }
+ for (TypeName lowerBound : this.lowerBounds) {
+ checkArgument(!lowerBound.isPrimitive() && lowerBound != VOID,
+ "invalid lower bound: %s", lowerBound);
+ }
+ }
+
+ @Override public WildcardTypeName annotated(List<AnnotationSpec> annotations) {
+ return new WildcardTypeName(upperBounds, lowerBounds, concatAnnotations(annotations));
+ }
+
+ @Override public TypeName withoutAnnotations() {
+ return new WildcardTypeName(upperBounds, lowerBounds);
+ }
+
+ @Override CodeWriter emit(CodeWriter out) throws IOException {
+ if (lowerBounds.size() == 1) {
+ return out.emit("? super $T", lowerBounds.get(0));
+ }
+ return upperBounds.get(0).equals(TypeName.OBJECT)
+ ? out.emit("?")
+ : out.emit("? extends $T", upperBounds.get(0));
+ }
+
+ /**
+ * Returns a type that represents an unknown type that extends {@code bound}. For example, if
+ * {@code bound} is {@code CharSequence.class}, this returns {@code ? extends CharSequence}. If
+ * {@code bound} is {@code Object.class}, this returns {@code ?}, which is shorthand for {@code
+ * ? extends Object}.
+ */
+ public static WildcardTypeName subtypeOf(TypeName upperBound) {
+ return new WildcardTypeName(Collections.singletonList(upperBound), Collections.emptyList());
+ }
+
+ public static WildcardTypeName subtypeOf(Type upperBound) {
+ return subtypeOf(TypeName.get(upperBound));
+ }
+
+ /**
+ * Returns a type that represents an unknown supertype of {@code bound}. For example, if {@code
+ * bound} is {@code String.class}, this returns {@code ? super String}.
+ */
+ public static WildcardTypeName supertypeOf(TypeName lowerBound) {
+ return new WildcardTypeName(Collections.singletonList(OBJECT),
+ Collections.singletonList(lowerBound));
+ }
+
+ public static WildcardTypeName supertypeOf(Type lowerBound) {
+ return supertypeOf(TypeName.get(lowerBound));
+ }
+
+ public static TypeName get(javax.lang.model.type.WildcardType mirror) {
+ return get(mirror, new LinkedHashMap<>());
+ }
+
+ static TypeName get(
+ javax.lang.model.type.WildcardType mirror,
+ Map<TypeParameterElement, TypeVariableName> typeVariables) {
+ TypeMirror extendsBound = mirror.getExtendsBound();
+ if (extendsBound == null) {
+ TypeMirror superBound = mirror.getSuperBound();
+ if (superBound == null) {
+ return subtypeOf(Object.class);
+ } else {
+ return supertypeOf(TypeName.get(superBound, typeVariables));
+ }
+ } else {
+ return subtypeOf(TypeName.get(extendsBound, typeVariables));
+ }
+ }
+
+ public static TypeName get(WildcardType wildcardName) {
+ return get(wildcardName, new LinkedHashMap<>());
+ }
+
+ static TypeName get(WildcardType wildcardName, Map<Type, TypeVariableName> map) {
+ return new WildcardTypeName(
+ list(wildcardName.getUpperBounds(), map),
+ list(wildcardName.getLowerBounds(), map));
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/AbstractTypesTest.java b/src/test/java/com/squareup/javapoet/AbstractTypesTest.java
new file mode 100644
index 0000000..86d9cbc
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/AbstractTypesTest.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+import static javax.lang.model.util.ElementFilter.fieldsIn;
+import static org.junit.Assert.*;
+
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVisitor;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.JavaFileObject;
+
+import org.junit.Test;
+
+public abstract class AbstractTypesTest {
+ protected abstract Elements getElements();
+ protected abstract Types getTypes();
+
+ private TypeElement getElement(Class<?> clazz) {
+ return getElements().getTypeElement(clazz.getCanonicalName());
+ }
+
+ private TypeMirror getMirror(Class<?> clazz) {
+ return getElement(clazz).asType();
+ }
+
+ @Test public void getBasicTypeMirror() {
+ assertThat(TypeName.get(getMirror(Object.class)))
+ .isEqualTo(ClassName.get(Object.class));
+ assertThat(TypeName.get(getMirror(Charset.class)))
+ .isEqualTo(ClassName.get(Charset.class));
+ assertThat(TypeName.get(getMirror(AbstractTypesTest.class)))
+ .isEqualTo(ClassName.get(AbstractTypesTest.class));
+ }
+
+ @Test public void getParameterizedTypeMirror() {
+ DeclaredType setType =
+ getTypes().getDeclaredType(getElement(Set.class), getMirror(Object.class));
+ assertThat(TypeName.get(setType))
+ .isEqualTo(ParameterizedTypeName.get(ClassName.get(Set.class), ClassName.OBJECT));
+ }
+
+ @Test public void errorTypes() {
+ JavaFileObject hasErrorTypes =
+ JavaFileObjects.forSourceLines(
+ "com.squareup.tacos.ErrorTypes",
+ "package com.squareup.tacos;",
+ "",
+ "@SuppressWarnings(\"hook-into-compiler\")",
+ "class ErrorTypes {",
+ " Tacos tacos;",
+ " Ingredients.Guacamole guacamole;",
+ "}");
+ Compilation compilation = javac().withProcessors(new AbstractProcessor() {
+ @Override
+ public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
+ TypeElement classFile =
+ processingEnv.getElementUtils().getTypeElement("com.squareup.tacos.ErrorTypes");
+ List<VariableElement> fields = fieldsIn(classFile.getEnclosedElements());
+ ErrorType topLevel = (ErrorType) fields.get(0).asType();
+ ErrorType member = (ErrorType) fields.get(1).asType();
+
+ assertThat(TypeName.get(topLevel)).isEqualTo(ClassName.get("", "Tacos"));
+ assertThat(TypeName.get(member)).isEqualTo(ClassName.get("Ingredients", "Guacamole"));
+ return false;
+ }
+
+ @Override
+ public Set<String> getSupportedAnnotationTypes() {
+ return Collections.singleton("*");
+ }
+ }).compile(hasErrorTypes);
+
+ assertThat(compilation).failed();
+ }
+
+ static class Parameterized<
+ Simple,
+ ExtendsClass extends Number,
+ ExtendsInterface extends Runnable,
+ ExtendsTypeVariable extends Simple,
+ Intersection extends Number & Runnable,
+ IntersectionOfInterfaces extends Runnable & Serializable> {}
+
+ @Test public void getTypeVariableTypeMirror() {
+ List<? extends TypeParameterElement> typeVariables =
+ getElement(Parameterized.class).getTypeParameters();
+
+ // Members of converted types use ClassName and not Class<?>.
+ ClassName number = ClassName.get(Number.class);
+ ClassName runnable = ClassName.get(Runnable.class);
+ ClassName serializable = ClassName.get(Serializable.class);
+
+ assertThat(TypeName.get(typeVariables.get(0).asType()))
+ .isEqualTo(TypeVariableName.get("Simple"));
+ assertThat(TypeName.get(typeVariables.get(1).asType()))
+ .isEqualTo(TypeVariableName.get("ExtendsClass", number));
+ assertThat(TypeName.get(typeVariables.get(2).asType()))
+ .isEqualTo(TypeVariableName.get("ExtendsInterface", runnable));
+ assertThat(TypeName.get(typeVariables.get(3).asType()))
+ .isEqualTo(TypeVariableName.get("ExtendsTypeVariable", TypeVariableName.get("Simple")));
+ assertThat(TypeName.get(typeVariables.get(4).asType()))
+ .isEqualTo(TypeVariableName.get("Intersection", number, runnable));
+ assertThat(TypeName.get(typeVariables.get(5).asType()))
+ .isEqualTo(TypeVariableName.get("IntersectionOfInterfaces", runnable, serializable));
+ assertThat(((TypeVariableName) TypeName.get(typeVariables.get(4).asType())).bounds)
+ .containsExactly(number, runnable);
+ }
+
+ static class Recursive<T extends Map<List<T>, Set<T[]>>> {}
+
+ @Test
+ public void getTypeVariableTypeMirrorRecursive() {
+ TypeMirror typeMirror = getElement(Recursive.class).asType();
+ ParameterizedTypeName typeName = (ParameterizedTypeName) TypeName.get(typeMirror);
+ String className = Recursive.class.getCanonicalName();
+ assertThat(typeName.toString()).isEqualTo(className + "<T>");
+
+ TypeVariableName typeVariableName = (TypeVariableName) typeName.typeArguments.get(0);
+
+ try {
+ typeVariableName.bounds.set(0, null);
+ fail("Expected UnsupportedOperationException");
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ assertThat(typeVariableName.toString()).isEqualTo("T");
+ assertThat(typeVariableName.bounds.toString())
+ .isEqualTo("[java.util.Map<java.util.List<T>, java.util.Set<T[]>>]");
+ }
+
+ @Test public void getPrimitiveTypeMirror() {
+ assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.BOOLEAN)))
+ .isEqualTo(TypeName.BOOLEAN);
+ assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.BYTE)))
+ .isEqualTo(TypeName.BYTE);
+ assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.SHORT)))
+ .isEqualTo(TypeName.SHORT);
+ assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.INT)))
+ .isEqualTo(TypeName.INT);
+ assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.LONG)))
+ .isEqualTo(TypeName.LONG);
+ assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.CHAR)))
+ .isEqualTo(TypeName.CHAR);
+ assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.FLOAT)))
+ .isEqualTo(TypeName.FLOAT);
+ assertThat(TypeName.get(getTypes().getPrimitiveType(TypeKind.DOUBLE)))
+ .isEqualTo(TypeName.DOUBLE);
+ }
+
+ @Test public void getArrayTypeMirror() {
+ assertThat(TypeName.get(getTypes().getArrayType(getMirror(Object.class))))
+ .isEqualTo(ArrayTypeName.of(ClassName.OBJECT));
+ }
+
+ @Test public void getVoidTypeMirror() {
+ assertThat(TypeName.get(getTypes().getNoType(TypeKind.VOID)))
+ .isEqualTo(TypeName.VOID);
+ }
+
+ @Test public void getNullTypeMirror() {
+ try {
+ TypeName.get(getTypes().getNullType());
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test public void parameterizedType() throws Exception {
+ ParameterizedTypeName type = ParameterizedTypeName.get(Map.class, String.class, Long.class);
+ assertThat(type.toString()).isEqualTo("java.util.Map<java.lang.String, java.lang.Long>");
+ }
+
+ @Test public void arrayType() throws Exception {
+ ArrayTypeName type = ArrayTypeName.of(String.class);
+ assertThat(type.toString()).isEqualTo("java.lang.String[]");
+ }
+
+ @Test public void wildcardExtendsType() throws Exception {
+ WildcardTypeName type = WildcardTypeName.subtypeOf(CharSequence.class);
+ assertThat(type.toString()).isEqualTo("? extends java.lang.CharSequence");
+ }
+
+ @Test public void wildcardExtendsObject() throws Exception {
+ WildcardTypeName type = WildcardTypeName.subtypeOf(Object.class);
+ assertThat(type.toString()).isEqualTo("?");
+ }
+
+ @Test public void wildcardSuperType() throws Exception {
+ WildcardTypeName type = WildcardTypeName.supertypeOf(String.class);
+ assertThat(type.toString()).isEqualTo("? super java.lang.String");
+ }
+
+ @Test public void wildcardMirrorNoBounds() throws Exception {
+ WildcardType wildcard = getTypes().getWildcardType(null, null);
+ TypeName type = TypeName.get(wildcard);
+ assertThat(type.toString()).isEqualTo("?");
+ }
+
+ @Test public void wildcardMirrorExtendsType() throws Exception {
+ Types types = getTypes();
+ Elements elements = getElements();
+ TypeMirror charSequence = elements.getTypeElement(CharSequence.class.getName()).asType();
+ WildcardType wildcard = types.getWildcardType(charSequence, null);
+ TypeName type = TypeName.get(wildcard);
+ assertThat(type.toString()).isEqualTo("? extends java.lang.CharSequence");
+ }
+
+ @Test public void wildcardMirrorSuperType() throws Exception {
+ Types types = getTypes();
+ Elements elements = getElements();
+ TypeMirror string = elements.getTypeElement(String.class.getName()).asType();
+ WildcardType wildcard = types.getWildcardType(null, string);
+ TypeName type = TypeName.get(wildcard);
+ assertThat(type.toString()).isEqualTo("? super java.lang.String");
+ }
+
+ @Test public void typeVariable() throws Exception {
+ TypeVariableName type = TypeVariableName.get("T", CharSequence.class);
+ assertThat(type.toString()).isEqualTo("T"); // (Bounds are only emitted in declaration.)
+ }
+
+ @Test public void box() throws Exception {
+ assertThat(TypeName.INT.box()).isEqualTo(ClassName.get(Integer.class));
+ assertThat(TypeName.VOID.box()).isEqualTo(ClassName.get(Void.class));
+ assertThat(ClassName.get(Integer.class).box()).isEqualTo(ClassName.get(Integer.class));
+ assertThat(ClassName.get(Void.class).box()).isEqualTo(ClassName.get(Void.class));
+ assertThat(TypeName.OBJECT.box()).isEqualTo(TypeName.OBJECT);
+ assertThat(ClassName.get(String.class).box()).isEqualTo(ClassName.get(String.class));
+ }
+
+ @Test public void unbox() throws Exception {
+ assertThat(TypeName.INT).isEqualTo(TypeName.INT.unbox());
+ assertThat(TypeName.VOID).isEqualTo(TypeName.VOID.unbox());
+ assertThat(ClassName.get(Integer.class).unbox()).isEqualTo(TypeName.INT.unbox());
+ assertThat(ClassName.get(Void.class).unbox()).isEqualTo(TypeName.VOID.unbox());
+ try {
+ TypeName.OBJECT.unbox();
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+ try {
+ ClassName.get(String.class).unbox();
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/AnnotatedTypeNameTest.java b/src/test/java/com/squareup/javapoet/AnnotatedTypeNameTest.java
new file mode 100644
index 0000000..42734ff
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/AnnotatedTypeNameTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+public class AnnotatedTypeNameTest {
+
+ private final static String NN = NeverNull.class.getCanonicalName();
+ private final AnnotationSpec NEVER_NULL = AnnotationSpec.builder(NeverNull.class).build();
+ private final static String TUA = TypeUseAnnotation.class.getCanonicalName();
+ private final AnnotationSpec TYPE_USE_ANNOTATION =
+ AnnotationSpec.builder(TypeUseAnnotation.class).build();
+
+ @Target(ElementType.TYPE_USE)
+ public @interface NeverNull {}
+
+ @Target(ElementType.TYPE_USE)
+ public @interface TypeUseAnnotation {}
+
+
+ @Test(expected=NullPointerException.class) public void nullAnnotationArray() {
+ TypeName.BOOLEAN.annotated((AnnotationSpec[]) null);
+ }
+
+ @Test(expected=NullPointerException.class) public void nullAnnotationList() {
+ TypeName.DOUBLE.annotated((List<AnnotationSpec>) null);
+ }
+
+ @Test public void annotated() {
+ TypeName simpleString = TypeName.get(String.class);
+ assertFalse(simpleString.isAnnotated());
+ assertEquals(simpleString, TypeName.get(String.class));
+
+ TypeName annotated = simpleString.annotated(NEVER_NULL);
+ assertTrue(annotated.isAnnotated());
+ assertEquals(annotated, annotated.annotated());
+ }
+
+ @Test public void annotatedType() {
+ TypeName type = TypeName.get(String.class);
+ TypeName actual = type.annotated(TYPE_USE_ANNOTATION);
+ assertThat(actual.toString()).isEqualTo("java.lang. @" + TUA + " String");
+ }
+
+ @Test public void annotatedTwice() {
+ TypeName type = TypeName.get(String.class);
+ TypeName actual =
+ type.annotated(NEVER_NULL)
+ .annotated(TYPE_USE_ANNOTATION);
+ assertThat(actual.toString())
+ .isEqualTo("java.lang. @" + NN + " @" + TUA + " String");
+ }
+
+ @Test public void annotatedParameterizedType() {
+ TypeName type = ParameterizedTypeName.get(List.class, String.class);
+ TypeName actual = type.annotated(TYPE_USE_ANNOTATION);
+ assertThat(actual.toString()).isEqualTo("java.util. @" + TUA + " List<java.lang.String>");
+ }
+
+ @Test public void annotatedArgumentOfParameterizedType() {
+ TypeName type = TypeName.get(String.class).annotated(TYPE_USE_ANNOTATION);
+ TypeName actual = ParameterizedTypeName.get(ClassName.get(List.class), type);
+ assertThat(actual.toString()).isEqualTo("java.util.List<java.lang. @" + TUA + " String>");
+ }
+
+ @Test public void annotatedWildcardTypeNameWithSuper() {
+ TypeName type = TypeName.get(String.class).annotated(TYPE_USE_ANNOTATION);
+ TypeName actual = WildcardTypeName.supertypeOf(type);
+ assertThat(actual.toString()).isEqualTo("? super java.lang. @" + TUA + " String");
+ }
+
+ @Test public void annotatedWildcardTypeNameWithExtends() {
+ TypeName type = TypeName.get(String.class).annotated(TYPE_USE_ANNOTATION);
+ TypeName actual = WildcardTypeName.subtypeOf(type);
+ assertThat(actual.toString()).isEqualTo("? extends java.lang. @" + TUA + " String");
+ }
+
+ @Test public void annotatedEquivalence() {
+ annotatedEquivalence(TypeName.VOID);
+ annotatedEquivalence(ArrayTypeName.get(Object[].class));
+ annotatedEquivalence(ClassName.get(Object.class));
+ annotatedEquivalence(ParameterizedTypeName.get(List.class, Object.class));
+ annotatedEquivalence(TypeVariableName.get(Object.class));
+ annotatedEquivalence(WildcardTypeName.get(Object.class));
+ }
+
+ private void annotatedEquivalence(TypeName type) {
+ assertFalse(type.isAnnotated());
+ assertEquals(type, type);
+ assertEquals(type.annotated(TYPE_USE_ANNOTATION), type.annotated(TYPE_USE_ANNOTATION));
+ assertNotEquals(type, type.annotated(TYPE_USE_ANNOTATION));
+ assertEquals(type.hashCode(), type.hashCode());
+ assertEquals(type.annotated(TYPE_USE_ANNOTATION).hashCode(),
+ type.annotated(TYPE_USE_ANNOTATION).hashCode());
+ assertNotEquals(type.hashCode(), type.annotated(TYPE_USE_ANNOTATION).hashCode());
+ }
+
+ // https://github.com/square/javapoet/issues/431
+ @Test public void annotatedNestedType() {
+ TypeName type = TypeName.get(Map.Entry.class).annotated(TYPE_USE_ANNOTATION);
+ assertThat(type.toString()).isEqualTo("java.util.Map. @" + TUA + " Entry");
+ }
+
+ @Test public void annotatedEnclosingAndNestedType() {
+ TypeName type = ((ClassName) TypeName.get(Map.class).annotated(TYPE_USE_ANNOTATION))
+ .nestedClass("Entry").annotated(TYPE_USE_ANNOTATION);
+ assertThat(type.toString()).isEqualTo("java.util. @" + TUA + " Map. @" + TUA + " Entry");
+ }
+
+ // https://github.com/square/javapoet/issues/431
+ @Test public void annotatedNestedParameterizedType() {
+ TypeName type = ParameterizedTypeName.get(Map.Entry.class, Byte.class, Byte.class)
+ .annotated(TYPE_USE_ANNOTATION);
+ assertThat(type.toString())
+ .isEqualTo("java.util.Map. @" + TUA + " Entry<java.lang.Byte, java.lang.Byte>");
+ }
+
+ @Test public void withoutAnnotationsOnAnnotatedEnclosingAndNestedType() {
+ TypeName type = ((ClassName) TypeName.get(Map.class).annotated(TYPE_USE_ANNOTATION))
+ .nestedClass("Entry").annotated(TYPE_USE_ANNOTATION);
+ assertThat(type.isAnnotated()).isTrue();
+ assertThat(type.withoutAnnotations()).isEqualTo(TypeName.get(Map.Entry.class));
+ }
+
+ @Test public void withoutAnnotationsOnAnnotatedEnclosingType() {
+ TypeName type = ((ClassName) TypeName.get(Map.class).annotated(TYPE_USE_ANNOTATION))
+ .nestedClass("Entry");
+ assertThat(type.isAnnotated()).isTrue();
+ assertThat(type.withoutAnnotations()).isEqualTo(TypeName.get(Map.Entry.class));
+ }
+
+ @Test public void withoutAnnotationsOnAnnotatedNestedType() {
+ TypeName type = ((ClassName) TypeName.get(Map.class))
+ .nestedClass("Entry").annotated(TYPE_USE_ANNOTATION);
+ assertThat(type.isAnnotated()).isTrue();
+ assertThat(type.withoutAnnotations()).isEqualTo(TypeName.get(Map.Entry.class));
+ }
+
+ // https://github.com/square/javapoet/issues/614
+ @Test public void annotatedArrayType() {
+ TypeName type = ArrayTypeName.of(ClassName.get(Object.class)).annotated(TYPE_USE_ANNOTATION);
+ assertThat(type.toString()).isEqualTo("java.lang.Object @" + TUA + " []");
+ }
+
+ @Test public void annotatedArrayElementType() {
+ TypeName type = ArrayTypeName.of(ClassName.get(Object.class).annotated(TYPE_USE_ANNOTATION));
+ assertThat(type.toString()).isEqualTo("java.lang. @" + TUA + " Object[]");
+ }
+
+ // https://github.com/square/javapoet/issues/614
+ @Test public void annotatedOuterMultidimensionalArrayType() {
+ TypeName type = ArrayTypeName.of(ArrayTypeName.of(ClassName.get(Object.class)))
+ .annotated(TYPE_USE_ANNOTATION);
+ assertThat(type.toString()).isEqualTo("java.lang.Object @" + TUA + " [][]");
+ }
+
+ // https://github.com/square/javapoet/issues/614
+ @Test public void annotatedInnerMultidimensionalArrayType() {
+ TypeName type = ArrayTypeName.of(ArrayTypeName.of(ClassName.get(Object.class))
+ .annotated(TYPE_USE_ANNOTATION));
+ assertThat(type.toString()).isEqualTo("java.lang.Object[] @" + TUA + " []");
+ }
+
+ // https://github.com/square/javapoet/issues/614
+ @Test public void annotatedArrayTypeVarargsParameter() {
+ TypeName type = ArrayTypeName.of(ArrayTypeName.of(ClassName.get(Object.class)))
+ .annotated(TYPE_USE_ANNOTATION);
+ MethodSpec varargsMethod = MethodSpec.methodBuilder("m")
+ .addParameter(
+ ParameterSpec.builder(type, "p")
+ .build())
+ .varargs()
+ .build();
+ assertThat(varargsMethod.toString()).isEqualTo(""
+ + "void m(java.lang.Object @" + TUA + " []... p) {\n"
+ + "}\n");
+ }
+
+ // https://github.com/square/javapoet/issues/614
+ @Test public void annotatedArrayTypeInVarargsParameter() {
+ TypeName type = ArrayTypeName.of(ArrayTypeName.of(ClassName.get(Object.class))
+ .annotated(TYPE_USE_ANNOTATION));
+ MethodSpec varargsMethod = MethodSpec.methodBuilder("m")
+ .addParameter(
+ ParameterSpec.builder(type, "p")
+ .build())
+ .varargs()
+ .build();
+ assertThat(varargsMethod.toString()).isEqualTo(""
+ + "void m(java.lang.Object[] @" + TUA + " ... p) {\n"
+ + "}\n");
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/AnnotationSpecTest.java b/src/test/java/com/squareup/javapoet/AnnotationSpecTest.java
new file mode 100644
index 0000000..49606c7
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/AnnotationSpecTest.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import com.google.testing.compile.CompilationRule;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import javax.lang.model.element.TypeElement;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+public final class AnnotationSpecTest {
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface AnnotationA {
+ }
+
+ @Inherited
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface AnnotationB {
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface AnnotationC {
+ String value();
+ }
+
+ public enum Breakfast {
+ WAFFLES, PANCAKES;
+ public String toString() { return name() + " with cherries!"; };
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface HasDefaultsAnnotation {
+
+ byte a() default 5;
+
+ short b() default 6;
+
+ int c() default 7;
+
+ long d() default 8;
+
+ float e() default 9.0f;
+
+ double f() default 10.0;
+
+ char[] g() default {0, 0xCAFE, 'z', '€', 'ℕ', '"', '\'', '\t', '\n'};
+
+ boolean h() default true;
+
+ Breakfast i() default Breakfast.WAFFLES;
+
+ AnnotationA j() default @AnnotationA();
+
+ String k() default "maple";
+
+ Class<? extends Annotation> l() default AnnotationB.class;
+
+ int[] m() default {1, 2, 3};
+
+ Breakfast[] n() default {Breakfast.WAFFLES, Breakfast.PANCAKES};
+
+ Breakfast o();
+
+ int p();
+
+ AnnotationC q() default @AnnotationC("foo");
+
+ Class<? extends Number>[] r() default {Byte.class, Short.class, Integer.class, Long.class};
+
+ }
+
+ @HasDefaultsAnnotation(
+ o = Breakfast.PANCAKES,
+ p = 1701,
+ f = 11.1,
+ m = {9, 8, 1},
+ l = Override.class,
+ j = @AnnotationA,
+ q = @AnnotationC("bar"),
+ r = {Float.class, Double.class})
+ public class IsAnnotated {
+ // empty
+ }
+
+ @Rule public final CompilationRule compilation = new CompilationRule();
+
+ @Test public void equalsAndHashCode() {
+ AnnotationSpec a = AnnotationSpec.builder(AnnotationC.class).build();
+ AnnotationSpec b = AnnotationSpec.builder(AnnotationC.class).build();
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ a = AnnotationSpec.builder(AnnotationC.class).addMember("value", "$S", "123").build();
+ b = AnnotationSpec.builder(AnnotationC.class).addMember("value", "$S", "123").build();
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ }
+
+ @Test public void defaultAnnotation() {
+ String name = IsAnnotated.class.getCanonicalName();
+ TypeElement element = compilation.getElements().getTypeElement(name);
+ AnnotationSpec annotation = AnnotationSpec.get(element.getAnnotationMirrors().get(0));
+
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addAnnotation(annotation)
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import com.squareup.javapoet.AnnotationSpecTest;\n"
+ + "import java.lang.Double;\n"
+ + "import java.lang.Float;\n"
+ + "import java.lang.Override;\n"
+ + "\n"
+ + "@AnnotationSpecTest.HasDefaultsAnnotation(\n"
+ + " o = AnnotationSpecTest.Breakfast.PANCAKES,\n"
+ + " p = 1701,\n"
+ + " f = 11.1,\n"
+ + " m = {\n"
+ + " 9,\n"
+ + " 8,\n"
+ + " 1\n"
+ + " },\n"
+ + " l = Override.class,\n"
+ + " j = @AnnotationSpecTest.AnnotationA,\n"
+ + " q = @AnnotationSpecTest.AnnotationC(\"bar\"),\n"
+ + " r = {\n"
+ + " Float.class,\n"
+ + " Double.class\n"
+ + " }\n"
+ + ")\n"
+ + "class Taco {\n"
+ + "}\n");
+ }
+
+ @Test public void defaultAnnotationWithImport() {
+ String name = IsAnnotated.class.getCanonicalName();
+ TypeElement element = compilation.getElements().getTypeElement(name);
+ AnnotationSpec annotation = AnnotationSpec.get(element.getAnnotationMirrors().get(0));
+ TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(IsAnnotated.class.getSimpleName());
+ typeBuilder.addAnnotation(annotation);
+ JavaFile file = JavaFile.builder("com.squareup.javapoet", typeBuilder.build()).build();
+ assertThat(file.toString()).isEqualTo(
+ "package com.squareup.javapoet;\n"
+ + "\n"
+ + "import java.lang.Double;\n"
+ + "import java.lang.Float;\n"
+ + "import java.lang.Override;\n"
+ + "\n"
+ + "@AnnotationSpecTest.HasDefaultsAnnotation(\n"
+ + " o = AnnotationSpecTest.Breakfast.PANCAKES,\n"
+ + " p = 1701,\n"
+ + " f = 11.1,\n"
+ + " m = {\n"
+ + " 9,\n"
+ + " 8,\n"
+ + " 1\n"
+ + " },\n"
+ + " l = Override.class,\n"
+ + " j = @AnnotationSpecTest.AnnotationA,\n"
+ + " q = @AnnotationSpecTest.AnnotationC(\"bar\"),\n"
+ + " r = {\n"
+ + " Float.class,\n"
+ + " Double.class\n"
+ + " }\n"
+ + ")\n"
+ + "class IsAnnotated {\n"
+ + "}\n"
+ );
+ }
+
+ @Test public void emptyArray() {
+ AnnotationSpec.Builder builder = AnnotationSpec.builder(HasDefaultsAnnotation.class);
+ builder.addMember("n", "$L", "{}");
+ assertThat(builder.build().toString()).isEqualTo(
+ "@com.squareup.javapoet.AnnotationSpecTest.HasDefaultsAnnotation(" + "n = {}" + ")");
+ builder.addMember("m", "$L", "{}");
+ assertThat(builder.build().toString())
+ .isEqualTo(
+ "@com.squareup.javapoet.AnnotationSpecTest.HasDefaultsAnnotation("
+ + "n = {}, m = {}"
+ + ")");
+ }
+
+ @Test public void dynamicArrayOfEnumConstants() {
+ AnnotationSpec.Builder builder = AnnotationSpec.builder(HasDefaultsAnnotation.class);
+ builder.addMember("n", "$T.$L", Breakfast.class, Breakfast.PANCAKES.name());
+ assertThat(builder.build().toString()).isEqualTo(
+ "@com.squareup.javapoet.AnnotationSpecTest.HasDefaultsAnnotation("
+ + "n = com.squareup.javapoet.AnnotationSpecTest.Breakfast.PANCAKES"
+ + ")");
+
+ // builder = AnnotationSpec.builder(HasDefaultsAnnotation.class);
+ builder.addMember("n", "$T.$L", Breakfast.class, Breakfast.WAFFLES.name());
+ builder.addMember("n", "$T.$L", Breakfast.class, Breakfast.PANCAKES.name());
+ assertThat(builder.build().toString()).isEqualTo(
+ "@com.squareup.javapoet.AnnotationSpecTest.HasDefaultsAnnotation("
+ + "n = {"
+ + "com.squareup.javapoet.AnnotationSpecTest.Breakfast.PANCAKES"
+ + ", com.squareup.javapoet.AnnotationSpecTest.Breakfast.WAFFLES"
+ + ", com.squareup.javapoet.AnnotationSpecTest.Breakfast.PANCAKES"
+ + "})");
+
+ builder = builder.build().toBuilder(); // idempotent
+ assertThat(builder.build().toString()).isEqualTo(
+ "@com.squareup.javapoet.AnnotationSpecTest.HasDefaultsAnnotation("
+ + "n = {"
+ + "com.squareup.javapoet.AnnotationSpecTest.Breakfast.PANCAKES"
+ + ", com.squareup.javapoet.AnnotationSpecTest.Breakfast.WAFFLES"
+ + ", com.squareup.javapoet.AnnotationSpecTest.Breakfast.PANCAKES"
+ + "})");
+
+ builder.addMember("n", "$T.$L", Breakfast.class, Breakfast.WAFFLES.name());
+ assertThat(builder.build().toString()).isEqualTo(
+ "@com.squareup.javapoet.AnnotationSpecTest.HasDefaultsAnnotation("
+ + "n = {"
+ + "com.squareup.javapoet.AnnotationSpecTest.Breakfast.PANCAKES"
+ + ", com.squareup.javapoet.AnnotationSpecTest.Breakfast.WAFFLES"
+ + ", com.squareup.javapoet.AnnotationSpecTest.Breakfast.PANCAKES"
+ + ", com.squareup.javapoet.AnnotationSpecTest.Breakfast.WAFFLES"
+ + "})");
+ }
+
+ @Test public void defaultAnnotationToBuilder() {
+ String name = IsAnnotated.class.getCanonicalName();
+ TypeElement element = compilation.getElements().getTypeElement(name);
+ AnnotationSpec.Builder builder = AnnotationSpec.get(element.getAnnotationMirrors().get(0))
+ .toBuilder();
+ builder.addMember("m", "$L", 123);
+ assertThat(builder.build().toString()).isEqualTo(
+ "@com.squareup.javapoet.AnnotationSpecTest.HasDefaultsAnnotation("
+ + "o = com.squareup.javapoet.AnnotationSpecTest.Breakfast.PANCAKES"
+ + ", p = 1701"
+ + ", f = 11.1"
+ + ", m = {9, 8, 1, 123}"
+ + ", l = java.lang.Override.class"
+ + ", j = @com.squareup.javapoet.AnnotationSpecTest.AnnotationA"
+ + ", q = @com.squareup.javapoet.AnnotationSpecTest.AnnotationC(\"bar\")"
+ + ", r = {java.lang.Float.class, java.lang.Double.class}"
+ + ")");
+ }
+
+ @Test public void reflectAnnotation() {
+ HasDefaultsAnnotation annotation = IsAnnotated.class.getAnnotation(HasDefaultsAnnotation.class);
+ AnnotationSpec spec = AnnotationSpec.get(annotation);
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addAnnotation(spec)
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import com.squareup.javapoet.AnnotationSpecTest;\n"
+ + "import java.lang.Double;\n"
+ + "import java.lang.Float;\n"
+ + "import java.lang.Override;\n"
+ + "\n"
+ + "@AnnotationSpecTest.HasDefaultsAnnotation(\n"
+ + " f = 11.1,\n"
+ + " l = Override.class,\n"
+ + " m = {\n"
+ + " 9,\n"
+ + " 8,\n"
+ + " 1\n"
+ + " },\n"
+ + " o = AnnotationSpecTest.Breakfast.PANCAKES,\n"
+ + " p = 1701,\n"
+ + " q = @AnnotationSpecTest.AnnotationC(\"bar\"),\n"
+ + " r = {\n"
+ + " Float.class,\n"
+ + " Double.class\n"
+ + " }\n"
+ + ")\n"
+ + "class Taco {\n"
+ + "}\n");
+ }
+
+ @Test public void reflectAnnotationWithDefaults() {
+ HasDefaultsAnnotation annotation = IsAnnotated.class.getAnnotation(HasDefaultsAnnotation.class);
+ AnnotationSpec spec = AnnotationSpec.get(annotation, true);
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addAnnotation(spec)
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import com.squareup.javapoet.AnnotationSpecTest;\n"
+ + "import java.lang.Double;\n"
+ + "import java.lang.Float;\n"
+ + "import java.lang.Override;\n"
+ + "\n"
+ + "@AnnotationSpecTest.HasDefaultsAnnotation(\n"
+ + " a = 5,\n"
+ + " b = 6,\n"
+ + " c = 7,\n"
+ + " d = 8,\n"
+ + " e = 9.0f,\n"
+ + " f = 11.1,\n"
+ + " g = {\n"
+ + " '\\u0000',\n"
+ + " '쫾',\n"
+ + " 'z',\n"
+ + " '€',\n"
+ + " 'ℕ',\n"
+ + " '\"',\n"
+ + " '\\'',\n"
+ + " '\\t',\n"
+ + " '\\n'\n"
+ + " },\n"
+ + " h = true,\n"
+ + " i = AnnotationSpecTest.Breakfast.WAFFLES,\n"
+ + " j = @AnnotationSpecTest.AnnotationA,\n"
+ + " k = \"maple\",\n"
+ + " l = Override.class,\n"
+ + " m = {\n"
+ + " 9,\n"
+ + " 8,\n"
+ + " 1\n"
+ + " },\n"
+ + " n = {\n"
+ + " AnnotationSpecTest.Breakfast.WAFFLES,\n"
+ + " AnnotationSpecTest.Breakfast.PANCAKES\n"
+ + " },\n"
+ + " o = AnnotationSpecTest.Breakfast.PANCAKES,\n"
+ + " p = 1701,\n"
+ + " q = @AnnotationSpecTest.AnnotationC(\"bar\"),\n"
+ + " r = {\n"
+ + " Float.class,\n"
+ + " Double.class\n"
+ + " }\n"
+ + ")\n"
+ + "class Taco {\n"
+ + "}\n");
+ }
+
+ @Test public void disallowsNullMemberName() {
+ AnnotationSpec.Builder builder = AnnotationSpec.builder(HasDefaultsAnnotation.class);
+ try {
+ AnnotationSpec.Builder $L = builder.addMember(null, "$L", "");
+ fail($L.build().toString());
+ } catch (NullPointerException e) {
+ assertThat(e).hasMessageThat().isEqualTo("name == null");
+ }
+ }
+
+ @Test public void requiresValidMemberName() {
+ AnnotationSpec.Builder builder = AnnotationSpec.builder(HasDefaultsAnnotation.class);
+ try {
+ AnnotationSpec.Builder $L = builder.addMember("@", "$L", "");
+ fail($L.build().toString());
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessageThat().isEqualTo("not a valid name: @");
+ }
+ }
+
+ private String toString(TypeSpec typeSpec) {
+ return JavaFile.builder("com.squareup.tacos", typeSpec).build().toString();
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/ClassNameTest.java b/src/test/java/com/squareup/javapoet/ClassNameTest.java
new file mode 100644
index 0000000..e2cc55e
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/ClassNameTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import com.google.testing.compile.CompilationRule;
+import java.util.Map;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
+
+@RunWith(JUnit4.class)
+public final class ClassNameTest {
+ @Rule public CompilationRule compilationRule = new CompilationRule();
+
+ @Test public void bestGuessForString_simpleClass() {
+ assertThat(ClassName.bestGuess(String.class.getName()))
+ .isEqualTo(ClassName.get("java.lang", "String"));
+ }
+
+ @Test public void bestGuessNonAscii() {
+ ClassName className = ClassName.bestGuess(
+ "com.\ud835\udc1andro\ud835\udc22d.\ud835\udc00ctiv\ud835\udc22ty");
+ assertEquals("com.\ud835\udc1andro\ud835\udc22d", className.packageName());
+ assertEquals("\ud835\udc00ctiv\ud835\udc22ty", className.simpleName());
+ }
+
+ static class OuterClass {
+ static class InnerClass {}
+ }
+
+ @Test public void bestGuessForString_nestedClass() {
+ assertThat(ClassName.bestGuess(Map.Entry.class.getCanonicalName()))
+ .isEqualTo(ClassName.get("java.util", "Map", "Entry"));
+ assertThat(ClassName.bestGuess(OuterClass.InnerClass.class.getCanonicalName()))
+ .isEqualTo(ClassName.get("com.squareup.javapoet",
+ "ClassNameTest", "OuterClass", "InnerClass"));
+ }
+
+ @Test public void bestGuessForString_defaultPackage() {
+ assertThat(ClassName.bestGuess("SomeClass"))
+ .isEqualTo(ClassName.get("", "SomeClass"));
+ assertThat(ClassName.bestGuess("SomeClass.Nested"))
+ .isEqualTo(ClassName.get("", "SomeClass", "Nested"));
+ assertThat(ClassName.bestGuess("SomeClass.Nested.EvenMore"))
+ .isEqualTo(ClassName.get("", "SomeClass", "Nested", "EvenMore"));
+ }
+
+ @Test public void bestGuessForString_confusingInput() {
+ assertBestGuessThrows("");
+ assertBestGuessThrows(".");
+ assertBestGuessThrows(".Map");
+ assertBestGuessThrows("java");
+ assertBestGuessThrows("java.util");
+ assertBestGuessThrows("java.util.");
+ assertBestGuessThrows("java..util.Map.Entry");
+ assertBestGuessThrows("java.util..Map.Entry");
+ assertBestGuessThrows("java.util.Map..Entry");
+ assertBestGuessThrows("com.test.$");
+ assertBestGuessThrows("com.test.LooksLikeAClass.pkg");
+ assertBestGuessThrows("!@#$gibberish%^&*");
+ }
+
+ private void assertBestGuessThrows(String s) {
+ try {
+ ClassName.bestGuess(s);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test public void createNestedClass() {
+ ClassName foo = ClassName.get("com.example", "Foo");
+ ClassName bar = foo.nestedClass("Bar");
+ assertThat(bar).isEqualTo(ClassName.get("com.example", "Foo", "Bar"));
+ ClassName baz = bar.nestedClass("Baz");
+ assertThat(baz).isEqualTo(ClassName.get("com.example", "Foo", "Bar", "Baz"));
+ }
+
+ static class $Outer {
+ static class $Inner {}
+ }
+
+ @Test public void classNameFromTypeElement() {
+ Elements elements = compilationRule.getElements();
+ TypeElement object = elements.getTypeElement(Object.class.getCanonicalName());
+ assertThat(ClassName.get(object).toString()).isEqualTo("java.lang.Object");
+ TypeElement outer = elements.getTypeElement($Outer.class.getCanonicalName());
+ assertThat(ClassName.get(outer).toString()).isEqualTo("com.squareup.javapoet.ClassNameTest.$Outer");
+ TypeElement inner = elements.getTypeElement($Outer.$Inner.class.getCanonicalName());
+ assertThat(ClassName.get(inner).toString()).isEqualTo("com.squareup.javapoet.ClassNameTest.$Outer.$Inner");
+ }
+
+ /**
+ * Buck builds with "source-based ABI generation" and those builds don't support
+ * {@link TypeElement#getKind()}. Test to confirm that we don't use that API.
+ */
+ @Test public void classNameFromTypeElementDoesntUseGetKind() {
+ Elements elements = compilationRule.getElements();
+ TypeElement object = elements.getTypeElement(Object.class.getCanonicalName());
+ assertThat(ClassName.get(preventGetKind(object)).toString())
+ .isEqualTo("java.lang.Object");
+ TypeElement outer = elements.getTypeElement($Outer.class.getCanonicalName());
+ assertThat(ClassName.get(preventGetKind(outer)).toString())
+ .isEqualTo("com.squareup.javapoet.ClassNameTest.$Outer");
+ TypeElement inner = elements.getTypeElement($Outer.$Inner.class.getCanonicalName());
+ assertThat(ClassName.get(preventGetKind(inner)).toString())
+ .isEqualTo("com.squareup.javapoet.ClassNameTest.$Outer.$Inner");
+ }
+
+ /** Returns a new instance like {@code object} that throws on {@code getKind()}. */
+ private TypeElement preventGetKind(TypeElement object) {
+ TypeElement spy = Mockito.spy(object);
+ when(spy.getKind()).thenThrow(new AssertionError());
+ when(spy.getEnclosingElement()).thenAnswer(invocation -> {
+ Object enclosingElement = invocation.callRealMethod();
+ return enclosingElement instanceof TypeElement
+ ? preventGetKind((TypeElement) enclosingElement)
+ : enclosingElement;
+ });
+ return spy;
+ }
+
+ @Test public void classNameFromClass() {
+ assertThat(ClassName.get(Object.class).toString())
+ .isEqualTo("java.lang.Object");
+ assertThat(ClassName.get(OuterClass.InnerClass.class).toString())
+ .isEqualTo("com.squareup.javapoet.ClassNameTest.OuterClass.InnerClass");
+ assertThat((ClassName.get(new Object() {}.getClass())).toString())
+ .isEqualTo("com.squareup.javapoet.ClassNameTest$1");
+ assertThat((ClassName.get(new Object() { Object inner = new Object() {}; }.inner.getClass())).toString())
+ .isEqualTo("com.squareup.javapoet.ClassNameTest$2$1");
+ assertThat((ClassName.get($Outer.class)).toString())
+ .isEqualTo("com.squareup.javapoet.ClassNameTest.$Outer");
+ assertThat((ClassName.get($Outer.$Inner.class)).toString())
+ .isEqualTo("com.squareup.javapoet.ClassNameTest.$Outer.$Inner");
+ }
+
+ @Test public void peerClass() {
+ assertThat(ClassName.get(Double.class).peerClass("Short"))
+ .isEqualTo(ClassName.get(Short.class));
+ assertThat(ClassName.get("", "Double").peerClass("Short"))
+ .isEqualTo(ClassName.get("", "Short"));
+ assertThat(ClassName.get("a.b", "Combo", "Taco").peerClass("Burrito"))
+ .isEqualTo(ClassName.get("a.b", "Combo", "Burrito"));
+ }
+
+ @Test public void fromClassRejectionTypes() {
+ try {
+ ClassName.get(int.class);
+ fail();
+ } catch (IllegalArgumentException ignored) {
+ }
+ try {
+ ClassName.get(void.class);
+ fail();
+ } catch (IllegalArgumentException ignored) {
+ }
+ try {
+ ClassName.get(Object[].class);
+ fail();
+ } catch (IllegalArgumentException ignored) {
+ }
+ }
+
+ @Test
+ public void reflectionName() {
+ assertEquals("java.lang.Object", TypeName.OBJECT.reflectionName());
+ assertEquals("java.lang.Thread$State", ClassName.get(Thread.State.class).reflectionName());
+ assertEquals("java.util.Map$Entry", ClassName.get(Map.Entry.class).reflectionName());
+ assertEquals("Foo", ClassName.get("", "Foo").reflectionName());
+ assertEquals("Foo$Bar$Baz", ClassName.get("", "Foo", "Bar", "Baz").reflectionName());
+ assertEquals("a.b.c.Foo$Bar$Baz", ClassName.get("a.b.c", "Foo", "Bar", "Baz").reflectionName());
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/CodeBlockTest.java b/src/test/java/com/squareup/javapoet/CodeBlockTest.java
new file mode 100644
index 0000000..2862809
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/CodeBlockTest.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public final class CodeBlockTest {
+ @Test public void equalsAndHashCode() {
+ CodeBlock a = CodeBlock.builder().build();
+ CodeBlock b = CodeBlock.builder().build();
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ a = CodeBlock.builder().add("$L", "taco").build();
+ b = CodeBlock.builder().add("$L", "taco").build();
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ }
+
+ @Test public void of() {
+ CodeBlock a = CodeBlock.of("$L taco", "delicious");
+ assertThat(a.toString()).isEqualTo("delicious taco");
+ }
+
+ @Test public void isEmpty() {
+ assertTrue(CodeBlock.builder().isEmpty());
+ assertTrue(CodeBlock.builder().add("").isEmpty());
+ assertFalse(CodeBlock.builder().add(" ").isEmpty());
+ }
+
+ @Test public void indentCannotBeIndexed() {
+ try {
+ CodeBlock.builder().add("$1>", "taco").build();
+ fail();
+ } catch (IllegalArgumentException exp) {
+ assertThat(exp)
+ .hasMessageThat()
+ .isEqualTo("$$, $>, $<, $[, $], $W, and $Z may not have an index");
+ }
+ }
+
+ @Test public void deindentCannotBeIndexed() {
+ try {
+ CodeBlock.builder().add("$1<", "taco").build();
+ fail();
+ } catch (IllegalArgumentException exp) {
+ assertThat(exp)
+ .hasMessageThat()
+ .isEqualTo("$$, $>, $<, $[, $], $W, and $Z may not have an index");
+ }
+ }
+
+ @Test public void dollarSignEscapeCannotBeIndexed() {
+ try {
+ CodeBlock.builder().add("$1$", "taco").build();
+ fail();
+ } catch (IllegalArgumentException exp) {
+ assertThat(exp)
+ .hasMessageThat()
+ .isEqualTo("$$, $>, $<, $[, $], $W, and $Z may not have an index");
+ }
+ }
+
+ @Test public void statementBeginningCannotBeIndexed() {
+ try {
+ CodeBlock.builder().add("$1[", "taco").build();
+ fail();
+ } catch (IllegalArgumentException exp) {
+ assertThat(exp)
+ .hasMessageThat()
+ .isEqualTo("$$, $>, $<, $[, $], $W, and $Z may not have an index");
+ }
+ }
+
+ @Test public void statementEndingCannotBeIndexed() {
+ try {
+ CodeBlock.builder().add("$1]", "taco").build();
+ fail();
+ } catch (IllegalArgumentException exp) {
+ assertThat(exp)
+ .hasMessageThat()
+ .isEqualTo("$$, $>, $<, $[, $], $W, and $Z may not have an index");
+ }
+ }
+
+ @Test public void nameFormatCanBeIndexed() {
+ CodeBlock block = CodeBlock.builder().add("$1N", "taco").build();
+ assertThat(block.toString()).isEqualTo("taco");
+ }
+
+ @Test public void literalFormatCanBeIndexed() {
+ CodeBlock block = CodeBlock.builder().add("$1L", "taco").build();
+ assertThat(block.toString()).isEqualTo("taco");
+ }
+
+ @Test public void stringFormatCanBeIndexed() {
+ CodeBlock block = CodeBlock.builder().add("$1S", "taco").build();
+ assertThat(block.toString()).isEqualTo("\"taco\"");
+ }
+
+ @Test public void typeFormatCanBeIndexed() {
+ CodeBlock block = CodeBlock.builder().add("$1T", String.class).build();
+ assertThat(block.toString()).isEqualTo("java.lang.String");
+ }
+
+ @Test public void simpleNamedArgument() {
+ Map<String, Object> map = new LinkedHashMap<>();
+ map.put("text", "taco");
+ CodeBlock block = CodeBlock.builder().addNamed("$text:S", map).build();
+ assertThat(block.toString()).isEqualTo("\"taco\"");
+ }
+
+ @Test public void repeatedNamedArgument() {
+ Map<String, Object> map = new LinkedHashMap<>();
+ map.put("text", "tacos");
+ CodeBlock block = CodeBlock.builder()
+ .addNamed("\"I like \" + $text:S + \". Do you like \" + $text:S + \"?\"", map)
+ .build();
+ assertThat(block.toString()).isEqualTo(
+ "\"I like \" + \"tacos\" + \". Do you like \" + \"tacos\" + \"?\"");
+ }
+
+ @Test public void namedAndNoArgFormat() {
+ Map<String, Object> map = new LinkedHashMap<>();
+ map.put("text", "tacos");
+ CodeBlock block = CodeBlock.builder()
+ .addNamed("$>\n$text:L for $$3.50", map).build();
+ assertThat(block.toString()).isEqualTo("\n tacos for $3.50");
+ }
+
+ @Test public void missingNamedArgument() {
+ try {
+ Map<String, Object> map = new LinkedHashMap<>();
+ CodeBlock.builder().addNamed("$text:S", map).build();
+ fail();
+ } catch(IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("Missing named argument for $text");
+ }
+ }
+
+ @Test public void lowerCaseNamed() {
+ try {
+ Map<String, Object> map = new LinkedHashMap<>();
+ map.put("Text", "tacos");
+ CodeBlock block = CodeBlock.builder().addNamed("$Text:S", map).build();
+ fail();
+ } catch(IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("argument 'Text' must start with a lowercase character");
+ }
+ }
+
+ @Test public void multipleNamedArguments() {
+ Map<String, Object> map = new LinkedHashMap<>();
+ map.put("pipe", System.class);
+ map.put("text", "tacos");
+
+ CodeBlock block = CodeBlock.builder()
+ .addNamed("$pipe:T.out.println(\"Let's eat some $text:L\");", map)
+ .build();
+
+ assertThat(block.toString()).isEqualTo(
+ "java.lang.System.out.println(\"Let's eat some tacos\");");
+ }
+
+ @Test public void namedNewline() {
+ Map<String, Object> map = new LinkedHashMap<>();
+ map.put("clazz", Integer.class);
+ CodeBlock block = CodeBlock.builder().addNamed("$clazz:T\n", map).build();
+ assertThat(block.toString()).isEqualTo("java.lang.Integer\n");
+ }
+
+ @Test public void danglingNamed() {
+ Map<String, Object> map = new LinkedHashMap<>();
+ map.put("clazz", Integer.class);
+ try {
+ CodeBlock.builder().addNamed("$clazz:T$", map).build();
+ fail();
+ } catch(IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("dangling $ at end");
+ }
+ }
+
+ @Test public void indexTooHigh() {
+ try {
+ CodeBlock.builder().add("$2T", String.class).build();
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("index 2 for '$2T' not in range (received 1 arguments)");
+ }
+ }
+
+ @Test public void indexIsZero() {
+ try {
+ CodeBlock.builder().add("$0T", String.class).build();
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("index 0 for '$0T' not in range (received 1 arguments)");
+ }
+ }
+
+ @Test public void indexIsNegative() {
+ try {
+ CodeBlock.builder().add("$-1T", String.class).build();
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("invalid format string: '$-1T'");
+ }
+ }
+
+ @Test public void indexWithoutFormatType() {
+ try {
+ CodeBlock.builder().add("$1", String.class).build();
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("dangling format characters in '$1'");
+ }
+ }
+
+ @Test public void indexWithoutFormatTypeNotAtStringEnd() {
+ try {
+ CodeBlock.builder().add("$1 taco", String.class).build();
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("invalid format string: '$1 taco'");
+ }
+ }
+
+ @Test public void indexButNoArguments() {
+ try {
+ CodeBlock.builder().add("$1T").build();
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("index 1 for '$1T' not in range (received 0 arguments)");
+ }
+ }
+
+ @Test public void formatIndicatorAlone() {
+ try {
+ CodeBlock.builder().add("$", String.class).build();
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("dangling format characters in '$'");
+ }
+ }
+
+ @Test public void formatIndicatorWithoutIndexOrFormatType() {
+ try {
+ CodeBlock.builder().add("$ tacoString", String.class).build();
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("invalid format string: '$ tacoString'");
+ }
+ }
+
+ @Test public void sameIndexCanBeUsedWithDifferentFormats() {
+ CodeBlock block = CodeBlock.builder()
+ .add("$1T.out.println($1S)", ClassName.get(System.class))
+ .build();
+ assertThat(block.toString()).isEqualTo("java.lang.System.out.println(\"java.lang.System\")");
+ }
+
+ @Test public void tooManyStatementEnters() {
+ CodeBlock codeBlock = CodeBlock.builder().add("$[$[").build();
+ try {
+ // We can't report this error until rendering type because code blocks might be composed.
+ codeBlock.toString();
+ fail();
+ } catch (IllegalStateException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("statement enter $[ followed by statement enter $[");
+ }
+ }
+
+ @Test public void statementExitWithoutStatementEnter() {
+ CodeBlock codeBlock = CodeBlock.builder().add("$]").build();
+ try {
+ // We can't report this error until rendering type because code blocks might be composed.
+ codeBlock.toString();
+ fail();
+ } catch (IllegalStateException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("statement exit $] has no matching statement enter $[");
+ }
+ }
+
+ @Test public void join() {
+ List<CodeBlock> codeBlocks = new ArrayList<>();
+ codeBlocks.add(CodeBlock.of("$S", "hello"));
+ codeBlocks.add(CodeBlock.of("$T", ClassName.get("world", "World")));
+ codeBlocks.add(CodeBlock.of("need tacos"));
+
+ CodeBlock joined = CodeBlock.join(codeBlocks, " || ");
+ assertThat(joined.toString()).isEqualTo("\"hello\" || world.World || need tacos");
+ }
+
+ @Test public void joining() {
+ List<CodeBlock> codeBlocks = new ArrayList<>();
+ codeBlocks.add(CodeBlock.of("$S", "hello"));
+ codeBlocks.add(CodeBlock.of("$T", ClassName.get("world", "World")));
+ codeBlocks.add(CodeBlock.of("need tacos"));
+
+ CodeBlock joined = codeBlocks.stream().collect(CodeBlock.joining(" || "));
+ assertThat(joined.toString()).isEqualTo("\"hello\" || world.World || need tacos");
+ }
+
+ @Test public void joiningSingle() {
+ List<CodeBlock> codeBlocks = new ArrayList<>();
+ codeBlocks.add(CodeBlock.of("$S", "hello"));
+
+ CodeBlock joined = codeBlocks.stream().collect(CodeBlock.joining(" || "));
+ assertThat(joined.toString()).isEqualTo("\"hello\"");
+ }
+
+ @Test public void joiningWithPrefixAndSuffix() {
+ List<CodeBlock> codeBlocks = new ArrayList<>();
+ codeBlocks.add(CodeBlock.of("$S", "hello"));
+ codeBlocks.add(CodeBlock.of("$T", ClassName.get("world", "World")));
+ codeBlocks.add(CodeBlock.of("need tacos"));
+
+ CodeBlock joined = codeBlocks.stream().collect(CodeBlock.joining(" || ", "start {", "} end"));
+ assertThat(joined.toString()).isEqualTo("start {\"hello\" || world.World || need tacos} end");
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/FieldSpecTest.java b/src/test/java/com/squareup/javapoet/FieldSpecTest.java
new file mode 100644
index 0000000..63f7aa8
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/FieldSpecTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import javax.lang.model.element.Modifier;
+
+public class FieldSpecTest {
+ @Test public void equalsAndHashCode() {
+ FieldSpec a = FieldSpec.builder(int.class, "foo").build();
+ FieldSpec b = FieldSpec.builder(int.class, "foo").build();
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ a = FieldSpec.builder(int.class, "FOO", Modifier.PUBLIC, Modifier.STATIC).build();
+ b = FieldSpec.builder(int.class, "FOO", Modifier.PUBLIC, Modifier.STATIC).build();
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ }
+
+ @Test public void nullAnnotationsAddition() {
+ try {
+ FieldSpec.builder(int.class, "foo").addAnnotations(null);
+ fail();
+ }
+ catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage())
+ .isEqualTo("annotationSpecs == null");
+ }
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/squareup/javapoet/FileReadingTest.java b/src/test/java/com/squareup/javapoet/FileReadingTest.java
new file mode 100644
index 0000000..eb19de0
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/FileReadingTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.concurrent.Callable;
+import javax.lang.model.element.Modifier;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaCompiler.CompilationTask;
+import javax.tools.JavaFileObject;
+import javax.tools.JavaFileObject.Kind;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+@RunWith(JUnit4.class)
+public class FileReadingTest {
+
+ // Used for storing compilation output.
+ @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Test public void javaFileObjectUri() {
+ TypeSpec type = TypeSpec.classBuilder("Test").build();
+ assertThat(JavaFile.builder("", type).build().toJavaFileObject().toUri())
+ .isEqualTo(URI.create("Test.java"));
+ assertThat(JavaFile.builder("foo", type).build().toJavaFileObject().toUri())
+ .isEqualTo(URI.create("foo/Test.java"));
+ assertThat(JavaFile.builder("com.example", type).build().toJavaFileObject().toUri())
+ .isEqualTo(URI.create("com/example/Test.java"));
+ }
+
+ @Test public void javaFileObjectKind() {
+ JavaFile javaFile = JavaFile.builder("", TypeSpec.classBuilder("Test").build()).build();
+ assertThat(javaFile.toJavaFileObject().getKind()).isEqualTo(Kind.SOURCE);
+ }
+
+ @Test public void javaFileObjectCharacterContent() throws IOException {
+ TypeSpec type = TypeSpec.classBuilder("Test")
+ .addJavadoc("Pi\u00f1ata\u00a1")
+ .addMethod(MethodSpec.methodBuilder("fooBar").build())
+ .build();
+ JavaFile javaFile = JavaFile.builder("foo", type).build();
+ JavaFileObject javaFileObject = javaFile.toJavaFileObject();
+
+ // We can never have encoding issues (everything is in process)
+ assertThat(javaFileObject.getCharContent(true)).isEqualTo(javaFile.toString());
+ assertThat(javaFileObject.getCharContent(false)).isEqualTo(javaFile.toString());
+ }
+
+ @Test public void javaFileObjectInputStreamIsUtf8() throws IOException {
+ JavaFile javaFile = JavaFile.builder("foo", TypeSpec.classBuilder("Test").build())
+ .addFileComment("Pi\u00f1ata\u00a1")
+ .build();
+ byte[] bytes = ByteStreams.toByteArray(javaFile.toJavaFileObject().openInputStream());
+
+ // JavaPoet always uses UTF-8.
+ assertThat(bytes).isEqualTo(javaFile.toString().getBytes(UTF_8));
+ }
+
+ @Test public void compileJavaFile() throws Exception {
+ final String value = "Hello World!";
+ TypeSpec type = TypeSpec.classBuilder("Test")
+ .addModifiers(Modifier.PUBLIC)
+ .addSuperinterface(ParameterizedTypeName.get(Callable.class, String.class))
+ .addMethod(MethodSpec.methodBuilder("call")
+ .returns(String.class)
+ .addModifiers(Modifier.PUBLIC)
+ .addStatement("return $S", value)
+ .build())
+ .build();
+ JavaFile javaFile = JavaFile.builder("foo", type).build();
+
+ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+ DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
+ StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnosticCollector,
+ Locale.getDefault(), UTF_8);
+ fileManager.setLocation(StandardLocation.CLASS_OUTPUT,
+ Collections.singleton(temporaryFolder.newFolder()));
+ CompilationTask task = compiler.getTask(null,
+ fileManager,
+ diagnosticCollector,
+ Collections.emptySet(),
+ Collections.emptySet(),
+ Collections.singleton(javaFile.toJavaFileObject()));
+
+ assertThat(task.call()).isTrue();
+ assertThat(diagnosticCollector.getDiagnostics()).isEmpty();
+
+ ClassLoader loader = fileManager.getClassLoader(StandardLocation.CLASS_OUTPUT);
+ Callable<?> test = Class.forName("foo.Test", true, loader)
+ .asSubclass(Callable.class)
+ .getDeclaredConstructor()
+ .newInstance();
+ assertThat(Callable.class.getMethod("call").invoke(test)).isEqualTo(value);
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/FileWritingTest.java b/src/test/java/com/squareup/javapoet/FileWritingTest.java
new file mode 100644
index 0000000..f817ddb
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/FileWritingTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import com.google.common.jimfs.Configuration;
+import com.google.common.jimfs.Jimfs;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Date;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Modifier;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.fail;
+
+@RunWith(JUnit4.class)
+public final class FileWritingTest {
+ // Used for testing java.io File behavior.
+ @Rule public final TemporaryFolder tmp = new TemporaryFolder();
+
+ // Used for testing java.nio.file Path behavior.
+ private final FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
+ private final Path fsRoot = fs.getRootDirectories().iterator().next();
+
+ // Used for testing annotation processor Filer behavior.
+ private final TestFiler filer = new TestFiler(fs, fsRoot);
+
+ @Test public void pathNotDirectory() throws IOException {
+ TypeSpec type = TypeSpec.classBuilder("Test").build();
+ JavaFile javaFile = JavaFile.builder("example", type).build();
+ Path path = fs.getPath("/foo/bar");
+ Files.createDirectories(path.getParent());
+ Files.createFile(path);
+ try {
+ javaFile.writeTo(path);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage()).isEqualTo("path /foo/bar exists but is not a directory.");
+ }
+ }
+
+ @Test public void fileNotDirectory() throws IOException {
+ TypeSpec type = TypeSpec.classBuilder("Test").build();
+ JavaFile javaFile = JavaFile.builder("example", type).build();
+ File file = new File(tmp.newFolder("foo"), "bar");
+ file.createNewFile();
+ try {
+ javaFile.writeTo(file);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage()).isEqualTo(
+ "path " + file.getPath() + " exists but is not a directory.");
+ }
+ }
+
+ @Test public void pathDefaultPackage() throws IOException {
+ TypeSpec type = TypeSpec.classBuilder("Test").build();
+ JavaFile.builder("", type).build().writeTo(fsRoot);
+
+ Path testPath = fsRoot.resolve("Test.java");
+ assertThat(Files.exists(testPath)).isTrue();
+ }
+
+ @Test public void fileDefaultPackage() throws IOException {
+ TypeSpec type = TypeSpec.classBuilder("Test").build();
+ JavaFile.builder("", type).build().writeTo(tmp.getRoot());
+
+ File testFile = new File(tmp.getRoot(), "Test.java");
+ assertThat(testFile.exists()).isTrue();
+ }
+
+ @Test public void filerDefaultPackage() throws IOException {
+ TypeSpec type = TypeSpec.classBuilder("Test").build();
+ JavaFile.builder("", type).build().writeTo(filer);
+
+ Path testPath = fsRoot.resolve("Test.java");
+ assertThat(Files.exists(testPath)).isTrue();
+ }
+
+ @Test public void pathNestedClasses() throws IOException {
+ TypeSpec type = TypeSpec.classBuilder("Test").build();
+ JavaFile.builder("foo", type).build().writeTo(fsRoot);
+ JavaFile.builder("foo.bar", type).build().writeTo(fsRoot);
+ JavaFile.builder("foo.bar.baz", type).build().writeTo(fsRoot);
+
+ Path fooPath = fsRoot.resolve(fs.getPath("foo", "Test.java"));
+ Path barPath = fsRoot.resolve(fs.getPath("foo", "bar", "Test.java"));
+ Path bazPath = fsRoot.resolve(fs.getPath("foo", "bar", "baz", "Test.java"));
+ assertThat(Files.exists(fooPath)).isTrue();
+ assertThat(Files.exists(barPath)).isTrue();
+ assertThat(Files.exists(bazPath)).isTrue();
+ }
+
+ @Test public void fileNestedClasses() throws IOException {
+ TypeSpec type = TypeSpec.classBuilder("Test").build();
+ JavaFile.builder("foo", type).build().writeTo(tmp.getRoot());
+ JavaFile.builder("foo.bar", type).build().writeTo(tmp.getRoot());
+ JavaFile.builder("foo.bar.baz", type).build().writeTo(tmp.getRoot());
+
+ File fooDir = new File(tmp.getRoot(), "foo");
+ File fooFile = new File(fooDir, "Test.java");
+ File barDir = new File(fooDir, "bar");
+ File barFile = new File(barDir, "Test.java");
+ File bazDir = new File(barDir, "baz");
+ File bazFile = new File(bazDir, "Test.java");
+ assertThat(fooFile.exists()).isTrue();
+ assertThat(barFile.exists()).isTrue();
+ assertThat(bazFile.exists()).isTrue();
+ }
+
+ @Test public void filerNestedClasses() throws IOException {
+ TypeSpec type = TypeSpec.classBuilder("Test").build();
+ JavaFile.builder("foo", type).build().writeTo(filer);
+ JavaFile.builder("foo.bar", type).build().writeTo(filer);
+ JavaFile.builder("foo.bar.baz", type).build().writeTo(filer);
+
+ Path fooPath = fsRoot.resolve(fs.getPath("foo", "Test.java"));
+ Path barPath = fsRoot.resolve(fs.getPath("foo", "bar", "Test.java"));
+ Path bazPath = fsRoot.resolve(fs.getPath("foo", "bar", "baz", "Test.java"));
+ assertThat(Files.exists(fooPath)).isTrue();
+ assertThat(Files.exists(barPath)).isTrue();
+ assertThat(Files.exists(bazPath)).isTrue();
+ }
+
+ @Test public void filerPassesOriginatingElements() throws IOException {
+ Element element1_1 = Mockito.mock(Element.class);
+ TypeSpec test1 = TypeSpec.classBuilder("Test1")
+ .addOriginatingElement(element1_1)
+ .build();
+
+ Element element2_1 = Mockito.mock(Element.class);
+ Element element2_2 = Mockito.mock(Element.class);
+ TypeSpec test2 = TypeSpec.classBuilder("Test2")
+ .addOriginatingElement(element2_1)
+ .addOriginatingElement(element2_2)
+ .build();
+
+ JavaFile.builder("example", test1).build().writeTo(filer);
+ JavaFile.builder("example", test2).build().writeTo(filer);
+
+ Path testPath1 = fsRoot.resolve(fs.getPath("example", "Test1.java"));
+ assertThat(filer.getOriginatingElements(testPath1)).containsExactly(element1_1);
+ Path testPath2 = fsRoot.resolve(fs.getPath("example", "Test2.java"));
+ assertThat(filer.getOriginatingElements(testPath2)).containsExactly(element2_1, element2_2);
+ }
+
+ @Test public void filerClassesWithTabIndent() throws IOException {
+ TypeSpec test = TypeSpec.classBuilder("Test")
+ .addField(Date.class, "madeFreshDate")
+ .addMethod(MethodSpec.methodBuilder("main")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .addParameter(String[].class, "args")
+ .addCode("$T.out.println($S);\n", System.class, "Hello World!")
+ .build())
+ .build();
+ JavaFile.builder("foo", test).indent("\t").build().writeTo(filer);
+
+ Path fooPath = fsRoot.resolve(fs.getPath("foo", "Test.java"));
+ assertThat(Files.exists(fooPath)).isTrue();
+ String source = new String(Files.readAllBytes(fooPath));
+
+ assertThat(source).isEqualTo(""
+ + "package foo;\n"
+ + "\n"
+ + "import java.lang.String;\n"
+ + "import java.lang.System;\n"
+ + "import java.util.Date;\n"
+ + "\n"
+ + "class Test {\n"
+ + "\tDate madeFreshDate;\n"
+ + "\n"
+ + "\tpublic static void main(String[] args) {\n"
+ + "\t\tSystem.out.println(\"Hello World!\");\n"
+ + "\t}\n"
+ + "}\n");
+ }
+
+ /**
+ * This test confirms that JavaPoet ignores the host charset and always uses UTF-8. The host
+ * charset is customized with {@code -Dfile.encoding=ISO-8859-1}.
+ */
+ @Test public void fileIsUtf8() throws IOException {
+ JavaFile javaFile = JavaFile.builder("foo", TypeSpec.classBuilder("Taco").build())
+ .addFileComment("Pi\u00f1ata\u00a1")
+ .build();
+ javaFile.writeTo(fsRoot);
+
+ Path fooPath = fsRoot.resolve(fs.getPath("foo", "Taco.java"));
+ assertThat(new String(Files.readAllBytes(fooPath), UTF_8)).isEqualTo(""
+ + "// Pi\u00f1ata\u00a1\n"
+ + "package foo;\n"
+ + "\n"
+ + "class Taco {\n"
+ + "}\n");
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/JavaFileTest.java b/src/test/java/com/squareup/javapoet/JavaFileTest.java
new file mode 100644
index 0000000..e056116
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/JavaFileTest.java
@@ -0,0 +1,692 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import javax.lang.model.element.Modifier;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(JUnit4.class)
+public final class JavaFileTest {
+ @Test public void importStaticReadmeExample() {
+ ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
+ ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards");
+ ClassName list = ClassName.get("java.util", "List");
+ ClassName arrayList = ClassName.get("java.util", "ArrayList");
+ TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);
+ MethodSpec beyond = MethodSpec.methodBuilder("beyond")
+ .returns(listOfHoverboards)
+ .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
+ .addStatement("result.add($T.createNimbus(2000))", hoverboard)
+ .addStatement("result.add($T.createNimbus(\"2001\"))", hoverboard)
+ .addStatement("result.add($T.createNimbus($T.THUNDERBOLT))", hoverboard, namedBoards)
+ .addStatement("$T.sort(result)", Collections.class)
+ .addStatement("return result.isEmpty() ? $T.emptyList() : result", Collections.class)
+ .build();
+ TypeSpec hello = TypeSpec.classBuilder("HelloWorld")
+ .addMethod(beyond)
+ .build();
+ JavaFile example = JavaFile.builder("com.example.helloworld", hello)
+ .addStaticImport(hoverboard, "createNimbus")
+ .addStaticImport(namedBoards, "*")
+ .addStaticImport(Collections.class, "*")
+ .build();
+ assertThat(example.toString()).isEqualTo(""
+ + "package com.example.helloworld;\n"
+ + "\n"
+ + "import static com.mattel.Hoverboard.Boards.*;\n"
+ + "import static com.mattel.Hoverboard.createNimbus;\n"
+ + "import static java.util.Collections.*;\n"
+ + "\n"
+ + "import com.mattel.Hoverboard;\n"
+ + "import java.util.ArrayList;\n"
+ + "import java.util.List;\n"
+ + "\n"
+ + "class HelloWorld {\n"
+ + " List<Hoverboard> beyond() {\n"
+ + " List<Hoverboard> result = new ArrayList<>();\n"
+ + " result.add(createNimbus(2000));\n"
+ + " result.add(createNimbus(\"2001\"));\n"
+ + " result.add(createNimbus(THUNDERBOLT));\n"
+ + " sort(result);\n"
+ + " return result.isEmpty() ? emptyList() : result;\n"
+ + " }\n"
+ + "}\n");
+ }
+ @Test public void importStaticForCrazyFormatsWorks() {
+ MethodSpec method = MethodSpec.methodBuilder("method").build();
+ JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .addStaticBlock(CodeBlock.builder()
+ .addStatement("$T", Runtime.class)
+ .addStatement("$T.a()", Runtime.class)
+ .addStatement("$T.X", Runtime.class)
+ .addStatement("$T$T", Runtime.class, Runtime.class)
+ .addStatement("$T.$T", Runtime.class, Runtime.class)
+ .addStatement("$1T$1T", Runtime.class)
+ .addStatement("$1T$2L$1T", Runtime.class, "?")
+ .addStatement("$1T$2L$2S$1T", Runtime.class, "?")
+ .addStatement("$1T$2L$2S$1T$3N$1T", Runtime.class, "?", method)
+ .addStatement("$T$L", Runtime.class, "?")
+ .addStatement("$T$S", Runtime.class, "?")
+ .addStatement("$T$N", Runtime.class, method)
+ .build())
+ .build())
+ .addStaticImport(Runtime.class, "*")
+ .build()
+ .toString(); // don't look at the generated code...
+ }
+
+ @Test public void importStaticMixed() {
+ JavaFile source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .addStaticBlock(CodeBlock.builder()
+ .addStatement("assert $1T.valueOf(\"BLOCKED\") == $1T.BLOCKED", Thread.State.class)
+ .addStatement("$T.gc()", System.class)
+ .addStatement("$1T.out.println($1T.nanoTime())", System.class)
+ .build())
+ .addMethod(MethodSpec.constructorBuilder()
+ .addParameter(Thread.State[].class, "states")
+ .varargs(true)
+ .build())
+ .build())
+ .addStaticImport(Thread.State.BLOCKED)
+ .addStaticImport(System.class, "*")
+ .addStaticImport(Thread.State.class, "valueOf")
+ .build();
+ assertThat(source.toString()).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import static java.lang.System.*;\n"
+ + "import static java.lang.Thread.State.BLOCKED;\n"
+ + "import static java.lang.Thread.State.valueOf;\n"
+ + "\n"
+ + "import java.lang.Thread;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " static {\n"
+ + " assert valueOf(\"BLOCKED\") == BLOCKED;\n"
+ + " gc();\n"
+ + " out.println(nanoTime());\n"
+ + " }\n"
+ + "\n"
+ + " Taco(Thread.State... states) {\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Ignore("addStaticImport doesn't support members with $L")
+ @Test public void importStaticDynamic() {
+ JavaFile source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .addMethod(MethodSpec.methodBuilder("main")
+ .addStatement("$T.$L.println($S)", System.class, "out", "hello")
+ .build())
+ .build())
+ .addStaticImport(System.class, "out")
+ .build();
+ assertThat(source.toString()).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import static java.lang.System.out;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " void main() {\n"
+ + " out.println(\"hello\");\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void importStaticNone() {
+ assertThat(JavaFile.builder("readme", importStaticTypeSpec("Util"))
+ .build().toString()).isEqualTo(""
+ + "package readme;\n"
+ + "\n"
+ + "import java.lang.System;\n"
+ + "import java.util.concurrent.TimeUnit;\n"
+ + "\n"
+ + "class Util {\n"
+ + " public static long minutesToSeconds(long minutes) {\n"
+ + " System.gc();\n"
+ + " return TimeUnit.SECONDS.convert(minutes, TimeUnit.MINUTES);\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void importStaticOnce() {
+ assertThat(JavaFile.builder("readme", importStaticTypeSpec("Util"))
+ .addStaticImport(TimeUnit.SECONDS)
+ .build().toString()).isEqualTo(""
+ + "package readme;\n"
+ + "\n"
+ + "import static java.util.concurrent.TimeUnit.SECONDS;\n"
+ + "\n"
+ + "import java.lang.System;\n"
+ + "import java.util.concurrent.TimeUnit;\n"
+ + "\n"
+ + "class Util {\n"
+ + " public static long minutesToSeconds(long minutes) {\n"
+ + " System.gc();\n"
+ + " return SECONDS.convert(minutes, TimeUnit.MINUTES);\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void importStaticTwice() {
+ assertThat(JavaFile.builder("readme", importStaticTypeSpec("Util"))
+ .addStaticImport(TimeUnit.SECONDS)
+ .addStaticImport(TimeUnit.MINUTES)
+ .build().toString()).isEqualTo(""
+ + "package readme;\n"
+ + "\n"
+ + "import static java.util.concurrent.TimeUnit.MINUTES;\n"
+ + "import static java.util.concurrent.TimeUnit.SECONDS;\n"
+ + "\n"
+ + "import java.lang.System;\n"
+ + "\n"
+ + "class Util {\n"
+ + " public static long minutesToSeconds(long minutes) {\n"
+ + " System.gc();\n"
+ + " return SECONDS.convert(minutes, MINUTES);\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void importStaticUsingWildcards() {
+ assertThat(JavaFile.builder("readme", importStaticTypeSpec("Util"))
+ .addStaticImport(TimeUnit.class, "*")
+ .addStaticImport(System.class, "*")
+ .build().toString()).isEqualTo(""
+ + "package readme;\n"
+ + "\n"
+ + "import static java.lang.System.*;\n"
+ + "import static java.util.concurrent.TimeUnit.*;\n"
+ + "\n"
+ + "class Util {\n"
+ + " public static long minutesToSeconds(long minutes) {\n"
+ + " gc();\n"
+ + " return SECONDS.convert(minutes, MINUTES);\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ private TypeSpec importStaticTypeSpec(String name) {
+ MethodSpec method = MethodSpec.methodBuilder("minutesToSeconds")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .returns(long.class)
+ .addParameter(long.class, "minutes")
+ .addStatement("$T.gc()", System.class)
+ .addStatement("return $1T.SECONDS.convert(minutes, $1T.MINUTES)", TimeUnit.class)
+ .build();
+ return TypeSpec.classBuilder(name).addMethod(method).build();
+
+ }
+ @Test public void noImports() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco").build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco {\n"
+ + "}\n");
+ }
+
+ @Test public void singleImport() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .addField(Date.class, "madeFreshDate")
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.util.Date;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " Date madeFreshDate;\n"
+ + "}\n");
+ }
+
+ @Test public void conflictingImports() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .addField(Date.class, "madeFreshDate")
+ .addField(ClassName.get("java.sql", "Date"), "madeFreshDatabaseDate")
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.util.Date;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " Date madeFreshDate;\n"
+ + "\n"
+ + " java.sql.Date madeFreshDatabaseDate;\n"
+ + "}\n");
+ }
+
+ @Test public void annotatedTypeParam() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .addField(ParameterizedTypeName.get(ClassName.get(List.class),
+ ClassName.get("com.squareup.meat", "Chorizo")
+ .annotated(AnnotationSpec.builder(ClassName.get("com.squareup.tacos", "Spicy"))
+ .build())), "chorizo")
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import com.squareup.meat.Chorizo;\n"
+ + "import java.util.List;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " List<@Spicy Chorizo> chorizo;\n"
+ + "}\n");
+ }
+
+ @Test public void skipJavaLangImportsWithConflictingClassLast() throws Exception {
+ // Whatever is used first wins! In this case the Float in java.lang is imported.
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .addField(ClassName.get("java.lang", "Float"), "litres")
+ .addField(ClassName.get("com.squareup.soda", "Float"), "beverage")
+ .build())
+ .skipJavaLangImports(true)
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " Float litres;\n"
+ + "\n"
+ + " com.squareup.soda.Float beverage;\n" // Second 'Float' is fully qualified.
+ + "}\n");
+ }
+
+ @Test public void skipJavaLangImportsWithConflictingClassFirst() throws Exception {
+ // Whatever is used first wins! In this case the Float in com.squareup.soda is imported.
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .addField(ClassName.get("com.squareup.soda", "Float"), "beverage")
+ .addField(ClassName.get("java.lang", "Float"), "litres")
+ .build())
+ .skipJavaLangImports(true)
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import com.squareup.soda.Float;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " Float beverage;\n"
+ + "\n"
+ + " java.lang.Float litres;\n" // Second 'Float' is fully qualified.
+ + "}\n");
+ }
+
+ @Test public void conflictingParentName() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("A")
+ .addType(TypeSpec.classBuilder("B")
+ .addType(TypeSpec.classBuilder("Twin").build())
+ .addType(TypeSpec.classBuilder("C")
+ .addField(ClassName.get("com.squareup.tacos", "A", "Twin", "D"), "d")
+ .build())
+ .build())
+ .addType(TypeSpec.classBuilder("Twin")
+ .addType(TypeSpec.classBuilder("D")
+ .build())
+ .build())
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class A {\n"
+ + " class B {\n"
+ + " class Twin {\n"
+ + " }\n"
+ + "\n"
+ + " class C {\n"
+ + " A.Twin.D d;\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " class Twin {\n"
+ + " class D {\n"
+ + " }\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void conflictingChildName() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("A")
+ .addType(TypeSpec.classBuilder("B")
+ .addType(TypeSpec.classBuilder("C")
+ .addField(ClassName.get("com.squareup.tacos", "A", "Twin", "D"), "d")
+ .addType(TypeSpec.classBuilder("Twin").build())
+ .build())
+ .build())
+ .addType(TypeSpec.classBuilder("Twin")
+ .addType(TypeSpec.classBuilder("D")
+ .build())
+ .build())
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class A {\n"
+ + " class B {\n"
+ + " class C {\n"
+ + " A.Twin.D d;\n"
+ + "\n"
+ + " class Twin {\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " class Twin {\n"
+ + " class D {\n"
+ + " }\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void conflictingNameOutOfScope() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("A")
+ .addType(TypeSpec.classBuilder("B")
+ .addType(TypeSpec.classBuilder("C")
+ .addField(ClassName.get("com.squareup.tacos", "A", "Twin", "D"), "d")
+ .addType(TypeSpec.classBuilder("Nested")
+ .addType(TypeSpec.classBuilder("Twin").build())
+ .build())
+ .build())
+ .build())
+ .addType(TypeSpec.classBuilder("Twin")
+ .addType(TypeSpec.classBuilder("D")
+ .build())
+ .build())
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class A {\n"
+ + " class B {\n"
+ + " class C {\n"
+ + " Twin.D d;\n"
+ + "\n"
+ + " class Nested {\n"
+ + " class Twin {\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " class Twin {\n"
+ + " class D {\n"
+ + " }\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void nestedClassAndSuperclassShareName() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .superclass(ClassName.get("com.squareup.wire", "Message"))
+ .addType(TypeSpec.classBuilder("Builder")
+ .superclass(ClassName.get("com.squareup.wire", "Message", "Builder"))
+ .build())
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import com.squareup.wire.Message;\n"
+ + "\n"
+ + "class Taco extends Message {\n"
+ + " class Builder extends Message.Builder {\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void classAndSuperclassShareName() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .superclass(ClassName.get("com.taco.bell", "Taco"))
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco extends com.taco.bell.Taco {\n"
+ + "}\n");
+ }
+
+ @Test public void conflictingAnnotation() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .addAnnotation(ClassName.get("com.taco.bell", "Taco"))
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "@com.taco.bell.Taco\n"
+ + "class Taco {\n"
+ + "}\n");
+ }
+
+ @Test public void conflictingAnnotationReferencedClass() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .addAnnotation(AnnotationSpec.builder(ClassName.get("com.squareup.tacos", "MyAnno"))
+ .addMember("value", "$T.class", ClassName.get("com.taco.bell", "Taco"))
+ .build())
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "@MyAnno(com.taco.bell.Taco.class)\n"
+ + "class Taco {\n"
+ + "}\n");
+ }
+
+ @Test public void conflictingTypeVariableBound() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .addTypeVariable(
+ TypeVariableName.get("T", ClassName.get("com.taco.bell", "Taco")))
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco<T extends com.taco.bell.Taco> {\n"
+ + "}\n");
+ }
+
+ @Test public void superclassReferencesSelf() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .superclass(ParameterizedTypeName.get(
+ ClassName.get(Comparable.class), ClassName.get("com.squareup.tacos", "Taco")))
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Comparable;\n"
+ + "\n"
+ + "class Taco extends Comparable<Taco> {\n"
+ + "}\n");
+ }
+
+ /** https://github.com/square/javapoet/issues/366 */
+ @Test public void annotationIsNestedClass() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("TestComponent")
+ .addAnnotation(ClassName.get("dagger", "Component"))
+ .addType(TypeSpec.classBuilder("Builder")
+ .addAnnotation(ClassName.get("dagger", "Component", "Builder"))
+ .build())
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import dagger.Component;\n"
+ + "\n"
+ + "@Component\n"
+ + "class TestComponent {\n"
+ + " @Component.Builder\n"
+ + " class Builder {\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void defaultPackage() throws Exception {
+ String source = JavaFile.builder("",
+ TypeSpec.classBuilder("HelloWorld")
+ .addMethod(MethodSpec.methodBuilder("main")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .addParameter(String[].class, "args")
+ .addCode("$T.out.println($S);\n", System.class, "Hello World!")
+ .build())
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "import java.lang.String;\n"
+ + "import java.lang.System;\n"
+ + "\n"
+ + "class HelloWorld {\n"
+ + " public static void main(String[] args) {\n"
+ + " System.out.println(\"Hello World!\");\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void defaultPackageTypesAreNotImported() throws Exception {
+ String source = JavaFile.builder("hello",
+ TypeSpec.classBuilder("World").addSuperinterface(ClassName.get("", "Test")).build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package hello;\n"
+ + "\n"
+ + "class World implements Test {\n"
+ + "}\n");
+ }
+
+ @Test public void topOfFileComment() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco").build())
+ .addFileComment("Generated $L by JavaPoet. DO NOT EDIT!", "2015-01-13")
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "// Generated 2015-01-13 by JavaPoet. DO NOT EDIT!\n"
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco {\n"
+ + "}\n");
+ }
+
+ @Test public void emptyLinesInTopOfFileComment() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco").build())
+ .addFileComment("\nGENERATED FILE:\n\nDO NOT EDIT!\n")
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "//\n"
+ + "// GENERATED FILE:\n"
+ + "//\n"
+ + "// DO NOT EDIT!\n"
+ + "//\n"
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco {\n"
+ + "}\n");
+ }
+
+ @Test public void packageClassConflictsWithNestedClass() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .addField(ClassName.get("com.squareup.tacos", "A"), "a")
+ .addType(TypeSpec.classBuilder("A").build())
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " com.squareup.tacos.A a;\n"
+ + "\n"
+ + " class A {\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void packageClassConflictsWithSuperlass() throws Exception {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .superclass(ClassName.get("com.taco.bell", "A"))
+ .addField(ClassName.get("com.squareup.tacos", "A"), "a")
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco extends com.taco.bell.A {\n"
+ + " A a;\n"
+ + "}\n");
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/LineWrapperTest.java b/src/test/java/com/squareup/javapoet/LineWrapperTest.java
new file mode 100644
index 0000000..ba8472c
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/LineWrapperTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(JUnit4.class)
+public final class LineWrapperTest {
+ @Test public void wrap() throws Exception {
+ StringBuffer out = new StringBuffer();
+ LineWrapper lineWrapper = new LineWrapper(out, " ", 10);
+ lineWrapper.append("abcde");
+ lineWrapper.wrappingSpace(2);
+ lineWrapper.append("fghij");
+ lineWrapper.close();
+ assertThat(out.toString()).isEqualTo("abcde\n fghij");
+ }
+
+ @Test public void noWrap() throws Exception {
+ StringBuffer out = new StringBuffer();
+ LineWrapper lineWrapper = new LineWrapper(out, " ", 10);
+ lineWrapper.append("abcde");
+ lineWrapper.wrappingSpace(2);
+ lineWrapper.append("fghi");
+ lineWrapper.close();
+ assertThat(out.toString()).isEqualTo("abcde fghi");
+ }
+
+ @Test public void zeroWidthNoWrap() throws Exception {
+ StringBuffer out = new StringBuffer();
+ LineWrapper lineWrapper = new LineWrapper(out, " ", 10);
+ lineWrapper.append("abcde");
+ lineWrapper.zeroWidthSpace(2);
+ lineWrapper.append("fghij");
+ lineWrapper.close();
+ assertThat(out.toString()).isEqualTo("abcdefghij");
+ }
+
+ @Test public void nospaceWrapMax() throws Exception {
+ StringBuffer out = new StringBuffer();
+ LineWrapper lineWrapper = new LineWrapper(out, " ", 10);
+ lineWrapper.append("abcde");
+ lineWrapper.zeroWidthSpace(2);
+ lineWrapper.append("fghijk");
+ lineWrapper.close();
+ assertThat(out.toString()).isEqualTo("abcde\n fghijk");
+ }
+
+ @Test public void multipleWrite() throws Exception {
+ StringBuffer out = new StringBuffer();
+ LineWrapper lineWrapper = new LineWrapper(out, " ", 10);
+ lineWrapper.append("ab");
+ lineWrapper.wrappingSpace(1);
+ lineWrapper.append("cd");
+ lineWrapper.wrappingSpace(1);
+ lineWrapper.append("ef");
+ lineWrapper.wrappingSpace(1);
+ lineWrapper.append("gh");
+ lineWrapper.wrappingSpace(1);
+ lineWrapper.append("ij");
+ lineWrapper.wrappingSpace(1);
+ lineWrapper.append("kl");
+ lineWrapper.wrappingSpace(1);
+ lineWrapper.append("mn");
+ lineWrapper.wrappingSpace(1);
+ lineWrapper.append("op");
+ lineWrapper.wrappingSpace(1);
+ lineWrapper.append("qr");
+ lineWrapper.close();
+ assertThat(out.toString()).isEqualTo("ab cd ef\n gh ij kl\n mn op qr");
+ }
+
+ @Test public void fencepost() throws Exception {
+ StringBuffer out = new StringBuffer();
+ LineWrapper lineWrapper = new LineWrapper(out, " ", 10);
+ lineWrapper.append("abcde");
+ lineWrapper.append("fghij");
+ lineWrapper.wrappingSpace(2);
+ lineWrapper.append("k");
+ lineWrapper.append("lmnop");
+ lineWrapper.close();
+ assertThat(out.toString()).isEqualTo("abcdefghij\n klmnop");
+ }
+
+ @Test public void fencepostZeroWidth() throws Exception {
+ StringBuffer out = new StringBuffer();
+ LineWrapper lineWrapper = new LineWrapper(out, " ", 10);
+ lineWrapper.append("abcde");
+ lineWrapper.append("fghij");
+ lineWrapper.zeroWidthSpace(2);
+ lineWrapper.append("k");
+ lineWrapper.append("lmnop");
+ lineWrapper.close();
+ assertThat(out.toString()).isEqualTo("abcdefghij\n klmnop");
+ }
+
+ @Test public void overlyLongLinesWithoutLeadingSpace() throws Exception {
+ StringBuffer out = new StringBuffer();
+ LineWrapper lineWrapper = new LineWrapper(out, " ", 10);
+ lineWrapper.append("abcdefghijkl");
+ lineWrapper.close();
+ assertThat(out.toString()).isEqualTo("abcdefghijkl");
+ }
+
+ @Test public void overlyLongLinesWithLeadingSpace() throws Exception {
+ StringBuffer out = new StringBuffer();
+ LineWrapper lineWrapper = new LineWrapper(out, " ", 10);
+ lineWrapper.wrappingSpace(2);
+ lineWrapper.append("abcdefghijkl");
+ lineWrapper.close();
+ assertThat(out.toString()).isEqualTo("\n abcdefghijkl");
+ }
+
+ @Test public void overlyLongLinesWithLeadingZeroWidth() throws Exception {
+ StringBuffer out = new StringBuffer();
+ LineWrapper lineWrapper = new LineWrapper(out, " ", 10);
+ lineWrapper.zeroWidthSpace(2);
+ lineWrapper.append("abcdefghijkl");
+ lineWrapper.close();
+ assertThat(out.toString()).isEqualTo("abcdefghijkl");
+ }
+
+ @Test public void noWrapEmbeddedNewlines() throws Exception {
+ StringBuffer out = new StringBuffer();
+ LineWrapper lineWrapper = new LineWrapper(out, " ", 10);
+ lineWrapper.append("abcde");
+ lineWrapper.wrappingSpace(2);
+ lineWrapper.append("fghi\njklmn");
+ lineWrapper.append("opqrstuvwxy");
+ lineWrapper.close();
+ assertThat(out.toString()).isEqualTo("abcde fghi\njklmnopqrstuvwxy");
+ }
+
+ @Test public void wrapEmbeddedNewlines() throws Exception {
+ StringBuffer out = new StringBuffer();
+ LineWrapper lineWrapper = new LineWrapper(out, " ", 10);
+ lineWrapper.append("abcde");
+ lineWrapper.wrappingSpace(2);
+ lineWrapper.append("fghij\nklmn");
+ lineWrapper.append("opqrstuvwxy");
+ lineWrapper.close();
+ assertThat(out.toString()).isEqualTo("abcde\n fghij\nklmnopqrstuvwxy");
+ }
+
+ @Test public void noWrapEmbeddedNewlines_ZeroWidth() throws Exception {
+ StringBuffer out = new StringBuffer();
+ LineWrapper lineWrapper = new LineWrapper(out, " ", 10);
+ lineWrapper.append("abcde");
+ lineWrapper.zeroWidthSpace(2);
+ lineWrapper.append("fghij\nklmn");
+ lineWrapper.append("opqrstuvwxyz");
+ lineWrapper.close();
+ assertThat(out.toString()).isEqualTo("abcdefghij\nklmnopqrstuvwxyz");
+ }
+
+ @Test public void wrapEmbeddedNewlines_ZeroWidth() throws Exception {
+ StringBuffer out = new StringBuffer();
+ LineWrapper lineWrapper = new LineWrapper(out, " ", 10);
+ lineWrapper.append("abcde");
+ lineWrapper.zeroWidthSpace(2);
+ lineWrapper.append("fghijk\nlmn");
+ lineWrapper.append("opqrstuvwxy");
+ lineWrapper.close();
+ assertThat(out.toString()).isEqualTo("abcde\n fghijk\nlmnopqrstuvwxy");
+ }
+
+ @Test public void noWrapMultipleNewlines() throws Exception {
+ StringBuffer out = new StringBuffer();
+ LineWrapper lineWrapper = new LineWrapper(out, " ", 10);
+ lineWrapper.append("abcde");
+ lineWrapper.wrappingSpace(2);
+ lineWrapper.append("fghi\nklmnopq\nr");
+ lineWrapper.wrappingSpace(2);
+ lineWrapper.append("stuvwxyz");
+ lineWrapper.close();
+ assertThat(out.toString()).isEqualTo("abcde fghi\nklmnopq\nr stuvwxyz");
+ }
+
+ @Test public void wrapMultipleNewlines() throws Exception {
+ StringBuffer out = new StringBuffer();
+ LineWrapper lineWrapper = new LineWrapper(out, " ", 10);
+ lineWrapper.append("abcde");
+ lineWrapper.wrappingSpace(2);
+ lineWrapper.append("fghi\nklmnopq\nrs");
+ lineWrapper.wrappingSpace(2);
+ lineWrapper.append("tuvwxyz1");
+ lineWrapper.close();
+ assertThat(out.toString()).isEqualTo("abcde fghi\nklmnopq\nrs\n tuvwxyz1");
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/MethodSpecTest.java b/src/test/java/com/squareup/javapoet/MethodSpecTest.java
new file mode 100644
index 0000000..5dfabaa
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/MethodSpecTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import com.google.testing.compile.CompilationRule;
+import java.io.Closeable;
+import java.io.IOException;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeoutException;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.truth.Truth.assertThat;
+import static javax.lang.model.util.ElementFilter.methodsIn;
+import static org.junit.Assert.fail;
+
+public final class MethodSpecTest {
+ @Rule public final CompilationRule compilation = new CompilationRule();
+
+ private Elements elements;
+ private Types types;
+
+ @Before public void setUp() {
+ elements = compilation.getElements();
+ types = compilation.getTypes();
+ }
+
+ private TypeElement getElement(Class<?> clazz) {
+ return elements.getTypeElement(clazz.getCanonicalName());
+ }
+
+ private ExecutableElement findFirst(Collection<ExecutableElement> elements, String name) {
+ for (ExecutableElement executableElement : elements) {
+ if (executableElement.getSimpleName().toString().equals(name)) {
+ return executableElement;
+ }
+ }
+ throw new IllegalArgumentException(name + " not found in " + elements);
+ }
+
+ @Test public void nullAnnotationsAddition() {
+ try {
+ MethodSpec.methodBuilder("doSomething").addAnnotations(null);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("annotationSpecs == null");
+ }
+ }
+
+ @Test public void nullTypeVariablesAddition() {
+ try {
+ MethodSpec.methodBuilder("doSomething").addTypeVariables(null);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("typeVariables == null");
+ }
+ }
+
+ @Test public void nullParametersAddition() {
+ try {
+ MethodSpec.methodBuilder("doSomething").addParameters(null);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("parameterSpecs == null");
+ }
+ }
+
+ @Test public void nullExceptionsAddition() {
+ try {
+ MethodSpec.methodBuilder("doSomething").addExceptions(null);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("exceptions == null");
+ }
+ }
+
+ @Target(ElementType.PARAMETER)
+ @interface Nullable {
+ }
+
+ abstract static class Everything {
+ @Deprecated protected abstract <T extends Runnable & Closeable> Runnable everything(
+ @Nullable String thing, List<? extends T> things) throws IOException, SecurityException;
+ }
+
+ abstract static class Generics {
+ <T, R, V extends Throwable> T run(R param) throws V {
+ return null;
+ }
+ }
+
+ abstract static class HasAnnotation {
+ @Override public abstract String toString();
+ }
+
+ interface Throws<R extends RuntimeException> {
+ void fail() throws R;
+ }
+
+ interface ExtendsOthers extends Callable<Integer>, Comparable<ExtendsOthers>,
+ Throws<IllegalStateException> {
+ }
+
+ interface ExtendsIterableWithDefaultMethods extends Iterable<Object> {
+ }
+
+ final class FinalClass {
+ void method() {
+ }
+ }
+
+ abstract static class InvalidOverrideMethods {
+ final void finalMethod() {
+ }
+
+ private void privateMethod() {
+ }
+
+ static void staticMethod() {
+ }
+ }
+
+ @Test public void overrideEverything() {
+ TypeElement classElement = getElement(Everything.class);
+ ExecutableElement methodElement = getOnlyElement(methodsIn(classElement.getEnclosedElements()));
+ MethodSpec method = MethodSpec.overriding(methodElement).build();
+ assertThat(method.toString()).isEqualTo(""
+ + "@java.lang.Override\n"
+ + "protected <T extends java.lang.Runnable & java.io.Closeable> java.lang.Runnable "
+ + "everything(\n"
+ + " java.lang.String arg0, java.util.List<? extends T> arg1) throws java.io.IOException,\n"
+ + " java.lang.SecurityException {\n"
+ + "}\n");
+ }
+
+ @Test public void overrideGenerics() {
+ TypeElement classElement = getElement(Generics.class);
+ ExecutableElement methodElement = getOnlyElement(methodsIn(classElement.getEnclosedElements()));
+ MethodSpec method = MethodSpec.overriding(methodElement)
+ .addStatement("return null")
+ .build();
+ assertThat(method.toString()).isEqualTo(""
+ + "@java.lang.Override\n"
+ + "<T, R, V extends java.lang.Throwable> T run(R param) throws V {\n"
+ + " return null;\n"
+ + "}\n");
+ }
+
+ @Test public void overrideDoesNotCopyOverrideAnnotation() {
+ TypeElement classElement = getElement(HasAnnotation.class);
+ ExecutableElement exec = getOnlyElement(methodsIn(classElement.getEnclosedElements()));
+ MethodSpec method = MethodSpec.overriding(exec).build();
+ assertThat(method.toString()).isEqualTo(""
+ + "@java.lang.Override\n"
+ + "public java.lang.String toString() {\n"
+ + "}\n");
+ }
+
+ @Test public void overrideDoesNotCopyDefaultModifier() {
+ TypeElement classElement = getElement(ExtendsIterableWithDefaultMethods.class);
+ DeclaredType classType = (DeclaredType) classElement.asType();
+ List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
+ ExecutableElement exec = findFirst(methods, "spliterator");
+ MethodSpec method = MethodSpec.overriding(exec, classType, types).build();
+ assertThat(method.toString()).isEqualTo(""
+ + "@java.lang.Override\n"
+ + "public java.util.Spliterator<java.lang.Object> spliterator() {\n"
+ + "}\n");
+ }
+
+ @Test public void overrideExtendsOthersWorksWithActualTypeParameters() {
+ TypeElement classElement = getElement(ExtendsOthers.class);
+ DeclaredType classType = (DeclaredType) classElement.asType();
+ List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
+ ExecutableElement exec = findFirst(methods, "call");
+ MethodSpec method = MethodSpec.overriding(exec, classType, types).build();
+ assertThat(method.toString()).isEqualTo(""
+ + "@java.lang.Override\n"
+ + "public java.lang.Integer call() throws java.lang.Exception {\n"
+ + "}\n");
+ exec = findFirst(methods, "compareTo");
+ method = MethodSpec.overriding(exec, classType, types).build();
+ assertThat(method.toString()).isEqualTo(""
+ + "@java.lang.Override\n"
+ + "public int compareTo(" + ExtendsOthers.class.getCanonicalName() + " arg0) {\n"
+ + "}\n");
+ exec = findFirst(methods, "fail");
+ method = MethodSpec.overriding(exec, classType, types).build();
+ assertThat(method.toString()).isEqualTo(""
+ + "@java.lang.Override\n"
+ + "public void fail() throws java.lang.IllegalStateException {\n"
+ + "}\n");
+ }
+
+ @Test public void overrideFinalClassMethod() {
+ TypeElement classElement = getElement(FinalClass.class);
+ List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
+ try {
+ MethodSpec.overriding(findFirst(methods, "method"));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo(
+ "Cannot override method on final class com.squareup.javapoet.MethodSpecTest.FinalClass");
+ }
+ }
+
+ @Test public void overrideInvalidModifiers() {
+ TypeElement classElement = getElement(InvalidOverrideMethods.class);
+ List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
+ try {
+ MethodSpec.overriding(findFirst(methods, "finalMethod"));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("cannot override method with modifiers: [final]");
+ }
+ try {
+ MethodSpec.overriding(findFirst(methods, "privateMethod"));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("cannot override method with modifiers: [private]");
+ }
+ try {
+ MethodSpec.overriding(findFirst(methods, "staticMethod"));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("cannot override method with modifiers: [static]");
+ }
+ }
+
+ @Test public void equalsAndHashCode() {
+ MethodSpec a = MethodSpec.constructorBuilder().build();
+ MethodSpec b = MethodSpec.constructorBuilder().build();
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ a = MethodSpec.methodBuilder("taco").build();
+ b = MethodSpec.methodBuilder("taco").build();
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ TypeElement classElement = getElement(Everything.class);
+ ExecutableElement methodElement = getOnlyElement(methodsIn(classElement.getEnclosedElements()));
+ a = MethodSpec.overriding(methodElement).build();
+ b = MethodSpec.overriding(methodElement).build();
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ }
+
+ @Test public void duplicateExceptionsIgnored() {
+ ClassName ioException = ClassName.get(IOException.class);
+ ClassName timeoutException = ClassName.get(TimeoutException.class);
+ MethodSpec methodSpec = MethodSpec.methodBuilder("duplicateExceptions")
+ .addException(ioException)
+ .addException(timeoutException)
+ .addException(timeoutException)
+ .addException(ioException)
+ .build();
+ assertThat(methodSpec.exceptions).isEqualTo(Arrays.asList(ioException, timeoutException));
+ assertThat(methodSpec.toBuilder().addException(ioException).build().exceptions)
+ .isEqualTo(Arrays.asList(ioException, timeoutException));
+ }
+
+ @Test public void nullIsNotAValidMethodName() {
+ try {
+ MethodSpec.methodBuilder(null);
+ fail("NullPointerException expected");
+ } catch (NullPointerException e) {
+ assertThat(e.getMessage()).isEqualTo("name == null");
+ }
+ }
+
+ @Test public void addModifiersVarargsShouldNotBeNull() {
+ try {
+ MethodSpec.methodBuilder("taco")
+ .addModifiers((Modifier[]) null);
+ fail("NullPointerException expected");
+ } catch (NullPointerException e) {
+ assertThat(e.getMessage()).isEqualTo("modifiers == null");
+ }
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/NameAllocatorTest.java b/src/test/java/com/squareup/javapoet/NameAllocatorTest.java
new file mode 100644
index 0000000..1840107
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/NameAllocatorTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+public final class NameAllocatorTest {
+ @Test public void usage() throws Exception {
+ NameAllocator nameAllocator = new NameAllocator();
+ assertThat(nameAllocator.newName("foo", 1)).isEqualTo("foo");
+ assertThat(nameAllocator.newName("bar", 2)).isEqualTo("bar");
+ assertThat(nameAllocator.get(1)).isEqualTo("foo");
+ assertThat(nameAllocator.get(2)).isEqualTo("bar");
+ }
+
+ @Test public void nameCollision() throws Exception {
+ NameAllocator nameAllocator = new NameAllocator();
+ assertThat(nameAllocator.newName("foo")).isEqualTo("foo");
+ assertThat(nameAllocator.newName("foo")).isEqualTo("foo_");
+ assertThat(nameAllocator.newName("foo")).isEqualTo("foo__");
+ }
+
+ @Test public void nameCollisionWithTag() throws Exception {
+ NameAllocator nameAllocator = new NameAllocator();
+ assertThat(nameAllocator.newName("foo", 1)).isEqualTo("foo");
+ assertThat(nameAllocator.newName("foo", 2)).isEqualTo("foo_");
+ assertThat(nameAllocator.newName("foo", 3)).isEqualTo("foo__");
+ assertThat(nameAllocator.get(1)).isEqualTo("foo");
+ assertThat(nameAllocator.get(2)).isEqualTo("foo_");
+ assertThat(nameAllocator.get(3)).isEqualTo("foo__");
+ }
+
+ @Test public void characterMappingSubstitute() throws Exception {
+ NameAllocator nameAllocator = new NameAllocator();
+ assertThat(nameAllocator.newName("a-b", 1)).isEqualTo("a_b");
+ }
+
+ @Test public void characterMappingSurrogate() throws Exception {
+ NameAllocator nameAllocator = new NameAllocator();
+ assertThat(nameAllocator.newName("a\uD83C\uDF7Ab", 1)).isEqualTo("a_b");
+ }
+
+ @Test public void characterMappingInvalidStartButValidPart() throws Exception {
+ NameAllocator nameAllocator = new NameAllocator();
+ assertThat(nameAllocator.newName("1ab", 1)).isEqualTo("_1ab");
+ }
+
+ @Test public void characterMappingInvalidStartIsInvalidPart() throws Exception {
+ NameAllocator nameAllocator = new NameAllocator();
+ assertThat(nameAllocator.newName("&ab", 1)).isEqualTo("_ab");
+ }
+
+ @Test public void javaKeyword() throws Exception {
+ NameAllocator nameAllocator = new NameAllocator();
+ assertThat(nameAllocator.newName("public", 1)).isEqualTo("public_");
+ assertThat(nameAllocator.get(1)).isEqualTo("public_");
+ }
+
+ @Test public void tagReuseForbidden() throws Exception {
+ NameAllocator nameAllocator = new NameAllocator();
+ nameAllocator.newName("foo", 1);
+ try {
+ nameAllocator.newName("bar", 1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("tag 1 cannot be used for both 'foo' and 'bar'");
+ }
+ }
+
+ @Test public void useBeforeAllocateForbidden() throws Exception {
+ NameAllocator nameAllocator = new NameAllocator();
+ try {
+ nameAllocator.get(1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("unknown tag: 1");
+ }
+ }
+
+ @Test public void cloneUsage() throws Exception {
+ NameAllocator outterAllocator = new NameAllocator();
+ outterAllocator.newName("foo", 1);
+
+ NameAllocator innerAllocator1 = outterAllocator.clone();
+ assertThat(innerAllocator1.newName("bar", 2)).isEqualTo("bar");
+ assertThat(innerAllocator1.newName("foo", 3)).isEqualTo("foo_");
+
+ NameAllocator innerAllocator2 = outterAllocator.clone();
+ assertThat(innerAllocator2.newName("foo", 2)).isEqualTo("foo_");
+ assertThat(innerAllocator2.newName("bar", 3)).isEqualTo("bar");
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/ParameterSpecTest.java b/src/test/java/com/squareup/javapoet/ParameterSpecTest.java
new file mode 100644
index 0000000..2f81866
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/ParameterSpecTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import javax.lang.model.element.Modifier;
+
+public class ParameterSpecTest {
+ @Test public void equalsAndHashCode() {
+ ParameterSpec a = ParameterSpec.builder(int.class, "foo").build();
+ ParameterSpec b = ParameterSpec.builder(int.class, "foo").build();
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ a = ParameterSpec.builder(int.class, "i").addModifiers(Modifier.STATIC).build();
+ b = ParameterSpec.builder(int.class, "i").addModifiers(Modifier.STATIC).build();
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ }
+
+ @Test public void nullAnnotationsAddition() {
+ try {
+ ParameterSpec.builder(int.class, "foo").addAnnotations(null);
+ fail();
+ } catch (Exception e) {
+ assertThat(e.getMessage())
+ .isEqualTo("annotationSpecs == null");
+ }
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/squareup/javapoet/TestFiler.java b/src/test/java/com/squareup/javapoet/TestFiler.java
new file mode 100644
index 0000000..274877c
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/TestFiler.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.processing.Filer;
+import javax.lang.model.element.Element;
+import javax.tools.FileObject;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+
+final class TestFiler implements Filer {
+ class Source extends SimpleJavaFileObject {
+ private final Path path;
+ protected Source(Path path) {
+ super(path.toUri(), Kind.SOURCE);
+ this.path = path;
+ }
+ @Override public OutputStream openOutputStream() throws IOException {
+ Path parent = path.getParent();
+ if (!Files.exists(parent)) fileSystemProvider.createDirectory(parent);
+ return fileSystemProvider.newOutputStream(path);
+ }
+ }
+
+ private final String separator;
+ private final Path fileSystemRoot;
+ private final FileSystemProvider fileSystemProvider;
+ private final Map<Path, Set<Element>> originatingElementsMap;
+
+ public TestFiler(FileSystem fileSystem, Path fsRoot) {
+ separator = fileSystem.getSeparator();
+ fileSystemRoot = fsRoot;
+ fileSystemProvider = fileSystem.provider();
+ originatingElementsMap = new LinkedHashMap<>();
+ }
+
+ public Set<Element> getOriginatingElements(Path path) {
+ return originatingElementsMap.get(path);
+ }
+
+ @Override public JavaFileObject createSourceFile(
+ CharSequence name, Element... originatingElements) throws IOException {
+ String relative = name.toString().replace(".", separator) + ".java"; // Assumes well-formed.
+ Path path = fileSystemRoot.resolve(relative);
+ originatingElementsMap.put(path, Util.immutableSet(Arrays.asList(originatingElements)));
+ return new Source(path);
+ }
+
+ @Override public JavaFileObject createClassFile(CharSequence name, Element... originatingElements)
+ throws IOException {
+ throw new UnsupportedOperationException("Not implemented.");
+ }
+
+ @Override public FileObject createResource(JavaFileManager.Location location, CharSequence pkg,
+ CharSequence relativeName, Element... originatingElements) throws IOException {
+ throw new UnsupportedOperationException("Not implemented.");
+ }
+
+ @Override public FileObject getResource(JavaFileManager.Location location, CharSequence pkg,
+ CharSequence relativeName) throws IOException {
+ throw new UnsupportedOperationException("Not implemented.");
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/TypeNameTest.java b/src/test/java/com/squareup/javapoet/TypeNameTest.java
new file mode 100644
index 0000000..99ed58d
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/TypeNameTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+
+public class TypeNameTest {
+
+ protected <E extends Enum<E>> E generic(E[] values) {
+ return values[0];
+ }
+
+ protected static class TestGeneric<T> {
+ class Inner {}
+
+ class InnerGeneric<T2> {}
+
+ static class NestedNonGeneric {}
+ }
+
+ protected static TestGeneric<String>.Inner testGenericStringInner() {
+ return null;
+ }
+
+ protected static TestGeneric<Integer>.Inner testGenericIntInner() {
+ return null;
+ }
+
+ protected static TestGeneric<Short>.InnerGeneric<Long> testGenericInnerLong() {
+ return null;
+ }
+
+ protected static TestGeneric<Short>.InnerGeneric<Integer> testGenericInnerInt() {
+ return null;
+ }
+
+ protected static TestGeneric.NestedNonGeneric testNestedNonGeneric() {
+ return null;
+ }
+
+ @Test public void genericType() throws Exception {
+ Method recursiveEnum = getClass().getDeclaredMethod("generic", Enum[].class);
+ TypeName.get(recursiveEnum.getReturnType());
+ TypeName.get(recursiveEnum.getGenericReturnType());
+ TypeName genericTypeName = TypeName.get(recursiveEnum.getParameterTypes()[0]);
+ TypeName.get(recursiveEnum.getGenericParameterTypes()[0]);
+
+ // Make sure the generic argument is present
+ assertThat(genericTypeName.toString()).contains("Enum");
+ }
+
+ @Test public void innerClassInGenericType() throws Exception {
+ Method genericStringInner = getClass().getDeclaredMethod("testGenericStringInner");
+ TypeName.get(genericStringInner.getReturnType());
+ TypeName genericTypeName = TypeName.get(genericStringInner.getGenericReturnType());
+ assertNotEquals(TypeName.get(genericStringInner.getGenericReturnType()),
+ TypeName.get(getClass().getDeclaredMethod("testGenericIntInner").getGenericReturnType()));
+
+ // Make sure the generic argument is present
+ assertThat(genericTypeName.toString()).isEqualTo(
+ TestGeneric.class.getCanonicalName() + "<java.lang.String>.Inner");
+ }
+
+ @Test public void innerGenericInGenericType() throws Exception {
+ Method genericStringInner = getClass().getDeclaredMethod("testGenericInnerLong");
+ TypeName.get(genericStringInner.getReturnType());
+ TypeName genericTypeName = TypeName.get(genericStringInner.getGenericReturnType());
+ assertNotEquals(TypeName.get(genericStringInner.getGenericReturnType()),
+ TypeName.get(getClass().getDeclaredMethod("testGenericInnerInt").getGenericReturnType()));
+
+ // Make sure the generic argument is present
+ assertThat(genericTypeName.toString()).isEqualTo(
+ TestGeneric.class.getCanonicalName() + "<java.lang.Short>.InnerGeneric<java.lang.Long>");
+ }
+
+ @Test public void innerStaticInGenericType() throws Exception {
+ Method staticInGeneric = getClass().getDeclaredMethod("testNestedNonGeneric");
+ TypeName.get(staticInGeneric.getReturnType());
+ TypeName typeName = TypeName.get(staticInGeneric.getGenericReturnType());
+
+ // Make sure there are no generic arguments
+ assertThat(typeName.toString()).isEqualTo(
+ TestGeneric.class.getCanonicalName() + ".NestedNonGeneric");
+ }
+
+ @Test public void equalsAndHashCodePrimitive() {
+ assertEqualsHashCodeAndToString(TypeName.BOOLEAN, TypeName.BOOLEAN);
+ assertEqualsHashCodeAndToString(TypeName.BYTE, TypeName.BYTE);
+ assertEqualsHashCodeAndToString(TypeName.CHAR, TypeName.CHAR);
+ assertEqualsHashCodeAndToString(TypeName.DOUBLE, TypeName.DOUBLE);
+ assertEqualsHashCodeAndToString(TypeName.FLOAT, TypeName.FLOAT);
+ assertEqualsHashCodeAndToString(TypeName.INT, TypeName.INT);
+ assertEqualsHashCodeAndToString(TypeName.LONG, TypeName.LONG);
+ assertEqualsHashCodeAndToString(TypeName.SHORT, TypeName.SHORT);
+ assertEqualsHashCodeAndToString(TypeName.VOID, TypeName.VOID);
+ }
+
+ @Test public void equalsAndHashCodeArrayTypeName() {
+ assertEqualsHashCodeAndToString(ArrayTypeName.of(Object.class),
+ ArrayTypeName.of(Object.class));
+ assertEqualsHashCodeAndToString(TypeName.get(Object[].class),
+ ArrayTypeName.of(Object.class));
+ }
+
+ @Test public void equalsAndHashCodeClassName() {
+ assertEqualsHashCodeAndToString(ClassName.get(Object.class), ClassName.get(Object.class));
+ assertEqualsHashCodeAndToString(TypeName.get(Object.class), ClassName.get(Object.class));
+ assertEqualsHashCodeAndToString(ClassName.bestGuess("java.lang.Object"),
+ ClassName.get(Object.class));
+ }
+
+ @Test public void equalsAndHashCodeParameterizedTypeName() {
+ assertEqualsHashCodeAndToString(ParameterizedTypeName.get(Object.class),
+ ParameterizedTypeName.get(Object.class));
+ assertEqualsHashCodeAndToString(ParameterizedTypeName.get(Set.class, UUID.class),
+ ParameterizedTypeName.get(Set.class, UUID.class));
+ assertNotEquals(ClassName.get(List.class), ParameterizedTypeName.get(List.class,
+ String.class));
+ }
+
+ @Test public void equalsAndHashCodeTypeVariableName() {
+ assertEqualsHashCodeAndToString(TypeVariableName.get(Object.class),
+ TypeVariableName.get(Object.class));
+ TypeVariableName typeVar1 = TypeVariableName.get("T", Comparator.class, Serializable.class);
+ TypeVariableName typeVar2 = TypeVariableName.get("T", Comparator.class, Serializable.class);
+ assertEqualsHashCodeAndToString(typeVar1, typeVar2);
+ }
+
+ @Test public void equalsAndHashCodeWildcardTypeName() {
+ assertEqualsHashCodeAndToString(WildcardTypeName.subtypeOf(Object.class),
+ WildcardTypeName.subtypeOf(Object.class));
+ assertEqualsHashCodeAndToString(WildcardTypeName.subtypeOf(Serializable.class),
+ WildcardTypeName.subtypeOf(Serializable.class));
+ assertEqualsHashCodeAndToString(WildcardTypeName.supertypeOf(String.class),
+ WildcardTypeName.supertypeOf(String.class));
+ }
+
+ @Test public void isPrimitive() throws Exception {
+ assertThat(TypeName.INT.isPrimitive()).isTrue();
+ assertThat(ClassName.get("java.lang", "Integer").isPrimitive()).isFalse();
+ assertThat(ClassName.get("java.lang", "String").isPrimitive()).isFalse();
+ assertThat(TypeName.VOID.isPrimitive()).isFalse();
+ assertThat(ClassName.get("java.lang", "Void").isPrimitive()).isFalse();
+ }
+
+ @Test public void isBoxedPrimitive() throws Exception {
+ assertThat(TypeName.INT.isBoxedPrimitive()).isFalse();
+ assertThat(ClassName.get("java.lang", "Integer").isBoxedPrimitive()).isTrue();
+ assertThat(ClassName.get("java.lang", "String").isBoxedPrimitive()).isFalse();
+ assertThat(TypeName.VOID.isBoxedPrimitive()).isFalse();
+ assertThat(ClassName.get("java.lang", "Void").isBoxedPrimitive()).isFalse();
+ }
+
+ private void assertEqualsHashCodeAndToString(TypeName a, TypeName b) {
+ assertEquals(a.toString(), b.toString());
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ assertFalse(a.equals(null));
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/TypeSpecTest.java b/src/test/java/com/squareup/javapoet/TypeSpecTest.java
new file mode 100644
index 0000000..9cd22c2
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/TypeSpecTest.java
@@ -0,0 +1,2359 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.testing.compile.CompilationRule;
+import java.io.IOException;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EventListener;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+@RunWith(JUnit4.class)
+public final class TypeSpecTest {
+ private final String tacosPackage = "com.squareup.tacos";
+ private static final String donutsPackage = "com.squareup.donuts";
+
+ @Rule public final CompilationRule compilation = new CompilationRule();
+
+ private TypeElement getElement(Class<?> clazz) {
+ return compilation.getElements().getTypeElement(clazz.getCanonicalName());
+ }
+
+ @Test public void basic() throws Exception {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addMethod(MethodSpec.methodBuilder("toString")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .returns(String.class)
+ .addCode("return $S;\n", "taco")
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Override;\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " @Override\n"
+ + " public final String toString() {\n"
+ + " return \"taco\";\n"
+ + " }\n"
+ + "}\n");
+ assertEquals(472949424, taco.hashCode()); // update expected number if source changes
+ }
+
+ @Test public void interestingTypes() throws Exception {
+ TypeName listOfAny = ParameterizedTypeName.get(
+ ClassName.get(List.class), WildcardTypeName.subtypeOf(Object.class));
+ TypeName listOfExtends = ParameterizedTypeName.get(
+ ClassName.get(List.class), WildcardTypeName.subtypeOf(Serializable.class));
+ TypeName listOfSuper = ParameterizedTypeName.get(ClassName.get(List.class),
+ WildcardTypeName.supertypeOf(String.class));
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addField(listOfAny, "extendsObject")
+ .addField(listOfExtends, "extendsSerializable")
+ .addField(listOfSuper, "superString")
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.io.Serializable;\n"
+ + "import java.lang.String;\n"
+ + "import java.util.List;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " List<?> extendsObject;\n"
+ + "\n"
+ + " List<? extends Serializable> extendsSerializable;\n"
+ + "\n"
+ + " List<? super String> superString;\n"
+ + "}\n");
+ }
+
+ @Test public void anonymousInnerClass() throws Exception {
+ ClassName foo = ClassName.get(tacosPackage, "Foo");
+ ClassName bar = ClassName.get(tacosPackage, "Bar");
+ ClassName thingThang = ClassName.get(tacosPackage, "Thing", "Thang");
+ TypeName thingThangOfFooBar = ParameterizedTypeName.get(thingThang, foo, bar);
+ ClassName thung = ClassName.get(tacosPackage, "Thung");
+ ClassName simpleThung = ClassName.get(tacosPackage, "SimpleThung");
+ TypeName thungOfSuperBar = ParameterizedTypeName.get(thung, WildcardTypeName.supertypeOf(bar));
+ TypeName thungOfSuperFoo = ParameterizedTypeName.get(thung, WildcardTypeName.supertypeOf(foo));
+ TypeName simpleThungOfBar = ParameterizedTypeName.get(simpleThung, bar);
+
+ ParameterSpec thungParameter = ParameterSpec.builder(thungOfSuperFoo, "thung")
+ .addModifiers(Modifier.FINAL)
+ .build();
+ TypeSpec aSimpleThung = TypeSpec.anonymousClassBuilder(CodeBlock.of("$N", thungParameter))
+ .superclass(simpleThungOfBar)
+ .addMethod(MethodSpec.methodBuilder("doSomething")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(bar, "bar")
+ .addCode("/* code snippets */\n")
+ .build())
+ .build();
+ TypeSpec aThingThang = TypeSpec.anonymousClassBuilder("")
+ .superclass(thingThangOfFooBar)
+ .addMethod(MethodSpec.methodBuilder("call")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .returns(thungOfSuperBar)
+ .addParameter(thungParameter)
+ .addCode("return $L;\n", aSimpleThung)
+ .build())
+ .build();
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addField(FieldSpec.builder(thingThangOfFooBar, "NAME")
+ .addModifiers(Modifier.STATIC, Modifier.FINAL, Modifier.FINAL)
+ .initializer("$L", aThingThang)
+ .build())
+ .build();
+
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Override;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " static final Thing.Thang<Foo, Bar> NAME = new Thing.Thang<Foo, Bar>() {\n"
+ + " @Override\n"
+ + " public Thung<? super Bar> call(final Thung<? super Foo> thung) {\n"
+ + " return new SimpleThung<Bar>(thung) {\n"
+ + " @Override\n"
+ + " public void doSomething(Bar bar) {\n"
+ + " /* code snippets */\n"
+ + " }\n"
+ + " };\n"
+ + " }\n"
+ + " };\n"
+ + "}\n");
+ }
+
+ @Test public void annotatedParameters() throws Exception {
+ TypeSpec service = TypeSpec.classBuilder("Foo")
+ .addMethod(MethodSpec.constructorBuilder()
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(long.class, "id")
+ .addParameter(ParameterSpec.builder(String.class, "one")
+ .addAnnotation(ClassName.get(tacosPackage, "Ping"))
+ .build())
+ .addParameter(ParameterSpec.builder(String.class, "two")
+ .addAnnotation(ClassName.get(tacosPackage, "Ping"))
+ .build())
+ .addParameter(ParameterSpec.builder(String.class, "three")
+ .addAnnotation(AnnotationSpec.builder(ClassName.get(tacosPackage, "Pong"))
+ .addMember("value", "$S", "pong")
+ .build())
+ .build())
+ .addParameter(ParameterSpec.builder(String.class, "four")
+ .addAnnotation(ClassName.get(tacosPackage, "Ping"))
+ .build())
+ .addCode("/* code snippets */\n")
+ .build())
+ .build();
+
+ assertThat(toString(service)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "class Foo {\n"
+ + " public Foo(long id, @Ping String one, @Ping String two, @Pong(\"pong\") String three,\n"
+ + " @Ping String four) {\n"
+ + " /* code snippets */\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ /**
+ * We had a bug where annotations were preventing us from doing the right thing when resolving
+ * imports. https://github.com/square/javapoet/issues/422
+ */
+ @Test public void annotationsAndJavaLangTypes() throws Exception {
+ ClassName freeRange = ClassName.get("javax.annotation", "FreeRange");
+ TypeSpec taco = TypeSpec.classBuilder("EthicalTaco")
+ .addField(ClassName.get(String.class)
+ .annotated(AnnotationSpec.builder(freeRange).build()), "meat")
+ .build();
+
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.String;\n"
+ + "import javax.annotation.FreeRange;\n"
+ + "\n"
+ + "class EthicalTaco {\n"
+ + " @FreeRange String meat;\n"
+ + "}\n");
+ }
+
+ @Test public void retrofitStyleInterface() throws Exception {
+ ClassName observable = ClassName.get(tacosPackage, "Observable");
+ ClassName fooBar = ClassName.get(tacosPackage, "FooBar");
+ ClassName thing = ClassName.get(tacosPackage, "Thing");
+ ClassName things = ClassName.get(tacosPackage, "Things");
+ ClassName map = ClassName.get("java.util", "Map");
+ ClassName string = ClassName.get("java.lang", "String");
+ ClassName headers = ClassName.get(tacosPackage, "Headers");
+ ClassName post = ClassName.get(tacosPackage, "POST");
+ ClassName body = ClassName.get(tacosPackage, "Body");
+ ClassName queryMap = ClassName.get(tacosPackage, "QueryMap");
+ ClassName header = ClassName.get(tacosPackage, "Header");
+ TypeSpec service = TypeSpec.interfaceBuilder("Service")
+ .addMethod(MethodSpec.methodBuilder("fooBar")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .addAnnotation(AnnotationSpec.builder(headers)
+ .addMember("value", "$S", "Accept: application/json")
+ .addMember("value", "$S", "User-Agent: foobar")
+ .build())
+ .addAnnotation(AnnotationSpec.builder(post)
+ .addMember("value", "$S", "/foo/bar")
+ .build())
+ .returns(ParameterizedTypeName.get(observable, fooBar))
+ .addParameter(ParameterSpec.builder(ParameterizedTypeName.get(things, thing), "things")
+ .addAnnotation(body)
+ .build())
+ .addParameter(ParameterSpec.builder(
+ ParameterizedTypeName.get(map, string, string), "query")
+ .addAnnotation(AnnotationSpec.builder(queryMap)
+ .addMember("encodeValues", "false")
+ .build())
+ .build())
+ .addParameter(ParameterSpec.builder(string, "authorization")
+ .addAnnotation(AnnotationSpec.builder(header)
+ .addMember("value", "$S", "Authorization")
+ .build())
+ .build())
+ .build())
+ .build();
+
+ assertThat(toString(service)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.String;\n"
+ + "import java.util.Map;\n"
+ + "\n"
+ + "interface Service {\n"
+ + " @Headers({\n"
+ + " \"Accept: application/json\",\n"
+ + " \"User-Agent: foobar\"\n"
+ + " })\n"
+ + " @POST(\"/foo/bar\")\n"
+ + " Observable<FooBar> fooBar(@Body Things<Thing> things,\n"
+ + " @QueryMap(encodeValues = false) Map<String, String> query,\n"
+ + " @Header(\"Authorization\") String authorization);\n"
+ + "}\n");
+ }
+
+ @Test public void annotatedField() throws Exception {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addField(FieldSpec.builder(String.class, "thing", Modifier.PRIVATE, Modifier.FINAL)
+ .addAnnotation(AnnotationSpec.builder(ClassName.get(tacosPackage, "JsonAdapter"))
+ .addMember("value", "$T.class", ClassName.get(tacosPackage, "Foo"))
+ .build())
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " @JsonAdapter(Foo.class)\n"
+ + " private final String thing;\n"
+ + "}\n");
+ }
+
+ @Test public void annotatedClass() throws Exception {
+ ClassName someType = ClassName.get(tacosPackage, "SomeType");
+ TypeSpec taco = TypeSpec.classBuilder("Foo")
+ .addAnnotation(AnnotationSpec.builder(ClassName.get(tacosPackage, "Something"))
+ .addMember("hi", "$T.$N", someType, "FIELD")
+ .addMember("hey", "$L", 12)
+ .addMember("hello", "$S", "goodbye")
+ .build())
+ .addModifiers(Modifier.PUBLIC)
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "@Something(\n"
+ + " hi = SomeType.FIELD,\n"
+ + " hey = 12,\n"
+ + " hello = \"goodbye\"\n"
+ + ")\n"
+ + "public class Foo {\n"
+ + "}\n");
+ }
+
+ @Test public void addAnnotationDisallowsNull() {
+ try {
+ TypeSpec.classBuilder("Foo").addAnnotation((AnnotationSpec) null);
+ fail();
+ } catch (NullPointerException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("annotationSpec == null");
+ }
+ try {
+ TypeSpec.classBuilder("Foo").addAnnotation((ClassName) null);
+ fail();
+ } catch (NullPointerException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("type == null");
+ }
+ try {
+ TypeSpec.classBuilder("Foo").addAnnotation((Class<?>) null);
+ fail();
+ } catch (NullPointerException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("clazz == null");
+ }
+ }
+
+ @Test public void enumWithSubclassing() throws Exception {
+ TypeSpec roshambo = TypeSpec.enumBuilder("Roshambo")
+ .addModifiers(Modifier.PUBLIC)
+ .addEnumConstant("ROCK", TypeSpec.anonymousClassBuilder("")
+ .addJavadoc("Avalanche!\n")
+ .build())
+ .addEnumConstant("PAPER", TypeSpec.anonymousClassBuilder("$S", "flat")
+ .addMethod(MethodSpec.methodBuilder("toString")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .returns(String.class)
+ .addCode("return $S;\n", "paper airplane!")
+ .build())
+ .build())
+ .addEnumConstant("SCISSORS", TypeSpec.anonymousClassBuilder("$S", "peace sign")
+ .build())
+ .addField(String.class, "handPosition", Modifier.PRIVATE, Modifier.FINAL)
+ .addMethod(MethodSpec.constructorBuilder()
+ .addParameter(String.class, "handPosition")
+ .addCode("this.handPosition = handPosition;\n")
+ .build())
+ .addMethod(MethodSpec.constructorBuilder()
+ .addCode("this($S);\n", "fist")
+ .build())
+ .build();
+ assertThat(toString(roshambo)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Override;\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "public enum Roshambo {\n"
+ + " /**\n"
+ + " * Avalanche!\n"
+ + " */\n"
+ + " ROCK,\n"
+ + "\n"
+ + " PAPER(\"flat\") {\n"
+ + " @Override\n"
+ + " public String toString() {\n"
+ + " return \"paper airplane!\";\n"
+ + " }\n"
+ + " },\n"
+ + "\n"
+ + " SCISSORS(\"peace sign\");\n"
+ + "\n"
+ + " private final String handPosition;\n"
+ + "\n"
+ + " Roshambo(String handPosition) {\n"
+ + " this.handPosition = handPosition;\n"
+ + " }\n"
+ + "\n"
+ + " Roshambo() {\n"
+ + " this(\"fist\");\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ /** https://github.com/square/javapoet/issues/193 */
+ @Test public void enumsMayDefineAbstractMethods() throws Exception {
+ TypeSpec roshambo = TypeSpec.enumBuilder("Tortilla")
+ .addModifiers(Modifier.PUBLIC)
+ .addEnumConstant("CORN", TypeSpec.anonymousClassBuilder("")
+ .addMethod(MethodSpec.methodBuilder("fold")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .build())
+ .build())
+ .addMethod(MethodSpec.methodBuilder("fold")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .build())
+ .build();
+ assertThat(toString(roshambo)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Override;\n"
+ + "\n"
+ + "public enum Tortilla {\n"
+ + " CORN {\n"
+ + " @Override\n"
+ + " public void fold() {\n"
+ + " }\n"
+ + " };\n"
+ + "\n"
+ + " public abstract void fold();\n"
+ + "}\n");
+ }
+
+ @Test public void enumConstantsRequired() throws Exception {
+ try {
+ TypeSpec.enumBuilder("Roshambo")
+ .build();
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test public void onlyEnumsMayHaveEnumConstants() throws Exception {
+ try {
+ TypeSpec.classBuilder("Roshambo")
+ .addEnumConstant("ROCK")
+ .build();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ @Test public void enumWithMembersButNoConstructorCall() throws Exception {
+ TypeSpec roshambo = TypeSpec.enumBuilder("Roshambo")
+ .addEnumConstant("SPOCK", TypeSpec.anonymousClassBuilder("")
+ .addMethod(MethodSpec.methodBuilder("toString")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .returns(String.class)
+ .addCode("return $S;\n", "west side")
+ .build())
+ .build())
+ .build();
+ assertThat(toString(roshambo)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Override;\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "enum Roshambo {\n"
+ + " SPOCK {\n"
+ + " @Override\n"
+ + " public String toString() {\n"
+ + " return \"west side\";\n"
+ + " }\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ /** https://github.com/square/javapoet/issues/253 */
+ @Test public void enumWithAnnotatedValues() throws Exception {
+ TypeSpec roshambo = TypeSpec.enumBuilder("Roshambo")
+ .addModifiers(Modifier.PUBLIC)
+ .addEnumConstant("ROCK", TypeSpec.anonymousClassBuilder("")
+ .addAnnotation(Deprecated.class)
+ .build())
+ .addEnumConstant("PAPER")
+ .addEnumConstant("SCISSORS")
+ .build();
+ assertThat(toString(roshambo)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Deprecated;\n"
+ + "\n"
+ + "public enum Roshambo {\n"
+ + " @Deprecated\n"
+ + " ROCK,\n"
+ + "\n"
+ + " PAPER,\n"
+ + "\n"
+ + " SCISSORS\n"
+ + "}\n");
+ }
+
+ @Test public void methodThrows() throws Exception {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addModifiers(Modifier.ABSTRACT)
+ .addMethod(MethodSpec.methodBuilder("throwOne")
+ .addException(IOException.class)
+ .build())
+ .addMethod(MethodSpec.methodBuilder("throwTwo")
+ .addException(IOException.class)
+ .addException(ClassName.get(tacosPackage, "SourCreamException"))
+ .build())
+ .addMethod(MethodSpec.methodBuilder("abstractThrow")
+ .addModifiers(Modifier.ABSTRACT)
+ .addException(IOException.class)
+ .build())
+ .addMethod(MethodSpec.methodBuilder("nativeThrow")
+ .addModifiers(Modifier.NATIVE)
+ .addException(IOException.class)
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.io.IOException;\n"
+ + "\n"
+ + "abstract class Taco {\n"
+ + " void throwOne() throws IOException {\n"
+ + " }\n"
+ + "\n"
+ + " void throwTwo() throws IOException, SourCreamException {\n"
+ + " }\n"
+ + "\n"
+ + " abstract void abstractThrow() throws IOException;\n"
+ + "\n"
+ + " native void nativeThrow() throws IOException;\n"
+ + "}\n");
+ }
+
+ @Test public void typeVariables() throws Exception {
+ TypeVariableName t = TypeVariableName.get("T");
+ TypeVariableName p = TypeVariableName.get("P", Number.class);
+ ClassName location = ClassName.get(tacosPackage, "Location");
+ TypeSpec typeSpec = TypeSpec.classBuilder("Location")
+ .addTypeVariable(t)
+ .addTypeVariable(p)
+ .addSuperinterface(ParameterizedTypeName.get(ClassName.get(Comparable.class), p))
+ .addField(t, "label")
+ .addField(p, "x")
+ .addField(p, "y")
+ .addMethod(MethodSpec.methodBuilder("compareTo")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .returns(int.class)
+ .addParameter(p, "p")
+ .addCode("return 0;\n")
+ .build())
+ .addMethod(MethodSpec.methodBuilder("of")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .addTypeVariable(t)
+ .addTypeVariable(p)
+ .returns(ParameterizedTypeName.get(location, t, p))
+ .addParameter(t, "label")
+ .addParameter(p, "x")
+ .addParameter(p, "y")
+ .addCode("throw new $T($S);\n", UnsupportedOperationException.class, "TODO")
+ .build())
+ .build();
+ assertThat(toString(typeSpec)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Comparable;\n"
+ + "import java.lang.Number;\n"
+ + "import java.lang.Override;\n"
+ + "import java.lang.UnsupportedOperationException;\n"
+ + "\n"
+ + "class Location<T, P extends Number> implements Comparable<P> {\n"
+ + " T label;\n"
+ + "\n"
+ + " P x;\n"
+ + "\n"
+ + " P y;\n"
+ + "\n"
+ + " @Override\n"
+ + " public int compareTo(P p) {\n"
+ + " return 0;\n"
+ + " }\n"
+ + "\n"
+ + " public static <T, P extends Number> Location<T, P> of(T label, P x, P y) {\n"
+ + " throw new UnsupportedOperationException(\"TODO\");\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void typeVariableWithBounds() {
+ AnnotationSpec a = AnnotationSpec.builder(ClassName.get("com.squareup.tacos", "A")).build();
+ TypeVariableName p = TypeVariableName.get("P", Number.class);
+ TypeVariableName q = (TypeVariableName) TypeVariableName.get("Q", Number.class).annotated(a);
+ TypeSpec typeSpec = TypeSpec.classBuilder("Location")
+ .addTypeVariable(p.withBounds(Comparable.class))
+ .addTypeVariable(q.withBounds(Comparable.class))
+ .addField(p, "x")
+ .addField(q, "y")
+ .build();
+ assertThat(toString(typeSpec)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Comparable;\n"
+ + "import java.lang.Number;\n"
+ + "\n"
+ + "class Location<P extends Number & Comparable, @A Q extends Number & Comparable> {\n"
+ + " P x;\n"
+ + "\n"
+ + " @A Q y;\n"
+ + "}\n");
+ }
+
+ @Test public void classImplementsExtends() throws Exception {
+ ClassName taco = ClassName.get(tacosPackage, "Taco");
+ ClassName food = ClassName.get("com.squareup.tacos", "Food");
+ TypeSpec typeSpec = TypeSpec.classBuilder("Taco")
+ .addModifiers(Modifier.ABSTRACT)
+ .superclass(ParameterizedTypeName.get(ClassName.get(AbstractSet.class), food))
+ .addSuperinterface(Serializable.class)
+ .addSuperinterface(ParameterizedTypeName.get(ClassName.get(Comparable.class), taco))
+ .build();
+ assertThat(toString(typeSpec)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.io.Serializable;\n"
+ + "import java.lang.Comparable;\n"
+ + "import java.util.AbstractSet;\n"
+ + "\n"
+ + "abstract class Taco extends AbstractSet<Food> "
+ + "implements Serializable, Comparable<Taco> {\n"
+ + "}\n");
+ }
+
+ @Test public void classImplementsNestedClass() throws Exception {
+ ClassName outer = ClassName.get(tacosPackage, "Outer");
+ ClassName inner = outer.nestedClass("Inner");
+ ClassName callable = ClassName.get(Callable.class);
+ TypeSpec typeSpec = TypeSpec.classBuilder("Outer")
+ .superclass(ParameterizedTypeName.get(callable,
+ inner))
+ .addType(TypeSpec.classBuilder("Inner")
+ .addModifiers(Modifier.STATIC)
+ .build())
+ .build();
+
+ assertThat(toString(typeSpec)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.util.concurrent.Callable;\n"
+ + "\n"
+ + "class Outer extends Callable<Outer.Inner> {\n"
+ + " static class Inner {\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void enumImplements() throws Exception {
+ TypeSpec typeSpec = TypeSpec.enumBuilder("Food")
+ .addSuperinterface(Serializable.class)
+ .addSuperinterface(Cloneable.class)
+ .addEnumConstant("LEAN_GROUND_BEEF")
+ .addEnumConstant("SHREDDED_CHEESE")
+ .build();
+ assertThat(toString(typeSpec)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.io.Serializable;\n"
+ + "import java.lang.Cloneable;\n"
+ + "\n"
+ + "enum Food implements Serializable, Cloneable {\n"
+ + " LEAN_GROUND_BEEF,\n"
+ + "\n"
+ + " SHREDDED_CHEESE\n"
+ + "}\n");
+ }
+
+ @Test public void interfaceExtends() throws Exception {
+ ClassName taco = ClassName.get(tacosPackage, "Taco");
+ TypeSpec typeSpec = TypeSpec.interfaceBuilder("Taco")
+ .addSuperinterface(Serializable.class)
+ .addSuperinterface(ParameterizedTypeName.get(ClassName.get(Comparable.class), taco))
+ .build();
+ assertThat(toString(typeSpec)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.io.Serializable;\n"
+ + "import java.lang.Comparable;\n"
+ + "\n"
+ + "interface Taco extends Serializable, Comparable<Taco> {\n"
+ + "}\n");
+ }
+
+ @Test public void nestedClasses() throws Exception {
+ ClassName taco = ClassName.get(tacosPackage, "Combo", "Taco");
+ ClassName topping = ClassName.get(tacosPackage, "Combo", "Taco", "Topping");
+ ClassName chips = ClassName.get(tacosPackage, "Combo", "Chips");
+ ClassName sauce = ClassName.get(tacosPackage, "Combo", "Sauce");
+ TypeSpec typeSpec = TypeSpec.classBuilder("Combo")
+ .addField(taco, "taco")
+ .addField(chips, "chips")
+ .addType(TypeSpec.classBuilder(taco.simpleName())
+ .addModifiers(Modifier.STATIC)
+ .addField(ParameterizedTypeName.get(ClassName.get(List.class), topping), "toppings")
+ .addField(sauce, "sauce")
+ .addType(TypeSpec.enumBuilder(topping.simpleName())
+ .addEnumConstant("SHREDDED_CHEESE")
+ .addEnumConstant("LEAN_GROUND_BEEF")
+ .build())
+ .build())
+ .addType(TypeSpec.classBuilder(chips.simpleName())
+ .addModifiers(Modifier.STATIC)
+ .addField(topping, "topping")
+ .addField(sauce, "dippingSauce")
+ .build())
+ .addType(TypeSpec.enumBuilder(sauce.simpleName())
+ .addEnumConstant("SOUR_CREAM")
+ .addEnumConstant("SALSA")
+ .addEnumConstant("QUESO")
+ .addEnumConstant("MILD")
+ .addEnumConstant("FIRE")
+ .build())
+ .build();
+
+ assertThat(toString(typeSpec)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.util.List;\n"
+ + "\n"
+ + "class Combo {\n"
+ + " Taco taco;\n"
+ + "\n"
+ + " Chips chips;\n"
+ + "\n"
+ + " static class Taco {\n"
+ + " List<Topping> toppings;\n"
+ + "\n"
+ + " Sauce sauce;\n"
+ + "\n"
+ + " enum Topping {\n"
+ + " SHREDDED_CHEESE,\n"
+ + "\n"
+ + " LEAN_GROUND_BEEF\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " static class Chips {\n"
+ + " Taco.Topping topping;\n"
+ + "\n"
+ + " Sauce dippingSauce;\n"
+ + " }\n"
+ + "\n"
+ + " enum Sauce {\n"
+ + " SOUR_CREAM,\n"
+ + "\n"
+ + " SALSA,\n"
+ + "\n"
+ + " QUESO,\n"
+ + "\n"
+ + " MILD,\n"
+ + "\n"
+ + " FIRE\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void annotation() throws Exception {
+ TypeSpec annotation = TypeSpec.annotationBuilder("MyAnnotation")
+ .addModifiers(Modifier.PUBLIC)
+ .addMethod(MethodSpec.methodBuilder("test")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .defaultValue("$L", 0)
+ .returns(int.class)
+ .build())
+ .build();
+
+ assertThat(toString(annotation)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "public @interface MyAnnotation {\n"
+ + " int test() default 0;\n"
+ + "}\n"
+ );
+ }
+
+ @Test public void innerAnnotationInAnnotationDeclaration() throws Exception {
+ TypeSpec bar = TypeSpec.annotationBuilder("Bar")
+ .addMethod(MethodSpec.methodBuilder("value")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .defaultValue("@$T", Deprecated.class)
+ .returns(Deprecated.class)
+ .build())
+ .build();
+
+ assertThat(toString(bar)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Deprecated;\n"
+ + "\n"
+ + "@interface Bar {\n"
+ + " Deprecated value() default @Deprecated;\n"
+ + "}\n"
+ );
+ }
+
+ @Test public void annotationWithFields() {
+ FieldSpec field = FieldSpec.builder(int.class, "FOO")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
+ .initializer("$L", 101)
+ .build();
+
+ TypeSpec anno = TypeSpec.annotationBuilder("Anno")
+ .addField(field)
+ .build();
+
+ assertThat(toString(anno)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "@interface Anno {\n"
+ + " int FOO = 101;\n"
+ + "}\n"
+ );
+ }
+
+ @Test
+ public void classCannotHaveDefaultValueForMethod() throws Exception {
+ try {
+ TypeSpec.classBuilder("Tacos")
+ .addMethod(MethodSpec.methodBuilder("test")
+ .addModifiers(Modifier.PUBLIC)
+ .defaultValue("0")
+ .returns(int.class)
+ .build())
+ .build();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ @Test
+ public void classCannotHaveDefaultMethods() throws Exception {
+ try {
+ TypeSpec.classBuilder("Tacos")
+ .addMethod(MethodSpec.methodBuilder("test")
+ .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
+ .returns(int.class)
+ .addCode(CodeBlock.builder().addStatement("return 0").build())
+ .build())
+ .build();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ @Test
+ public void interfaceStaticMethods() throws Exception {
+ TypeSpec bar = TypeSpec.interfaceBuilder("Tacos")
+ .addMethod(MethodSpec.methodBuilder("test")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .returns(int.class)
+ .addCode(CodeBlock.builder().addStatement("return 0").build())
+ .build())
+ .build();
+
+ assertThat(toString(bar)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "interface Tacos {\n"
+ + " static int test() {\n"
+ + " return 0;\n"
+ + " }\n"
+ + "}\n"
+ );
+ }
+
+ @Test
+ public void interfaceDefaultMethods() throws Exception {
+ TypeSpec bar = TypeSpec.interfaceBuilder("Tacos")
+ .addMethod(MethodSpec.methodBuilder("test")
+ .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
+ .returns(int.class)
+ .addCode(CodeBlock.builder().addStatement("return 0").build())
+ .build())
+ .build();
+
+ assertThat(toString(bar)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "interface Tacos {\n"
+ + " default int test() {\n"
+ + " return 0;\n"
+ + " }\n"
+ + "}\n"
+ );
+ }
+
+ @Test public void referencedAndDeclaredSimpleNamesConflict() throws Exception {
+ FieldSpec internalTop = FieldSpec.builder(
+ ClassName.get(tacosPackage, "Top"), "internalTop").build();
+ FieldSpec internalBottom = FieldSpec.builder(
+ ClassName.get(tacosPackage, "Top", "Middle", "Bottom"), "internalBottom").build();
+ FieldSpec externalTop = FieldSpec.builder(
+ ClassName.get(donutsPackage, "Top"), "externalTop").build();
+ FieldSpec externalBottom = FieldSpec.builder(
+ ClassName.get(donutsPackage, "Bottom"), "externalBottom").build();
+ TypeSpec top = TypeSpec.classBuilder("Top")
+ .addField(internalTop)
+ .addField(internalBottom)
+ .addField(externalTop)
+ .addField(externalBottom)
+ .addType(TypeSpec.classBuilder("Middle")
+ .addField(internalTop)
+ .addField(internalBottom)
+ .addField(externalTop)
+ .addField(externalBottom)
+ .addType(TypeSpec.classBuilder("Bottom")
+ .addField(internalTop)
+ .addField(internalBottom)
+ .addField(externalTop)
+ .addField(externalBottom)
+ .build())
+ .build())
+ .build();
+ assertThat(toString(top)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import com.squareup.donuts.Bottom;\n"
+ + "\n"
+ + "class Top {\n"
+ + " Top internalTop;\n"
+ + "\n"
+ + " Middle.Bottom internalBottom;\n"
+ + "\n"
+ + " com.squareup.donuts.Top externalTop;\n"
+ + "\n"
+ + " Bottom externalBottom;\n"
+ + "\n"
+ + " class Middle {\n"
+ + " Top internalTop;\n"
+ + "\n"
+ + " Bottom internalBottom;\n"
+ + "\n"
+ + " com.squareup.donuts.Top externalTop;\n"
+ + "\n"
+ + " com.squareup.donuts.Bottom externalBottom;\n"
+ + "\n"
+ + " class Bottom {\n"
+ + " Top internalTop;\n"
+ + "\n"
+ + " Bottom internalBottom;\n"
+ + "\n"
+ + " com.squareup.donuts.Top externalTop;\n"
+ + "\n"
+ + " com.squareup.donuts.Bottom externalBottom;\n"
+ + " }\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void simpleNamesConflictInThisAndOtherPackage() throws Exception {
+ FieldSpec internalOther = FieldSpec.builder(
+ ClassName.get(tacosPackage, "Other"), "internalOther").build();
+ FieldSpec externalOther = FieldSpec.builder(
+ ClassName.get(donutsPackage, "Other"), "externalOther").build();
+ TypeSpec gen = TypeSpec.classBuilder("Gen")
+ .addField(internalOther)
+ .addField(externalOther)
+ .build();
+ assertThat(toString(gen)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Gen {\n"
+ + " Other internalOther;\n"
+ + "\n"
+ + " com.squareup.donuts.Other externalOther;\n"
+ + "}\n");
+ }
+
+ @Test public void originatingElementsIncludesThoseOfNestedTypes() {
+ Element outerElement = Mockito.mock(Element.class);
+ Element innerElement = Mockito.mock(Element.class);
+ TypeSpec outer = TypeSpec.classBuilder("Outer")
+ .addOriginatingElement(outerElement)
+ .addType(TypeSpec.classBuilder("Inner")
+ .addOriginatingElement(innerElement)
+ .build())
+ .build();
+ assertThat(outer.originatingElements).containsExactly(outerElement, innerElement);
+ }
+
+ @Test public void intersectionType() {
+ TypeVariableName typeVariable = TypeVariableName.get("T", Comparator.class, Serializable.class);
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addMethod(MethodSpec.methodBuilder("getComparator")
+ .addTypeVariable(typeVariable)
+ .returns(typeVariable)
+ .addCode("return null;\n")
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.io.Serializable;\n"
+ + "import java.util.Comparator;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " <T extends Comparator & Serializable> T getComparator() {\n"
+ + " return null;\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void arrayType() {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addField(int[].class, "ints")
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " int[] ints;\n"
+ + "}\n");
+ }
+
+ @Test public void javadoc() {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addJavadoc("A hard or soft tortilla, loosely folded and filled with whatever {@link \n")
+ .addJavadoc("{@link $T random} tex-mex stuff we could find in the pantry\n", Random.class)
+ .addJavadoc(CodeBlock.of("and some {@link $T} cheese.\n", String.class))
+ .addField(FieldSpec.builder(boolean.class, "soft")
+ .addJavadoc("True for a soft flour tortilla; false for a crunchy corn tortilla.\n")
+ .build())
+ .addMethod(MethodSpec.methodBuilder("refold")
+ .addJavadoc("Folds the back of this taco to reduce sauce leakage.\n"
+ + "\n"
+ + "<p>For {@link $T#KOREAN}, the front may also be folded.\n", Locale.class)
+ .addParameter(Locale.class, "locale")
+ .build())
+ .build();
+ // Mentioning a type in Javadoc will not cause an import to be added (java.util.Random here),
+ // but the short name will be used if it's already imported (java.util.Locale here).
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.util.Locale;\n"
+ + "\n"
+ + "/**\n"
+ + " * A hard or soft tortilla, loosely folded and filled with whatever {@link \n"
+ + " * {@link java.util.Random random} tex-mex stuff we could find in the pantry\n"
+ + " * and some {@link java.lang.String} cheese.\n"
+ + " */\n"
+ + "class Taco {\n"
+ + " /**\n"
+ + " * True for a soft flour tortilla; false for a crunchy corn tortilla.\n"
+ + " */\n"
+ + " boolean soft;\n"
+ + "\n"
+ + " /**\n"
+ + " * Folds the back of this taco to reduce sauce leakage.\n"
+ + " *\n"
+ + " * <p>For {@link Locale#KOREAN}, the front may also be folded.\n"
+ + " */\n"
+ + " void refold(Locale locale) {\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void annotationsInAnnotations() throws Exception {
+ ClassName beef = ClassName.get(tacosPackage, "Beef");
+ ClassName chicken = ClassName.get(tacosPackage, "Chicken");
+ ClassName option = ClassName.get(tacosPackage, "Option");
+ ClassName mealDeal = ClassName.get(tacosPackage, "MealDeal");
+ TypeSpec menu = TypeSpec.classBuilder("Menu")
+ .addAnnotation(AnnotationSpec.builder(mealDeal)
+ .addMember("price", "$L", 500)
+ .addMember("options", "$L", AnnotationSpec.builder(option)
+ .addMember("name", "$S", "taco")
+ .addMember("meat", "$T.class", beef)
+ .build())
+ .addMember("options", "$L", AnnotationSpec.builder(option)
+ .addMember("name", "$S", "quesadilla")
+ .addMember("meat", "$T.class", chicken)
+ .build())
+ .build())
+ .build();
+ assertThat(toString(menu)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "@MealDeal(\n"
+ + " price = 500,\n"
+ + " options = {\n"
+ + " @Option(name = \"taco\", meat = Beef.class),\n"
+ + " @Option(name = \"quesadilla\", meat = Chicken.class)\n"
+ + " }\n"
+ + ")\n"
+ + "class Menu {\n"
+ + "}\n");
+ }
+
+ @Test public void varargs() throws Exception {
+ TypeSpec taqueria = TypeSpec.classBuilder("Taqueria")
+ .addMethod(MethodSpec.methodBuilder("prepare")
+ .addParameter(int.class, "workers")
+ .addParameter(Runnable[].class, "jobs")
+ .varargs()
+ .build())
+ .build();
+ assertThat(toString(taqueria)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Runnable;\n"
+ + "\n"
+ + "class Taqueria {\n"
+ + " void prepare(int workers, Runnable... jobs) {\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void codeBlocks() throws Exception {
+ CodeBlock ifBlock = CodeBlock.builder()
+ .beginControlFlow("if (!a.equals(b))")
+ .addStatement("return i")
+ .endControlFlow()
+ .build();
+ CodeBlock methodBody = CodeBlock.builder()
+ .addStatement("$T size = $T.min(listA.size(), listB.size())", int.class, Math.class)
+ .beginControlFlow("for ($T i = 0; i < size; i++)", int.class)
+ .addStatement("$T $N = $N.get(i)", String.class, "a", "listA")
+ .addStatement("$T $N = $N.get(i)", String.class, "b", "listB")
+ .add("$L", ifBlock)
+ .endControlFlow()
+ .addStatement("return size")
+ .build();
+ CodeBlock fieldBlock = CodeBlock.builder()
+ .add("$>$>")
+ .add("\n$T.<$T, $T>builder()$>$>", ImmutableMap.class, String.class, String.class)
+ .add("\n.add($S, $S)", '\'', "&#39;")
+ .add("\n.add($S, $S)", '&', "&amp;")
+ .add("\n.add($S, $S)", '<', "&lt;")
+ .add("\n.add($S, $S)", '>', "&gt;")
+ .add("\n.build()$<$<")
+ .add("$<$<")
+ .build();
+ FieldSpec escapeHtml = FieldSpec.builder(ParameterizedTypeName.get(
+ Map.class, String.class, String.class), "ESCAPE_HTML")
+ .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
+ .initializer(fieldBlock)
+ .build();
+ TypeSpec util = TypeSpec.classBuilder("Util")
+ .addField(escapeHtml)
+ .addMethod(MethodSpec.methodBuilder("commonPrefixLength")
+ .returns(int.class)
+ .addParameter(ParameterizedTypeName.get(List.class, String.class), "listA")
+ .addParameter(ParameterizedTypeName.get(List.class, String.class), "listB")
+ .addCode(methodBody)
+ .build())
+ .build();
+ assertThat(toString(util)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import com.google.common.collect.ImmutableMap;\n"
+ + "import java.lang.Math;\n"
+ + "import java.lang.String;\n"
+ + "import java.util.List;\n"
+ + "import java.util.Map;\n"
+ + "\n"
+ + "class Util {\n"
+ + " private static final Map<String, String> ESCAPE_HTML = \n"
+ + " ImmutableMap.<String, String>builder()\n"
+ + " .add(\"\'\", \"&#39;\")\n"
+ + " .add(\"&\", \"&amp;\")\n"
+ + " .add(\"<\", \"&lt;\")\n"
+ + " .add(\">\", \"&gt;\")\n"
+ + " .build();\n"
+ + "\n"
+ + " int commonPrefixLength(List<String> listA, List<String> listB) {\n"
+ + " int size = Math.min(listA.size(), listB.size());\n"
+ + " for (int i = 0; i < size; i++) {\n"
+ + " String a = listA.get(i);\n"
+ + " String b = listB.get(i);\n"
+ + " if (!a.equals(b)) {\n"
+ + " return i;\n"
+ + " }\n"
+ + " }\n"
+ + " return size;\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void indexedElseIf() throws Exception {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addMethod(MethodSpec.methodBuilder("choices")
+ .beginControlFlow("if ($1L != null || $1L == $2L)", "taco", "otherTaco")
+ .addStatement("$T.out.println($S)", System.class, "only one taco? NOO!")
+ .nextControlFlow("else if ($1L.$3L && $2L.$3L)", "taco", "otherTaco", "isSupreme()")
+ .addStatement("$T.out.println($S)", System.class, "taco heaven")
+ .endControlFlow()
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.System;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " void choices() {\n"
+ + " if (taco != null || taco == otherTaco) {\n"
+ + " System.out.println(\"only one taco? NOO!\");\n"
+ + " } else if (taco.isSupreme() && otherTaco.isSupreme()) {\n"
+ + " System.out.println(\"taco heaven\");\n"
+ + " }\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void elseIf() throws Exception {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addMethod(MethodSpec.methodBuilder("choices")
+ .beginControlFlow("if (5 < 4) ")
+ .addStatement("$T.out.println($S)", System.class, "wat")
+ .nextControlFlow("else if (5 < 6)")
+ .addStatement("$T.out.println($S)", System.class, "hello")
+ .endControlFlow()
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.System;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " void choices() {\n"
+ + " if (5 < 4) {\n"
+ + " System.out.println(\"wat\");\n"
+ + " } else if (5 < 6) {\n"
+ + " System.out.println(\"hello\");\n"
+ + " }\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void doWhile() throws Exception {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addMethod(MethodSpec.methodBuilder("loopForever")
+ .beginControlFlow("do")
+ .addStatement("$T.out.println($S)", System.class, "hello")
+ .endControlFlow("while (5 < 6)")
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.System;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " void loopForever() {\n"
+ + " do {\n"
+ + " System.out.println(\"hello\");\n"
+ + " } while (5 < 6);\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void inlineIndent() throws Exception {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addMethod(MethodSpec.methodBuilder("inlineIndent")
+ .addCode("if (3 < 4) {\n$>$T.out.println($S);\n$<}\n", System.class, "hello")
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.System;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " void inlineIndent() {\n"
+ + " if (3 < 4) {\n"
+ + " System.out.println(\"hello\");\n"
+ + " }\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void defaultModifiersForInterfaceMembers() throws Exception {
+ TypeSpec taco = TypeSpec.interfaceBuilder("Taco")
+ .addField(FieldSpec.builder(String.class, "SHELL")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
+ .initializer("$S", "crunchy corn")
+ .build())
+ .addMethod(MethodSpec.methodBuilder("fold")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .build())
+ .addType(TypeSpec.classBuilder("Topping")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "interface Taco {\n"
+ + " String SHELL = \"crunchy corn\";\n"
+ + "\n"
+ + " void fold();\n"
+ + "\n"
+ + " class Topping {\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void defaultModifiersForMemberInterfacesAndEnums() throws Exception {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addType(TypeSpec.classBuilder("Meat")
+ .addModifiers(Modifier.STATIC)
+ .build())
+ .addType(TypeSpec.interfaceBuilder("Tortilla")
+ .addModifiers(Modifier.STATIC)
+ .build())
+ .addType(TypeSpec.enumBuilder("Topping")
+ .addModifiers(Modifier.STATIC)
+ .addEnumConstant("SALSA")
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " static class Meat {\n"
+ + " }\n"
+ + "\n"
+ + " interface Tortilla {\n"
+ + " }\n"
+ + "\n"
+ + " enum Topping {\n"
+ + " SALSA\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void membersOrdering() throws Exception {
+ // Hand out names in reverse-alphabetical order to defend against unexpected sorting.
+ TypeSpec taco = TypeSpec.classBuilder("Members")
+ .addType(TypeSpec.classBuilder("Z").build())
+ .addType(TypeSpec.classBuilder("Y").build())
+ .addField(String.class, "X", Modifier.STATIC)
+ .addField(String.class, "W")
+ .addField(String.class, "V", Modifier.STATIC)
+ .addField(String.class, "U")
+ .addMethod(MethodSpec.methodBuilder("T").addModifiers(Modifier.STATIC).build())
+ .addMethod(MethodSpec.methodBuilder("S").build())
+ .addMethod(MethodSpec.methodBuilder("R").addModifiers(Modifier.STATIC).build())
+ .addMethod(MethodSpec.methodBuilder("Q").build())
+ .addMethod(MethodSpec.constructorBuilder().addParameter(int.class, "p").build())
+ .addMethod(MethodSpec.constructorBuilder().addParameter(long.class, "o").build())
+ .build();
+ // Static fields, instance fields, constructors, methods, classes.
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "class Members {\n"
+ + " static String X;\n"
+ + "\n"
+ + " static String V;\n"
+ + "\n"
+ + " String W;\n"
+ + "\n"
+ + " String U;\n"
+ + "\n"
+ + " Members(int p) {\n"
+ + " }\n"
+ + "\n"
+ + " Members(long o) {\n"
+ + " }\n"
+ + "\n"
+ + " static void T() {\n"
+ + " }\n"
+ + "\n"
+ + " void S() {\n"
+ + " }\n"
+ + "\n"
+ + " static void R() {\n"
+ + " }\n"
+ + "\n"
+ + " void Q() {\n"
+ + " }\n"
+ + "\n"
+ + " class Z {\n"
+ + " }\n"
+ + "\n"
+ + " class Y {\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void nativeMethods() throws Exception {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addMethod(MethodSpec.methodBuilder("nativeInt")
+ .addModifiers(Modifier.NATIVE)
+ .returns(int.class)
+ .build())
+ // GWT JSNI
+ .addMethod(MethodSpec.methodBuilder("alert")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.NATIVE)
+ .addParameter(String.class, "msg")
+ .addCode(CodeBlock.builder()
+ .add(" /*-{\n")
+ .indent()
+ .addStatement("$$wnd.alert(msg)")
+ .unindent()
+ .add("}-*/")
+ .build())
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " native int nativeInt();\n"
+ + "\n"
+ + " public static native void alert(String msg) /*-{\n"
+ + " $wnd.alert(msg);\n"
+ + " }-*/;\n"
+ + "}\n");
+ }
+
+ @Test public void nullStringLiteral() throws Exception {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addField(FieldSpec.builder(String.class, "NULL")
+ .initializer("$S", (Object) null)
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " String NULL = null;\n"
+ + "}\n");
+ }
+
+ @Test public void annotationToString() throws Exception {
+ AnnotationSpec annotation = AnnotationSpec.builder(SuppressWarnings.class)
+ .addMember("value", "$S", "unused")
+ .build();
+ assertThat(annotation.toString()).isEqualTo("@java.lang.SuppressWarnings(\"unused\")");
+ }
+
+ @Test public void codeBlockToString() throws Exception {
+ CodeBlock codeBlock = CodeBlock.builder()
+ .addStatement("$T $N = $S.substring(0, 3)", String.class, "s", "taco")
+ .build();
+ assertThat(codeBlock.toString()).isEqualTo("java.lang.String s = \"taco\".substring(0, 3);\n");
+ }
+
+ @Test public void codeBlockAddStatementOfCodeBlockToString() throws Exception {
+ CodeBlock contents = CodeBlock.of("$T $N = $S.substring(0, 3)", String.class, "s", "taco");
+ CodeBlock statement = CodeBlock.builder().addStatement(contents).build();
+ assertThat(statement.toString()).isEqualTo("java.lang.String s = \"taco\".substring(0, 3);\n");
+ }
+
+ @Test public void fieldToString() throws Exception {
+ FieldSpec field = FieldSpec.builder(String.class, "s", Modifier.FINAL)
+ .initializer("$S.substring(0, 3)", "taco")
+ .build();
+ assertThat(field.toString())
+ .isEqualTo("final java.lang.String s = \"taco\".substring(0, 3);\n");
+ }
+
+ @Test public void methodToString() throws Exception {
+ MethodSpec method = MethodSpec.methodBuilder("toString")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .returns(String.class)
+ .addStatement("return $S", "taco")
+ .build();
+ assertThat(method.toString()).isEqualTo(""
+ + "@java.lang.Override\n"
+ + "public java.lang.String toString() {\n"
+ + " return \"taco\";\n"
+ + "}\n");
+ }
+
+ @Test public void constructorToString() throws Exception {
+ MethodSpec constructor = MethodSpec.constructorBuilder()
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(ClassName.get(tacosPackage, "Taco"), "taco")
+ .addStatement("this.$N = $N", "taco", "taco")
+ .build();
+ assertThat(constructor.toString()).isEqualTo(""
+ + "public Constructor(com.squareup.tacos.Taco taco) {\n"
+ + " this.taco = taco;\n"
+ + "}\n");
+ }
+
+ @Test public void parameterToString() throws Exception {
+ ParameterSpec parameter = ParameterSpec.builder(ClassName.get(tacosPackage, "Taco"), "taco")
+ .addModifiers(Modifier.FINAL)
+ .addAnnotation(ClassName.get("javax.annotation", "Nullable"))
+ .build();
+ assertThat(parameter.toString())
+ .isEqualTo("@javax.annotation.Nullable final com.squareup.tacos.Taco taco");
+ }
+
+ @Test public void classToString() throws Exception {
+ TypeSpec type = TypeSpec.classBuilder("Taco")
+ .build();
+ assertThat(type.toString()).isEqualTo(""
+ + "class Taco {\n"
+ + "}\n");
+ }
+
+ @Test public void anonymousClassToString() throws Exception {
+ TypeSpec type = TypeSpec.anonymousClassBuilder("")
+ .addSuperinterface(Runnable.class)
+ .addMethod(MethodSpec.methodBuilder("run")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .build())
+ .build();
+ assertThat(type.toString()).isEqualTo(""
+ + "new java.lang.Runnable() {\n"
+ + " @java.lang.Override\n"
+ + " public void run() {\n"
+ + " }\n"
+ + "}");
+ }
+
+ @Test public void interfaceClassToString() throws Exception {
+ TypeSpec type = TypeSpec.interfaceBuilder("Taco")
+ .build();
+ assertThat(type.toString()).isEqualTo(""
+ + "interface Taco {\n"
+ + "}\n");
+ }
+
+ @Test public void annotationDeclarationToString() throws Exception {
+ TypeSpec type = TypeSpec.annotationBuilder("Taco")
+ .build();
+ assertThat(type.toString()).isEqualTo(""
+ + "@interface Taco {\n"
+ + "}\n");
+ }
+
+ private String toString(TypeSpec typeSpec) {
+ return JavaFile.builder(tacosPackage, typeSpec).build().toString();
+ }
+
+ @Test public void multilineStatement() throws Exception {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addMethod(MethodSpec.methodBuilder("toString")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .returns(String.class)
+ .addStatement("return $S\n+ $S\n+ $S\n+ $S\n+ $S",
+ "Taco(", "beef,", "lettuce,", "cheese", ")")
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Override;\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " @Override\n"
+ + " public String toString() {\n"
+ + " return \"Taco(\"\n"
+ + " + \"beef,\"\n"
+ + " + \"lettuce,\"\n"
+ + " + \"cheese\"\n"
+ + " + \")\";\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void multilineStatementWithAnonymousClass() throws Exception {
+ TypeName stringComparator = ParameterizedTypeName.get(Comparator.class, String.class);
+ TypeName listOfString = ParameterizedTypeName.get(List.class, String.class);
+ TypeSpec prefixComparator = TypeSpec.anonymousClassBuilder("")
+ .addSuperinterface(stringComparator)
+ .addMethod(MethodSpec.methodBuilder("compare")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .returns(int.class)
+ .addParameter(String.class, "a")
+ .addParameter(String.class, "b")
+ .addStatement("return a.substring(0, length)\n"
+ + ".compareTo(b.substring(0, length))")
+ .build())
+ .build();
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addMethod(MethodSpec.methodBuilder("comparePrefix")
+ .returns(stringComparator)
+ .addParameter(int.class, "length", Modifier.FINAL)
+ .addStatement("return $L", prefixComparator)
+ .build())
+ .addMethod(MethodSpec.methodBuilder("sortPrefix")
+ .addParameter(listOfString, "list")
+ .addParameter(int.class, "length", Modifier.FINAL)
+ .addStatement("$T.sort(\nlist,\n$L)", Collections.class, prefixComparator)
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Override;\n"
+ + "import java.lang.String;\n"
+ + "import java.util.Collections;\n"
+ + "import java.util.Comparator;\n"
+ + "import java.util.List;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " Comparator<String> comparePrefix(final int length) {\n"
+ + " return new Comparator<String>() {\n"
+ + " @Override\n"
+ + " public int compare(String a, String b) {\n"
+ + " return a.substring(0, length)\n"
+ + " .compareTo(b.substring(0, length));\n"
+ + " }\n"
+ + " };\n"
+ + " }\n"
+ + "\n"
+ + " void sortPrefix(List<String> list, final int length) {\n"
+ + " Collections.sort(\n"
+ + " list,\n"
+ + " new Comparator<String>() {\n"
+ + " @Override\n"
+ + " public int compare(String a, String b) {\n"
+ + " return a.substring(0, length)\n"
+ + " .compareTo(b.substring(0, length));\n"
+ + " }\n"
+ + " });\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void multilineStrings() throws Exception {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addField(FieldSpec.builder(String.class, "toppings")
+ .initializer("$S", "shell\nbeef\nlettuce\ncheese\n")
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " String toppings = \"shell\\n\"\n"
+ + " + \"beef\\n\"\n"
+ + " + \"lettuce\\n\"\n"
+ + " + \"cheese\\n\";\n"
+ + "}\n");
+ }
+
+ @Test public void doubleFieldInitialization() {
+ try {
+ FieldSpec.builder(String.class, "listA")
+ .initializer("foo")
+ .initializer("bar")
+ .build();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ try {
+ FieldSpec.builder(String.class, "listA")
+ .initializer(CodeBlock.builder().add("foo").build())
+ .initializer(CodeBlock.builder().add("bar").build())
+ .build();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ @Test public void nullAnnotationsAddition() {
+ try {
+ TypeSpec.classBuilder("Taco").addAnnotations(null);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage())
+ .isEqualTo("annotationSpecs == null");
+ }
+ }
+
+ @Test public void multipleAnnotationAddition() {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addAnnotations(Arrays.asList(
+ AnnotationSpec.builder(SuppressWarnings.class)
+ .addMember("value", "$S", "unchecked")
+ .build(),
+ AnnotationSpec.builder(Deprecated.class).build()))
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Deprecated;\n"
+ + "import java.lang.SuppressWarnings;\n"
+ + "\n"
+ + "@SuppressWarnings(\"unchecked\")\n"
+ + "@Deprecated\n"
+ + "class Taco {\n"
+ + "}\n");
+ }
+
+ @Test public void nullFieldsAddition() {
+ try {
+ TypeSpec.classBuilder("Taco").addFields(null);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage())
+ .isEqualTo("fieldSpecs == null");
+ }
+ }
+
+ @Test public void multipleFieldAddition() {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addFields(Arrays.asList(
+ FieldSpec.builder(int.class, "ANSWER", Modifier.STATIC, Modifier.FINAL).build(),
+ FieldSpec.builder(BigDecimal.class, "price", Modifier.PRIVATE).build()))
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.math.BigDecimal;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " static final int ANSWER;\n"
+ + "\n"
+ + " private BigDecimal price;\n"
+ + "}\n");
+ }
+
+ @Test public void nullMethodsAddition() {
+ try {
+ TypeSpec.classBuilder("Taco").addMethods(null);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage())
+ .isEqualTo("methodSpecs == null");
+ }
+ }
+
+ @Test public void multipleMethodAddition() {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addMethods(Arrays.asList(
+ MethodSpec.methodBuilder("getAnswer")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .returns(int.class)
+ .addStatement("return $L", 42)
+ .build(),
+ MethodSpec.methodBuilder("getRandomQuantity")
+ .addModifiers(Modifier.PUBLIC)
+ .returns(int.class)
+ .addJavadoc("chosen by fair dice roll ;)")
+ .addStatement("return $L", 4)
+ .build()))
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " public static int getAnswer() {\n"
+ + " return 42;\n"
+ + " }\n"
+ + "\n"
+ + " /**\n"
+ + " * chosen by fair dice roll ;) */\n"
+ + " public int getRandomQuantity() {\n"
+ + " return 4;\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void nullSuperinterfacesAddition() {
+ try {
+ TypeSpec.classBuilder("Taco").addSuperinterfaces(null);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage())
+ .isEqualTo("superinterfaces == null");
+ }
+ }
+
+ @Test public void nullSingleSuperinterfaceAddition() {
+ try {
+ TypeSpec.classBuilder("Taco").addSuperinterface((TypeName) null);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage())
+ .isEqualTo("superinterface == null");
+ }
+ }
+
+ @Test public void nullInSuperinterfaceIterableAddition() {
+ List<TypeName> superinterfaces = new ArrayList<>();
+ superinterfaces.add(TypeName.get(List.class));
+ superinterfaces.add(null);
+
+ try {
+ TypeSpec.classBuilder("Taco").addSuperinterfaces(superinterfaces);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage())
+ .isEqualTo("superinterface == null");
+ }
+ }
+
+ @Test public void multipleSuperinterfaceAddition() {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addSuperinterfaces(Arrays.asList(
+ TypeName.get(Serializable.class),
+ TypeName.get(EventListener.class)))
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.io.Serializable;\n"
+ + "import java.util.EventListener;\n"
+ + "\n"
+ + "class Taco implements Serializable, EventListener {\n"
+ + "}\n");
+ }
+
+ @Test public void nullModifiersAddition() {
+ try {
+ TypeSpec.classBuilder("Taco").addModifiers((Modifier) null);
+ fail();
+ } catch(IllegalArgumentException expected) {
+ assertThat(expected.getMessage())
+ .isEqualTo("modifiers contain null");
+ }
+ }
+
+ @Test public void nullTypeVariablesAddition() {
+ try {
+ TypeSpec.classBuilder("Taco").addTypeVariables(null);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage())
+ .isEqualTo("typeVariables == null");
+ }
+ }
+
+ @Test public void multipleTypeVariableAddition() {
+ TypeSpec location = TypeSpec.classBuilder("Location")
+ .addTypeVariables(Arrays.asList(
+ TypeVariableName.get("T"),
+ TypeVariableName.get("P", Number.class)))
+ .build();
+ assertThat(toString(location)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Number;\n"
+ + "\n"
+ + "class Location<T, P extends Number> {\n"
+ + "}\n");
+ }
+
+ @Test public void nullTypesAddition() {
+ try {
+ TypeSpec.classBuilder("Taco").addTypes(null);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage())
+ .isEqualTo("typeSpecs == null");
+ }
+ }
+
+ @Test public void multipleTypeAddition() {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addTypes(Arrays.asList(
+ TypeSpec.classBuilder("Topping").build(),
+ TypeSpec.classBuilder("Sauce").build()))
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " class Topping {\n"
+ + " }\n"
+ + "\n"
+ + " class Sauce {\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void tryCatch() {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addMethod(MethodSpec.methodBuilder("addTopping")
+ .addParameter(ClassName.get("com.squareup.tacos", "Topping"), "topping")
+ .beginControlFlow("try")
+ .addCode("/* do something tricky with the topping */\n")
+ .nextControlFlow("catch ($T e)",
+ ClassName.get("com.squareup.tacos", "IllegalToppingException"))
+ .endControlFlow()
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " void addTopping(Topping topping) {\n"
+ + " try {\n"
+ + " /* do something tricky with the topping */\n"
+ + " } catch (IllegalToppingException e) {\n"
+ + " }\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void ifElse() {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addMethod(
+ MethodSpec.methodBuilder("isDelicious")
+ .addParameter(TypeName.INT, "count")
+ .returns(TypeName.BOOLEAN)
+ .beginControlFlow("if (count > 0)")
+ .addStatement("return true")
+ .nextControlFlow("else")
+ .addStatement("return false")
+ .endControlFlow()
+ .build()
+ )
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " boolean isDelicious(int count) {\n"
+ + " if (count > 0) {\n"
+ + " return true;\n"
+ + " } else {\n"
+ + " return false;\n"
+ + " }\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void literalFromAnything() {
+ Object value = new Object() {
+ @Override public String toString() {
+ return "foo";
+ }
+ };
+ assertThat(CodeBlock.of("$L", value).toString()).isEqualTo("foo");
+ }
+
+ @Test public void nameFromCharSequence() {
+ assertThat(CodeBlock.of("$N", "text").toString()).isEqualTo("text");
+ }
+
+ @Test public void nameFromField() {
+ FieldSpec field = FieldSpec.builder(String.class, "field").build();
+ assertThat(CodeBlock.of("$N", field).toString()).isEqualTo("field");
+ }
+
+ @Test public void nameFromParameter() {
+ ParameterSpec parameter = ParameterSpec.builder(String.class, "parameter").build();
+ assertThat(CodeBlock.of("$N", parameter).toString()).isEqualTo("parameter");
+ }
+
+ @Test public void nameFromMethod() {
+ MethodSpec method = MethodSpec.methodBuilder("method")
+ .addModifiers(Modifier.ABSTRACT)
+ .returns(String.class)
+ .build();
+ assertThat(CodeBlock.of("$N", method).toString()).isEqualTo("method");
+ }
+
+ @Test public void nameFromType() {
+ TypeSpec type = TypeSpec.classBuilder("Type").build();
+ assertThat(CodeBlock.of("$N", type).toString()).isEqualTo("Type");
+ }
+
+ @Test public void nameFromUnsupportedType() {
+ try {
+ CodeBlock.builder().add("$N", String.class);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("expected name but was " + String.class);
+ }
+ }
+
+ @Test public void stringFromAnything() {
+ Object value = new Object() {
+ @Override public String toString() {
+ return "foo";
+ }
+ };
+ assertThat(CodeBlock.of("$S", value).toString()).isEqualTo("\"foo\"");
+ }
+
+ @Test public void stringFromNull() {
+ assertThat(CodeBlock.of("$S", new Object[] {null}).toString()).isEqualTo("null");
+ }
+
+ @Test public void typeFromTypeName() {
+ TypeName typeName = TypeName.get(String.class);
+ assertThat(CodeBlock.of("$T", typeName).toString()).isEqualTo("java.lang.String");
+ }
+
+ @Test public void typeFromTypeMirror() {
+ TypeMirror mirror = getElement(String.class).asType();
+ assertThat(CodeBlock.of("$T", mirror).toString()).isEqualTo("java.lang.String");
+ }
+
+ @Test public void typeFromTypeElement() {
+ TypeElement element = getElement(String.class);
+ assertThat(CodeBlock.of("$T", element).toString()).isEqualTo("java.lang.String");
+ }
+
+ @Test public void typeFromReflectType() {
+ assertThat(CodeBlock.of("$T", String.class).toString()).isEqualTo("java.lang.String");
+ }
+
+ @Test public void typeFromUnsupportedType() {
+ try {
+ CodeBlock.builder().add("$T", "java.lang.String");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("expected type but was java.lang.String");
+ }
+ }
+
+ @Test public void tooFewArguments() {
+ try {
+ CodeBlock.builder().add("$S");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("index 1 for '$S' not in range (received 0 arguments)");
+ }
+ }
+
+ @Test public void unusedArgumentsRelative() {
+ try {
+ CodeBlock.builder().add("$L $L", "a", "b", "c");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("unused arguments: expected 2, received 3");
+ }
+ }
+
+ @Test public void unusedArgumentsIndexed() {
+ try {
+ CodeBlock.builder().add("$1L $2L", "a", "b", "c");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("unused argument: $3");
+ }
+ try {
+ CodeBlock.builder().add("$1L $1L $1L", "a", "b", "c");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("unused arguments: $2, $3");
+ }
+ try {
+ CodeBlock.builder().add("$3L $1L $3L $1L $3L", "a", "b", "c", "d");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo("unused arguments: $2, $4");
+ }
+ }
+
+ @Test public void superClassOnlyValidForClasses() {
+ try {
+ TypeSpec.annotationBuilder("A").superclass(ClassName.get(Object.class));
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ TypeSpec.enumBuilder("E").superclass(ClassName.get(Object.class));
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ TypeSpec.interfaceBuilder("I").superclass(ClassName.get(Object.class));
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ @Test public void invalidSuperClass() {
+ try {
+ TypeSpec.classBuilder("foo")
+ .superclass(ClassName.get(List.class))
+ .superclass(ClassName.get(Map.class));
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ TypeSpec.classBuilder("foo")
+ .superclass(TypeName.INT);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test public void staticCodeBlock() {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addField(String.class, "foo", Modifier.PRIVATE)
+ .addField(String.class, "FOO", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
+ .addStaticBlock(CodeBlock.builder()
+ .addStatement("FOO = $S", "FOO")
+ .build())
+ .addMethod(MethodSpec.methodBuilder("toString")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .returns(String.class)
+ .addCode("return FOO;\n")
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Override;\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " private static final String FOO;\n"
+ + "\n"
+ + " static {\n"
+ + " FOO = \"FOO\";\n"
+ + " }\n"
+ + "\n"
+ + " private String foo;\n"
+ + "\n"
+ + " @Override\n"
+ + " public String toString() {\n"
+ + " return FOO;\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void initializerBlockInRightPlace() {
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addField(String.class, "foo", Modifier.PRIVATE)
+ .addField(String.class, "FOO", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
+ .addStaticBlock(CodeBlock.builder()
+ .addStatement("FOO = $S", "FOO")
+ .build())
+ .addMethod(MethodSpec.constructorBuilder().build())
+ .addMethod(MethodSpec.methodBuilder("toString")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .returns(String.class)
+ .addCode("return FOO;\n")
+ .build())
+ .addInitializerBlock(CodeBlock.builder()
+ .addStatement("foo = $S", "FOO")
+ .build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Override;\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " private static final String FOO;\n"
+ + "\n"
+ + " static {\n"
+ + " FOO = \"FOO\";\n"
+ + " }\n"
+ + "\n"
+ + " private String foo;\n"
+ + "\n"
+ + " {\n"
+ + " foo = \"FOO\";\n"
+ + " }\n"
+ + "\n"
+ + " Taco() {\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public String toString() {\n"
+ + " return FOO;\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void initializersToBuilder() {
+ // Tests if toBuilder() contains correct static and instance initializers
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addField(String.class, "foo", Modifier.PRIVATE)
+ .addField(String.class, "FOO", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
+ .addStaticBlock(CodeBlock.builder()
+ .addStatement("FOO = $S", "FOO")
+ .build())
+ .addMethod(MethodSpec.constructorBuilder().build())
+ .addMethod(MethodSpec.methodBuilder("toString")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .returns(String.class)
+ .addCode("return FOO;\n")
+ .build())
+ .addInitializerBlock(CodeBlock.builder()
+ .addStatement("foo = $S", "FOO")
+ .build())
+ .build();
+
+ TypeSpec recreatedTaco = taco.toBuilder().build();
+ assertThat(toString(taco)).isEqualTo(toString(recreatedTaco));
+
+ TypeSpec initializersAdded = taco.toBuilder()
+ .addInitializerBlock(CodeBlock.builder()
+ .addStatement("foo = $S", "instanceFoo")
+ .build())
+ .addStaticBlock(CodeBlock.builder()
+ .addStatement("FOO = $S", "staticFoo")
+ .build())
+ .build();
+
+ assertThat(toString(initializersAdded)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.Override;\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " private static final String FOO;\n"
+ + "\n"
+ + " static {\n"
+ + " FOO = \"FOO\";\n"
+ + " }\n"
+ + " static {\n"
+ + " FOO = \"staticFoo\";\n"
+ + " }\n"
+ + "\n"
+ + " private String foo;\n"
+ + "\n"
+ + " {\n"
+ + " foo = \"FOO\";\n"
+ + " }\n"
+ + " {\n"
+ + " foo = \"instanceFoo\";\n"
+ + " }\n"
+ + "\n"
+ + " Taco() {\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public String toString() {\n"
+ + " return FOO;\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void initializerBlockUnsupportedExceptionOnInterface() {
+ TypeSpec.Builder interfaceBuilder = TypeSpec.interfaceBuilder("Taco");
+ try {
+ interfaceBuilder.addInitializerBlock(CodeBlock.builder().build());
+ fail("Exception expected");
+ } catch (UnsupportedOperationException e) {
+ }
+ }
+
+ @Test public void initializerBlockUnsupportedExceptionOnAnnotation() {
+ TypeSpec.Builder annotationBuilder = TypeSpec.annotationBuilder("Taco");
+ try {
+ annotationBuilder.addInitializerBlock(CodeBlock.builder().build());
+ fail("Exception expected");
+ } catch (UnsupportedOperationException e) {
+ }
+ }
+
+ @Test public void lineWrapping() {
+ MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("call");
+ methodBuilder.addCode("$[call(");
+ for (int i = 0; i < 32; i++) {
+ methodBuilder.addParameter(String.class, "s" + i);
+ methodBuilder.addCode(i > 0 ? ",$W$S" : "$S", i);
+ }
+ methodBuilder.addCode(");$]\n");
+
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addMethod(methodBuilder.build())
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " void call(String s0, String s1, String s2, String s3, String s4, String s5, String s6, String s7,\n"
+ + " String s8, String s9, String s10, String s11, String s12, String s13, String s14, String s15,\n"
+ + " String s16, String s17, String s18, String s19, String s20, String s21, String s22,\n"
+ + " String s23, String s24, String s25, String s26, String s27, String s28, String s29,\n"
+ + " String s30, String s31) {\n"
+ + " call(\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\", \"11\", \"12\", \"13\", \"14\", \"15\", \"16\",\n"
+ + " \"17\", \"18\", \"19\", \"20\", \"21\", \"22\", \"23\", \"24\", \"25\", \"26\", \"27\", \"28\", \"29\", \"30\", \"31\");\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void lineWrappingWithZeroWidthSpace() {
+ MethodSpec method = MethodSpec.methodBuilder("call")
+ .addCode("$[iAmSickOfWaitingInLine($Z")
+ .addCode("it, has, been, far, too, long, of, a, wait, and, i, would, like, to, eat, ")
+ .addCode("this, is, a, run, on, sentence")
+ .addCode(");$]\n")
+ .build();
+
+ TypeSpec taco = TypeSpec.classBuilder("Taco")
+ .addMethod(method)
+ .build();
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " void call() {\n"
+ + " iAmSickOfWaitingInLine(\n"
+ + " it, has, been, far, too, long, of, a, wait, and, i, would, like, to, eat, this, is, a, run, on, sentence);\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test public void equalsAndHashCode() {
+ TypeSpec a = TypeSpec.interfaceBuilder("taco").build();
+ TypeSpec b = TypeSpec.interfaceBuilder("taco").build();
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ a = TypeSpec.classBuilder("taco").build();
+ b = TypeSpec.classBuilder("taco").build();
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ a = TypeSpec.enumBuilder("taco").addEnumConstant("SALSA").build();
+ b = TypeSpec.enumBuilder("taco").addEnumConstant("SALSA").build();
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ a = TypeSpec.annotationBuilder("taco").build();
+ b = TypeSpec.annotationBuilder("taco").build();
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ }
+
+ @Test public void classNameFactories() {
+ ClassName className = ClassName.get("com.example", "Example");
+ assertThat(TypeSpec.classBuilder(className).build().name).isEqualTo("Example");
+ assertThat(TypeSpec.interfaceBuilder(className).build().name).isEqualTo("Example");
+ assertThat(TypeSpec.enumBuilder(className).addEnumConstant("A").build().name).isEqualTo("Example");
+ assertThat(TypeSpec.annotationBuilder(className).build().name).isEqualTo("Example");
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/TypesEclipseTest.java b/src/test/java/com/squareup/javapoet/TypesEclipseTest.java
new file mode 100644
index 0000000..2759f17
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/TypesEclipseTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import static com.google.common.base.Charsets.*;
+import static com.google.common.base.Preconditions.*;
+
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+
+import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.runners.model.Statement;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+@RunWith(JUnit4.class)
+public final class TypesEclipseTest extends AbstractTypesTest {
+ /**
+ * A {@link JUnit4} {@link Rule} that executes tests such that a instances of {@link Elements} and
+ * {@link Types} are available during execution.
+ *
+ * <p>To use this rule in a test, just add the following field: <pre> {@code
+ * @Rule public CompilationRule compilationRule = new CompilationRule();}
+ *
+ * @author Gregory Kick
+ */
+ public static final class CompilationRule implements TestRule {
+ private Elements elements;
+ private Types types;
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return new Statement() {
+ @Override public void evaluate() throws Throwable {
+ final AtomicReference<Throwable> thrown = new AtomicReference<>();
+ boolean successful = compile(ImmutableList.of(new AbstractProcessor() {
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+
+ @Override
+ public Set<String> getSupportedAnnotationTypes() {
+ return ImmutableSet.of("*");
+ }
+
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+ elements = processingEnv.getElementUtils();
+ types = processingEnv.getTypeUtils();
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations,
+ RoundEnvironment roundEnv) {
+ // just run the test on the last round after compilation is over
+ if (roundEnv.processingOver()) {
+ try {
+ base.evaluate();
+ } catch (Throwable e) {
+ thrown.set(e);
+ }
+ }
+ return false;
+ }
+ }));
+ checkState(successful);
+ Throwable t = thrown.get();
+ if (t != null) {
+ throw t;
+ }
+ }
+ };
+ }
+
+ /**
+ * Returns the {@link Elements} instance associated with the current execution of the rule.
+ *
+ * @throws IllegalStateException if this method is invoked outside the execution of the rule.
+ */
+ public Elements getElements() {
+ checkState(elements != null, "Not running within the rule");
+ return elements;
+ }
+
+ /**
+ * Returns the {@link Types} instance associated with the current execution of the rule.
+ *
+ * @throws IllegalStateException if this method is invoked outside the execution of the rule.
+ */
+ public Types getTypes() {
+ checkState(elements != null, "Not running within the rule");
+ return types;
+ }
+
+ static private boolean compile(Iterable<? extends Processor> processors) {
+ JavaCompiler compiler = new EclipseCompiler();
+ DiagnosticCollector<JavaFileObject> diagnosticCollector =
+ new DiagnosticCollector<>();
+ JavaFileManager fileManager = compiler.getStandardFileManager(diagnosticCollector, Locale.getDefault(), UTF_8);
+ JavaCompiler.CompilationTask task = compiler.getTask(
+ null,
+ fileManager,
+ diagnosticCollector,
+ ImmutableSet.of(),
+ ImmutableSet.of(TypesEclipseTest.class.getCanonicalName()),
+ ImmutableSet.of());
+ task.setProcessors(processors);
+ return task.call();
+ }
+ }
+
+ @Rule public final CompilationRule compilation = new CompilationRule();
+
+ @Override
+ protected Elements getElements() {
+ return compilation.getElements();
+ }
+
+ @Override
+ protected Types getTypes() {
+ return compilation.getTypes();
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/TypesTest.java b/src/test/java/com/squareup/javapoet/TypesTest.java
new file mode 100644
index 0000000..2455ae5
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/TypesTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import com.google.testing.compile.CompilationRule;
+
+@RunWith(JUnit4.class)
+public final class TypesTest extends AbstractTypesTest {
+ @Rule public final CompilationRule compilation = new CompilationRule();
+
+ @Override
+ protected Elements getElements() {
+ return compilation.getElements();
+ }
+
+ @Override
+ protected Types getTypes() {
+ return compilation.getTypes();
+ }
+}
diff --git a/src/test/java/com/squareup/javapoet/UtilTest.java b/src/test/java/com/squareup/javapoet/UtilTest.java
new file mode 100644
index 0000000..d4b9c52
--- /dev/null
+++ b/src/test/java/com/squareup/javapoet/UtilTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.javapoet;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class UtilTest {
+ @Test public void characterLiteral() {
+ assertEquals("a", Util.characterLiteralWithoutSingleQuotes('a'));
+ assertEquals("b", Util.characterLiteralWithoutSingleQuotes('b'));
+ assertEquals("c", Util.characterLiteralWithoutSingleQuotes('c'));
+ assertEquals("%", Util.characterLiteralWithoutSingleQuotes('%'));
+ // common escapes
+ assertEquals("\\b", Util.characterLiteralWithoutSingleQuotes('\b'));
+ assertEquals("\\t", Util.characterLiteralWithoutSingleQuotes('\t'));
+ assertEquals("\\n", Util.characterLiteralWithoutSingleQuotes('\n'));
+ assertEquals("\\f", Util.characterLiteralWithoutSingleQuotes('\f'));
+ assertEquals("\\r", Util.characterLiteralWithoutSingleQuotes('\r'));
+ assertEquals("\"", Util.characterLiteralWithoutSingleQuotes('"'));
+ assertEquals("\\'", Util.characterLiteralWithoutSingleQuotes('\''));
+ assertEquals("\\\\", Util.characterLiteralWithoutSingleQuotes('\\'));
+ // octal escapes
+ assertEquals("\\u0000", Util.characterLiteralWithoutSingleQuotes('\0'));
+ assertEquals("\\u0007", Util.characterLiteralWithoutSingleQuotes('\7'));
+ assertEquals("?", Util.characterLiteralWithoutSingleQuotes('\77'));
+ assertEquals("\\u007f", Util.characterLiteralWithoutSingleQuotes('\177'));
+ assertEquals("¿", Util.characterLiteralWithoutSingleQuotes('\277'));
+ assertEquals("ÿ", Util.characterLiteralWithoutSingleQuotes('\377'));
+ // unicode escapes
+ assertEquals("\\u0000", Util.characterLiteralWithoutSingleQuotes('\u0000'));
+ assertEquals("\\u0001", Util.characterLiteralWithoutSingleQuotes('\u0001'));
+ assertEquals("\\u0002", Util.characterLiteralWithoutSingleQuotes('\u0002'));
+ assertEquals("€", Util.characterLiteralWithoutSingleQuotes('\u20AC'));
+ assertEquals("☃", Util.characterLiteralWithoutSingleQuotes('\u2603'));
+ assertEquals("♠", Util.characterLiteralWithoutSingleQuotes('\u2660'));
+ assertEquals("♣", Util.characterLiteralWithoutSingleQuotes('\u2663'));
+ assertEquals("♥", Util.characterLiteralWithoutSingleQuotes('\u2665'));
+ assertEquals("♦", Util.characterLiteralWithoutSingleQuotes('\u2666'));
+ assertEquals("✵", Util.characterLiteralWithoutSingleQuotes('\u2735'));
+ assertEquals("✺", Util.characterLiteralWithoutSingleQuotes('\u273A'));
+ assertEquals("/", Util.characterLiteralWithoutSingleQuotes('\uFF0F'));
+ }
+
+ @Test public void stringLiteral() {
+ stringLiteral("abc");
+ stringLiteral("♦♥♠♣");
+ stringLiteral("€\\t@\\t$", "€\t@\t$", " ");
+ stringLiteral("abc();\\n\"\n + \"def();", "abc();\ndef();", " ");
+ stringLiteral("This is \\\"quoted\\\"!", "This is \"quoted\"!", " ");
+ stringLiteral("e^{i\\\\pi}+1=0", "e^{i\\pi}+1=0", " ");
+ }
+
+ void stringLiteral(String string) {
+ stringLiteral(string, string, " ");
+ }
+
+ void stringLiteral(String expected, String value, String indent) {
+ assertEquals("\"" + expected + "\"", Util.stringLiteralWithDoubleQuotes(value, indent));
+ }
+}