aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJake Wharton <JakeWharton@GMail.com>2015-01-07 11:21:31 -0800
committerJake Wharton <JakeWharton@GMail.com>2015-01-07 11:21:31 -0800
commit21a955d166aacbcaa2f969b12c54778cca9172ca (patch)
tree20afa3b8ea86ea69911530b28e7d3d04e7a3f526
parent12b3d2a3b4ddf036b5f2fced0d23b381909ee382 (diff)
parentd7b2189e9e91cad8f2fec9b5d5dd3b85ef6ce6b9 (diff)
downloadjavapoet-21a955d166aacbcaa2f969b12c54778cca9172ca.tar.gz
Merge pull request #152 from square/jwilson_0105_violence
First, rough draft of immutable builders.
-rw-r--r--checkstyle.xml2
-rw-r--r--src/main/java/com/squareup/javawriter/builders/CodeWriter.java168
-rw-r--r--src/main/java/com/squareup/javawriter/builders/FieldSpec.java58
-rw-r--r--src/main/java/com/squareup/javawriter/builders/JavaFile.java70
-rw-r--r--src/main/java/com/squareup/javawriter/builders/MethodSpec.java121
-rw-r--r--src/main/java/com/squareup/javawriter/builders/Name.java36
-rw-r--r--src/main/java/com/squareup/javawriter/builders/ParameterSpec.java61
-rw-r--r--src/main/java/com/squareup/javawriter/builders/Snippet.java73
-rw-r--r--src/main/java/com/squareup/javawriter/builders/TypeSpec.java80
-rw-r--r--src/test/java/com/squareup/javawriter/builders/TypeSpecTest.java58
10 files changed, 726 insertions, 1 deletions
diff --git a/checkstyle.xml b/checkstyle.xml
index 675e2f2..ad56010 100644
--- a/checkstyle.xml
+++ b/checkstyle.xml
@@ -83,7 +83,7 @@
<module name="AvoidNestedBlocks"/>
<!--module name="EmptyBlock"/-->
<module name="LeftCurly"/>
- <module name="NeedBraces"/>
+ <!--<module name="NeedBraces"/>-->
<module name="RightCurly"/>
diff --git a/src/main/java/com/squareup/javawriter/builders/CodeWriter.java b/src/main/java/com/squareup/javawriter/builders/CodeWriter.java
new file mode 100644
index 0000000..005b666
--- /dev/null
+++ b/src/main/java/com/squareup/javawriter/builders/CodeWriter.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.javawriter.builders;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSortedMap;
+import com.squareup.javawriter.ClassName;
+import com.squareup.javawriter.StringLiteral;
+import com.squareup.javawriter.TypeName;
+import com.squareup.javawriter.TypeNames;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Converts a {@link JavaFile} to a string suitable to both human- and javac-consumption. This
+ * honors imports, indentation, and deferred variable names.
+ */
+class CodeWriter {
+ private final String indent = " ";
+ private final StringBuilder out;
+ private final ImmutableMap<ClassName, String> importedTypes;
+ private final LinkedHashSet<TypeName> emittedTypes = new LinkedHashSet<>();
+ private final List<TypeName> visibleTypes = new ArrayList<>();
+ private int indentLevel;
+
+ public CodeWriter(StringBuilder out, ImmutableMap<ClassName, String> importedTypes) {
+ this.out = checkNotNull(out);
+ this.importedTypes = checkNotNull(importedTypes);
+ }
+
+ public CodeWriter indent() {
+ indentLevel++;
+ return this;
+ }
+
+ public CodeWriter unindent() {
+ checkState(indentLevel > 0);
+ indentLevel--;
+ return this;
+ }
+
+ public CodeWriter pushVisibleType(TypeName typeName) {
+ visibleTypes.add(typeName);
+ return this;
+ }
+
+ public CodeWriter popVisibleType(TypeName typeName) {
+ checkState(visibleTypes.remove(typeName));
+ return this;
+ }
+
+ public CodeWriter emit(String format, Object... args) {
+ return emit(new Snippet(format, args));
+ }
+
+ public CodeWriter emit(Snippet snippet) {
+ int a = 0;
+ for (String part : snippet.formatParts) {
+ switch (part) {
+ case "$L":
+ emitAndIndent(String.valueOf(snippet.args.get(a++)));
+ break;
+
+ case "$S":
+ String arg = String.valueOf(snippet.args.get(a++));
+ emitAndIndent(StringLiteral.forValue(arg).literal());
+ break;
+
+ case "$T":
+ emitType(snippet.args.get(a++));
+ break;
+
+ case "$$":
+ emitAndIndent("$");
+ break;
+
+ default:
+ emitAndIndent(part);
+ break;
+ }
+ }
+ return this;
+ }
+
+ private void emitType(Object arg) {
+ TypeName typeName = toTypeName(arg);
+ emittedTypes.add(typeName);
+
+ String shortName = !visibleTypes.contains(typeName)
+ ? importedTypes.get(typeName)
+ : null;
+
+ emitAndIndent(shortName != null ? shortName : typeName.toString());
+ }
+
+ /** Emits {@code s} with indentation as required. */
+ private void emitAndIndent(String s) {
+ boolean first = true;
+ for (String line : s.split("\n", -1)) {
+ if (!first) out.append('\n');
+ first = false;
+ if (line.isEmpty()) continue; // Don't indent empty lines.
+ emitIndentationIfNecessary();
+ out.append(line);
+ }
+ }
+
+ private void emitIndentationIfNecessary() {
+ // Only emit indentation immediately after a '\n' character.
+ if (out.length() <= 0 || out.charAt(out.length() - 1) != '\n') return;
+
+ for (int j = 0; j < indentLevel; j++) {
+ out.append(indent);
+ }
+ }
+
+ private TypeName toTypeName(Object arg) {
+ if (arg instanceof TypeName) return (TypeName) arg;
+ if (arg instanceof Class<?>) return TypeNames.forClass((Class<?>) arg);
+ throw new IllegalArgumentException("Expected type but was " + arg);
+ }
+
+ /**
+ * 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.
+ */
+ ImmutableMap<ClassName, String> suggestedImports() {
+ // Find the simple names that can be imported, and the classes that they target.
+ Map<String, ClassName> simpleNameToType = new LinkedHashMap<>();
+ for (TypeName typeName : emittedTypes) {
+ if (!(typeName instanceof ClassName)) continue;
+ ClassName className = (ClassName) typeName;
+ if (simpleNameToType.containsKey(className.simpleName())) continue;
+ simpleNameToType.put(className.simpleName(), className);
+ }
+
+ // Invert the map.
+ ImmutableSortedMap.Builder<ClassName, String> typeToSimpleName
+ = ImmutableSortedMap.naturalOrder();
+ for (Map.Entry<String, ClassName> entry : simpleNameToType.entrySet()) {
+ typeToSimpleName.put(entry.getValue(), entry.getKey());
+ }
+
+ // TODO(jwilson): omit imports from java.lang, unless their simple names is also present in the
+ // current class's package. (Yuck.)
+
+ return typeToSimpleName.build();
+ }
+}
diff --git a/src/main/java/com/squareup/javawriter/builders/FieldSpec.java b/src/main/java/com/squareup/javawriter/builders/FieldSpec.java
new file mode 100644
index 0000000..20322d8
--- /dev/null
+++ b/src/main/java/com/squareup/javawriter/builders/FieldSpec.java
@@ -0,0 +1,58 @@
+/*
+ * 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.javawriter.builders;
+
+import com.squareup.javawriter.TypeName;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/** A generated field declaration. */
+public final class FieldSpec {
+ public final TypeName type;
+ public final Name name;
+ public final Snippet initializer;
+
+ private FieldSpec(Builder builder) {
+ this.type = checkNotNull(builder.type);
+ this.name = checkNotNull(builder.name);
+ this.initializer = checkNotNull(builder.initializer);
+ }
+
+ public static final class Builder {
+ private TypeName type;
+ private Name name;
+ private Snippet initializer;
+
+ public Builder type(TypeName type) {
+ this.type = type;
+ return this;
+ }
+
+ public Builder name(Name name) {
+ this.name = name;
+ return this;
+ }
+
+ public Builder initializer(String format, Object... args) {
+ this.initializer = new Snippet(format, args);
+ return this;
+ }
+
+ public FieldSpec build() {
+ return new FieldSpec(this);
+ }
+ }
+}
diff --git a/src/main/java/com/squareup/javawriter/builders/JavaFile.java b/src/main/java/com/squareup/javawriter/builders/JavaFile.java
new file mode 100644
index 0000000..59dbdec
--- /dev/null
+++ b/src/main/java/com/squareup/javawriter/builders/JavaFile.java
@@ -0,0 +1,70 @@
+/*
+ * 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.javawriter.builders;
+
+import com.google.common.collect.ImmutableMap;
+import com.squareup.javawriter.ClassName;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/** A Java file containing a single top level class. */
+public final class JavaFile {
+ public final TypeSpec typeSpec;
+
+ private JavaFile(Builder builder) {
+ this.typeSpec = checkNotNull(builder.typeSpec);
+ }
+
+ public String toString() {
+ // First pass: emit the entire class, just to collect the types we'll need to import.
+ ImmutableMap<ClassName, String> noImports = ImmutableMap.of();
+ CodeWriter importsCollector = new CodeWriter(new StringBuilder(), noImports);
+ emit(noImports, importsCollector);
+ ImmutableMap<ClassName, String> suggestedImports = importsCollector.suggestedImports();
+
+ // Second pass: Write the code, taking advantage of the imports.
+ StringBuilder result = new StringBuilder();
+ CodeWriter codeWriter = new CodeWriter(result, suggestedImports);
+ emit(suggestedImports, codeWriter);
+ return result.toString();
+ }
+
+ private void emit(ImmutableMap<ClassName, String> imports, CodeWriter codeWriter) {
+ codeWriter.emit("package $L;\n", typeSpec.name.packageName());
+ codeWriter.emit("\n");
+ for (ClassName className : imports.keySet()) {
+ codeWriter.emit("import $L;\n", className);
+ }
+ codeWriter.emit("\n");
+ typeSpec.emit(codeWriter);
+ }
+
+ public static final class Builder {
+ private TypeSpec typeSpec;
+
+ public Builder classSpec(TypeSpec typeSpec) {
+ checkArgument(!typeSpec.name.enclosingClassName().isPresent(),
+ "Cannot create a JavaFile for %s", typeSpec.name);
+ this.typeSpec = typeSpec;
+ return this;
+ }
+
+ public JavaFile build() {
+ return new JavaFile(this);
+ }
+ }
+}
diff --git a/src/main/java/com/squareup/javawriter/builders/MethodSpec.java b/src/main/java/com/squareup/javawriter/builders/MethodSpec.java
new file mode 100644
index 0000000..4d0a24b
--- /dev/null
+++ b/src/main/java/com/squareup/javawriter/builders/MethodSpec.java
@@ -0,0 +1,121 @@
+/*
+ * 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.javawriter.builders;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javawriter.ClassName;
+import com.squareup.javawriter.TypeName;
+import com.squareup.javawriter.VoidName;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import javax.lang.model.element.Modifier;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/** A generated method declaration. */
+public final class MethodSpec {
+ public final Name name;
+ public final ImmutableList<ClassName> annotations;
+ public final ImmutableList<ParameterSpec> parameters;
+ public final ImmutableSet<Modifier> modifiers;
+ public final TypeName returnType;
+ public final ImmutableList<Snippet> snippets;
+
+ private MethodSpec(Builder builder) {
+ this.name = checkNotNull(builder.name);
+ this.annotations = ImmutableList.copyOf(builder.annotations);
+ this.parameters = ImmutableList.copyOf(builder.parameters);
+ this.modifiers = ImmutableSet.copyOf(builder.modifiers);
+ this.returnType = builder.returnType;
+ this.snippets = ImmutableList.copyOf(builder.snippets);
+ }
+
+ void emit(CodeWriter codeWriter) {
+ codeWriter.emit("$T $L(", returnType, name); // TODO(jwilson): modifiers.
+
+ boolean firstParameter = true;
+ for (ParameterSpec parameterSpec : parameters) {
+ if (!firstParameter) codeWriter.emit(", ");
+ codeWriter.emit("$T $L", parameterSpec.type, parameterSpec.name);
+ firstParameter = false;
+ }
+ codeWriter.emit(") {\n");
+
+ codeWriter.indent();
+ for (Snippet snippet : snippets) {
+ codeWriter.emit(snippet);
+ }
+ codeWriter.unindent();
+
+ codeWriter.emit("}\n");
+ }
+
+ public static final class Builder {
+ private Name name;
+ private List<ClassName> annotations = new ArrayList<>();
+ private List<ParameterSpec> parameters = new ArrayList<>();
+ private List<Modifier> modifiers = new ArrayList<>();
+ private TypeName returnType = VoidName.VOID;
+ private List<Snippet> snippets = new ArrayList<>();
+
+ public Builder name(String name) {
+ this.name = new Name(name);
+ return this;
+ }
+
+ public Builder name(Name name) {
+ this.name = name;
+ return this;
+ }
+
+ public Builder addAnnotation(Class<? extends Annotation> annotation) {
+ this.annotations.add(ClassName.fromClass(annotation));
+ return this;
+ }
+
+ public Builder addParameter(ParameterSpec parameterSpec) {
+ this.parameters.add(parameterSpec);
+ return this;
+ }
+
+ public Builder addParameter(Class<?> type, String name) {
+ this.parameters.add(new ParameterSpec.Builder().type(type).name(name).build());
+ return this;
+ }
+
+ public Builder addModifiers(Modifier... modifiers) {
+ this.modifiers.addAll(Arrays.asList(modifiers));
+ return this;
+ }
+
+ public Builder returns(Class<?> returnType) {
+ this.returnType = ClassName.fromClass(returnType);
+ return this;
+ }
+
+ public Builder addCode(String format, Object... args) {
+ snippets.add(new Snippet(format, args));
+ return this;
+ }
+
+ public MethodSpec build() {
+ return new MethodSpec(this);
+ }
+ }
+}
diff --git a/src/main/java/com/squareup/javawriter/builders/Name.java b/src/main/java/com/squareup/javawriter/builders/Name.java
new file mode 100644
index 0000000..36d1fb2
--- /dev/null
+++ b/src/main/java/com/squareup/javawriter/builders/Name.java
@@ -0,0 +1,36 @@
+/*
+ * 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.javawriter.builders;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A member name. If necessary, the seed name will be mangled to cope with keyword collision and
+ * name collision. For example, given the seed name {@code public}, the generated code may use
+ * {@code public_} or {@code public1}.
+ */
+public final class Name {
+ public final String seed;
+
+ public Name(String seed) {
+ this.seed = checkNotNull(seed);
+ }
+
+ @Override public String toString() {
+ // TODO(jwilson): implement deferred naming so that `new Name("public")` yields "public_" etc.
+ return seed;
+ }
+}
diff --git a/src/main/java/com/squareup/javawriter/builders/ParameterSpec.java b/src/main/java/com/squareup/javawriter/builders/ParameterSpec.java
new file mode 100644
index 0000000..2db9ae7
--- /dev/null
+++ b/src/main/java/com/squareup/javawriter/builders/ParameterSpec.java
@@ -0,0 +1,61 @@
+/*
+ * 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.javawriter.builders;
+
+import com.squareup.javawriter.TypeName;
+import com.squareup.javawriter.TypeNames;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/** A generated parameter declaration. */
+public final class ParameterSpec {
+ public final TypeName type;
+ public final Name name;
+
+ private ParameterSpec(Builder builder) {
+ this.type = checkNotNull(builder.type);
+ this.name = checkNotNull(builder.name);
+ }
+
+ public static final class Builder {
+ private TypeName type;
+ private Name name;
+
+ public Builder type(TypeName type) {
+ this.type = type;
+ return this;
+ }
+
+ public Builder type(Class<?> type) {
+ this.type = TypeNames.forClass(type);
+ return this;
+ }
+
+ public Builder name(String name) {
+ this.name = new Name(name);
+ return this;
+ }
+
+ public Builder name(Name name) {
+ this.name = name;
+ return this;
+ }
+
+ public ParameterSpec build() {
+ return new ParameterSpec(this);
+ }
+ }
+}
diff --git a/src/main/java/com/squareup/javawriter/builders/Snippet.java b/src/main/java/com/squareup/javawriter/builders/Snippet.java
new file mode 100644
index 0000000..39aa695
--- /dev/null
+++ b/src/main/java/com/squareup/javawriter/builders/Snippet.java
@@ -0,0 +1,73 @@
+/*
+ * 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.javawriter.builders;
+
+import com.google.common.collect.ImmutableList;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * A deferred format string. Unlike {@link java.text.Format} which uses percent {@code %} to escape
+ * placeholders, this uses {@code $}, and has its own set of permitted placeholders:
+ *
+ * <ul>
+ * <li>{@code $L} emits the <em>literal</em> value with no escaping.
+ * <li>{@code $S} escapes the value as a <em>string</em>, wraps it with double quotes, and emits
+ * that.
+ * <li>{@code $T} emits a <em>type</em> reference. Types will be imported if possible.
+ * <li>{@code $$} emits a dollar sign.
+ * </ul>
+ */
+final class Snippet {
+ /** A heterogeneous list containing string literals and value placeholders. */
+ final ImmutableList<String> formatParts;
+ final ImmutableList<Object> args;
+
+ public Snippet(String format, Object[] args) {
+ ImmutableList.Builder<String> formatPartsBuilder = ImmutableList.builder();
+ int expectedArgsLength = 0;
+ for (int p = 0, nextP; p < format.length(); p = nextP) {
+ if (format.charAt(p) != '$') {
+ nextP = format.indexOf('$', p + 1);
+ if (nextP == -1) nextP = format.length();
+ } else {
+ checkState(p + 1 < format.length(), "dangling $ in format string %s", format);
+ switch (format.charAt(p + 1)) {
+ case 'L':
+ case 'S':
+ case 'T':
+ expectedArgsLength++;
+ // Fall through.
+ case '$':
+ nextP = p + 2;
+ break;
+
+ default:
+ throw new IllegalArgumentException("invalid format string: " + format);
+ }
+ }
+
+ formatPartsBuilder.add(format.substring(p, nextP));
+ }
+
+ checkArgument(args.length == expectedArgsLength,
+ "expected %s args but was %s", expectedArgsLength, args);
+
+ this.formatParts = formatPartsBuilder.build();
+ this.args = ImmutableList.copyOf(args);
+ }
+}
diff --git a/src/main/java/com/squareup/javawriter/builders/TypeSpec.java b/src/main/java/com/squareup/javawriter/builders/TypeSpec.java
new file mode 100644
index 0000000..7cd0d80
--- /dev/null
+++ b/src/main/java/com/squareup/javawriter/builders/TypeSpec.java
@@ -0,0 +1,80 @@
+/*
+ * 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.javawriter.builders;
+
+import com.google.common.collect.ImmutableList;
+import com.squareup.javawriter.ClassName;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/** A generated class, interface, or enum declaration. */
+public final class TypeSpec {
+ public final Type type;
+ public final ClassName name;
+ public final ImmutableList<MethodSpec> methodSpecs;
+
+ private TypeSpec(Builder builder) {
+ this.type = checkNotNull(builder.type);
+ this.name = checkNotNull(builder.name);
+ this.methodSpecs = ImmutableList.copyOf(builder.methodSpecs);
+ }
+
+ void emit(CodeWriter codeWriter) {
+ codeWriter.emit("class $L {\n", name.simpleName()); // TODO(jwilson): modifiers.
+ codeWriter.indent();
+
+ boolean firstMethod = true;
+ for (MethodSpec methodSpec : methodSpecs) {
+ if (!firstMethod) codeWriter.emit("\n");
+ methodSpec.emit(codeWriter);
+ firstMethod = false;
+ }
+
+ codeWriter.unindent();
+ codeWriter.emit("}\n");
+ }
+
+ public static enum Type {
+ CLASS, INTERFACE, ENUM
+ }
+
+ public static final class Builder {
+ private Type type = Type.CLASS;
+ private ClassName name;
+ private List<MethodSpec> methodSpecs = new ArrayList<>();
+
+ public Builder type(Type type) {
+ this.type = type;
+ return this;
+ }
+
+ public Builder name(ClassName name) {
+ this.name = name;
+ return this;
+ }
+
+ public Builder addMethod(MethodSpec methodSpec) {
+ methodSpecs.add(methodSpec);
+ return this;
+ }
+
+ public TypeSpec build() {
+ return new TypeSpec(this);
+ }
+ }
+}
diff --git a/src/test/java/com/squareup/javawriter/builders/TypeSpecTest.java b/src/test/java/com/squareup/javawriter/builders/TypeSpecTest.java
new file mode 100644
index 0000000..8fd68be
--- /dev/null
+++ b/src/test/java/com/squareup/javawriter/builders/TypeSpecTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.javawriter.builders;
+
+import com.squareup.javawriter.ClassName;
+import javax.lang.model.element.Modifier;
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+
+public class TypeSpecTest {
+ @Test public void test() throws Exception {
+ TypeSpec taco = new TypeSpec.Builder()
+ .name(ClassName.create("com.squareup.tacos", "Taco"))
+ .addMethod(new MethodSpec.Builder()
+ .name("toString")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .returns(String.class)
+ .addCode("return $S;\n", "taco")
+ .build())
+ .build();
+
+ // TODO: fix modifiers
+ // TODO: fix annotations
+
+ assertThat(toString(taco)).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " String toString() {\n"
+ + " return \"taco\";\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ private String toString(TypeSpec typeSpec) {
+ return new JavaFile.Builder()
+ .classSpec(typeSpec)
+ .build()
+ .toString();
+ }
+}