[\\w]).*");
+ private static final Pattern LOWERCASE = Pattern.compile("[a-z]+[\\w_]*");
/**
- * Adds code using named arguments.
- *
- * 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.
- *
- *
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.
+ * A heterogeneous list containing string literals and value placeholders.
*/
- public Builder addNamed(String format, Map 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));
- break;
- }
-
- if (p != nextP) {
- formatParts.add(format.substring(p, nextP));
- p = nextP;
- }
+ final List formatParts;
+ final List args;
- 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;
+ private CodeBlock(Builder builder) {
+ this.formatParts = Util.immutableList(builder.formatParts);
+ this.args = Util.immutableList(builder.args);
}
- /**
- * Add code with positional or relative arguments.
- *
- * Relative arguments map 1:1 with the placeholders in the format string.
- *
- *
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)
- *
- *
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++;
- }
+ public boolean isEmpty() {
+ return formatParts.isEmpty();
+ }
- 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 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;
+ @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());
}
- private boolean isNoArgPlaceholder(char c) {
- return c == '$' || c == '>' || c == '<' || c == '[' || c == ']' || c == 'W' || c == 'Z';
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
}
- 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));
- }
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder();
+ try {
+ new CodeWriter(out).emit(this);
+ return out.toString();
+ } catch (IOException e) {
+ throw new AssertionError();
+ }
}
- 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);
+ public static CodeBlock of(String format, Object... args)
+ {
+ return new Builder().add(format, args).build();
}
- private Object argToLiteral(Object o) {
- return o;
+ public static CodeBlock of(CodeBlock codeBlock)
+ {
+ return new Builder().add(codeBlock).build();
}
- private String argToString(Object o) {
- return o != null ? String.valueOf(o) : null;
+ public static CodeBlock ofStatement(String format, Object... args)
+ {
+ return new Builder().addStatement(format, args).build();
}
- 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);
+ public static CodeBlock ofStatement(CodeBlock codeBlock)
+ {
+ return new Builder().addStatement(codeBlock).build();
}
+
/**
- * @param controlFlow the control flow construct and its code, such as "if (foo == 5)".
- * Shouldn't contain braces or newline characters.
+ * 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 Builder beginControlFlow(String controlFlow, Object... args) {
- add(controlFlow + " {\n", args);
- indent();
- return this;
+ public static CodeBlock join(Iterable codeBlocks, String separator) {
+ return StreamSupport.stream(codeBlocks.spliterator(), false).collect(joining(separator));
}
/**
- * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
- * Shouldn't contain braces or newline characters.
+ * 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 Builder nextControlFlow(String controlFlow, Object... args) {
- unindent();
- add("} " + controlFlow + " {\n", args);
- indent();
- return this;
- }
-
- public Builder endControlFlow() {
- unindent();
- add("}\n");
- return this;
+ public static Collector joining(String separator) {
+ return Collector.of(
+ () -> new CodeBlockJoiner(separator, builder()),
+ CodeBlockJoiner::add,
+ CodeBlockJoiner::merge,
+ CodeBlockJoiner::join);
}
/**
- * @param controlFlow the optional control flow construct and its code, such as
- * "while(foo == 20)". Only used for "do/while" control flows.
+ * 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 Builder endControlFlow(String controlFlow, Object... args) {
- unindent();
- add("} " + controlFlow + ";\n", args);
- return this;
+ public static Collector 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 Builder addStatement(String format, Object... args) {
- add("$[");
- add(format, args);
- add(";\n$]");
- return this;
+ public static Builder builder() {
+ return new Builder();
}
- public Builder addStatement(CodeBlock codeBlock) {
- return addStatement("$L", codeBlock);
+ public Builder toBuilder() {
+ Builder builder = new Builder();
+ builder.formatParts.addAll(formatParts);
+ builder.args.addAll(args);
+ return builder;
}
- public Builder add(CodeBlock codeBlock) {
- formatParts.addAll(codeBlock.formatParts);
- args.addAll(codeBlock.args);
- return this;
- }
+ public static final class Builder {
+ final List formatParts = new ArrayList<>();
+ final List args = new ArrayList<>();
- public Builder indent() {
- this.formatParts.add("$>");
- return this;
- }
+ private Builder() {
+ }
- public Builder unindent() {
- this.formatParts.add("$<");
- return this;
- }
+ public boolean isEmpty() {
+ return formatParts.isEmpty();
+ }
- public Builder clear() {
- formatParts.clear();
- args.clear();
- return this;
- }
+ /**
+ * Adds code using named arguments.
+ *
+ * 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.
+ *
+ *
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 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));
+ 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;
+ }
- public CodeBlock build() {
- return new CodeBlock(this);
- }
- }
+ /**
+ * Add code with positional or relative arguments.
+ *
+ * Relative arguments map 1:1 with the placeholders in the format string.
+ *
+ *
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)
+ *
+ *
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 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 static final class CodeBlockJoiner {
- private final String delimiter;
- private final Builder builder;
- private boolean first = true;
+ private boolean isNoArgPlaceholder(char c) {
+ return c == '$' || c == '>' || c == '<' || c == '[' || c == ']' || c == 'W' || c == 'Z';
+ }
- CodeBlockJoiner(String delimiter, Builder builder) {
- this.delimiter = delimiter;
- this.builder = builder;
- }
+ 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));
+ }
+ }
- CodeBlockJoiner add(CodeBlock codeBlock) {
- if (!first) {
- builder.add(delimiter);
- }
- first = false;
+ 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);
+ }
- builder.add(codeBlock);
- return this;
- }
+ 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);
+ }
- CodeBlockJoiner merge(CodeBlockJoiner other) {
- CodeBlock otherBlock = other.builder.build();
- if (!otherBlock.isEmpty()) {
- add(otherBlock);
- }
- return this;
+ 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 Builder clear() {
+ formatParts.clear();
+ args.clear();
+ return this;
+ }
+
+ public CodeBlock build() {
+ return new CodeBlock(this);
+ }
}
- CodeBlock join() {
- return builder.build();
+ 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/test/java/com/squareup/javapoet/AnnotationSpecTest.java b/src/test/java/com/squareup/javapoet/AnnotationSpecTest.java
index 97c1e6e90..653ba2b2b 100644
--- a/src/test/java/com/squareup/javapoet/AnnotationSpecTest.java
+++ b/src/test/java/com/squareup/javapoet/AnnotationSpecTest.java
@@ -383,6 +383,85 @@ public class IsAnnotated {
assertThat(builder.build().toString()).isEqualTo("@java.lang.SuppressWarnings(\"Bar\")");
}
+ @Test public void addMembersWithAnnotationSpec(){
+ String packageName = "package com.squareup.computerScience;";
+ ClassName java = ClassName.get(packageName, "Java");
+ ClassName language = ClassName.get(packageName, "Language");
+ ClassName computerScience = ClassName.get(packageName, "ComputerScience");
+
+ AnnotationSpec javaAnnotation = AnnotationSpec.builder(language)
+ .addMember("name", "$S", "function")
+ .addMember("language", "$T.class", java)
+ .build();
+
+ AnnotationSpec.Builder builder = AnnotationSpec.builder(computerScience);
+ AnnotationSpec computerScienceAnnotation = AnnotationSpec.addMembers(builder, "ComputerScience", "$L"
+ , javaAnnotation).build();
+ assertThat(computerScienceAnnotation.toString()).isEqualTo(
+ "@package com.squareup.computerScience;" +
+ ".ComputerScience" +
+ "(" +
+ "ComputerScience = @package com.squareup.computerScience;" +
+ ".Language" +
+ "(" +
+ "name = \"function\", language = package com.squareup.computerScience;" +
+ ".Java.class" +
+ ")" +
+ ")");
+ }
+
+
+ @Test public void addMembersWithMoreAnnotationSpec(){
+ String packageName = "package com.squareup.computerScience;";
+ ClassName pytorch = ClassName.get(packageName, "Pytorch");
+ ClassName tensorflow = ClassName.get(packageName, "Tensorflow");
+ ClassName mxnet = ClassName.get(packageName, "MXNet");
+ ClassName dlFramework = ClassName.get(packageName, "DeepLearningFramework");
+ ClassName python = ClassName.get(packageName, "Python");
+
+ AnnotationSpec pytorchAnnotation = AnnotationSpec.builder(dlFramework)
+ .addMember("name", "$S", "function")
+ .addMember("DeepLearningFramework", "$T.class", pytorch)
+ .build();
+
+ AnnotationSpec tfAnnotation = AnnotationSpec.builder(dlFramework)
+ .addMember("name", "$S", "function")
+ .addMember("DeepLearningFramework", "$T.class", tensorflow)
+ .build();
+
+ AnnotationSpec mxnetAnnotation = AnnotationSpec.builder(dlFramework)
+ .addMember("name", "$S", "function")
+ .addMember("DeepLearningFramework", "$T.class", mxnet)
+ .build();
+
+ AnnotationSpec.Builder builder = AnnotationSpec.builder(python);
+ AnnotationSpec pythonAnnotation = AnnotationSpec.addMembers(builder, "DeepLearningFramework", "$L"
+ , pytorchAnnotation, tfAnnotation, mxnetAnnotation).build();
+ assertThat(pythonAnnotation.toString()).isEqualTo("" +
+ "@package com.squareup.computerScience;" +
+ ".Python" +
+ "(" +
+ "DeepLearningFramework = " +
+ "{" +
+ "@package com.squareup.computerScience;" +
+ ".DeepLearningFramework" +
+ "(" +
+ "name = \"function\", DeepLearningFramework = package com.squareup.computerScience;.Pytorch.class" +
+ "), " +
+ "@package com.squareup.computerScience;" +
+ ".DeepLearningFramework" +
+ "(" +
+ "name = \"function\", DeepLearningFramework = package com.squareup.computerScience;.Tensorflow.class" +
+ "), " +
+ "@package com.squareup.computerScience;" +
+ ".DeepLearningFramework" +
+ "(" +
+ "name = \"function\", DeepLearningFramework = package com.squareup.computerScience;.MXNet.class" +
+ ")" +
+ "}" +
+ ")\n");
+ }
+
private String toString(TypeSpec typeSpec) {
return JavaFile.builder("com.squareup.tacos", typeSpec).build().toString();
}
diff --git a/src/test/java/com/squareup/javapoet/CodeBlockTest.java b/src/test/java/com/squareup/javapoet/CodeBlockTest.java
index 11b75fa4f..fce24a47b 100644
--- a/src/test/java/com/squareup/javapoet/CodeBlockTest.java
+++ b/src/test/java/com/squareup/javapoet/CodeBlockTest.java
@@ -19,6 +19,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+
import org.junit.Test;
import static com.google.common.truth.Truth.assertThat;
@@ -27,325 +28,373 @@
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 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 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 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 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 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 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 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 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 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 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 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 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");
- }
-
- @Test public void clear() {
- CodeBlock block = CodeBlock.builder()
- .addStatement("$S", "Test string")
- .clear()
- .build();
-
- assertThat(block.toString()).isEmpty();
- }
+ @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");
+ CodeBlock b = CodeBlock.of(a);
+ assertThat(b.toString()).isEqualTo("delicious taco");
+ }
+
+ @Test
+ public void ofStatement() {
+ CodeBlock a = CodeBlock.of("$L taco", "delicious");
+ assertThat(a.toString()).isEqualTo("delicious taco");
+ CodeBlock b = CodeBlock.ofStatement("$L taco", "delicious");
+ assertThat(b.toString()).isEqualTo("delicious taco;\n");
+ CodeBlock c = CodeBlock.ofStatement(a);
+ assertThat(c.toString()).isEqualTo("delicious taco;\n");
+ }
+
+ @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 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 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 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 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 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 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 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 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 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 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 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 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");
+ }
+
+ @Test
+ public void clear() {
+ CodeBlock block = CodeBlock.builder()
+ .addStatement("$S", "Test string")
+ .clear()
+ .build();
+
+ assertThat(block.toString()).isEmpty();
+ }
}