diff --git a/src/main/java/com/squareup/javapoet/CodeBlock.java b/src/main/java/com/squareup/javapoet/CodeBlock.java
index 5376984..ebedbd6 100644
--- a/src/main/java/com/squareup/javapoet/CodeBlock.java
+++ b/src/main/java/com/squareup/javapoet/CodeBlock.java
@@ -21,11 +21,14 @@
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
+import java.util.Collections;
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.Collectors;
+import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static com.squareup.javapoet.Util.checkArgument;
@@ -415,6 +418,156 @@ public Builder add(CodeBlock codeBlock) {
return this;
}
+ /**
+ * Structures a lambda function based on some inputs and a CodeBlock body.
+ * Should be used with {@link #add(String, Object...) addCode},
+ * to provide specific behaviour such as
+ * methodcall((int x, int y) -> x + y, 5).
+ * @param parameters the input parameters of the function.
+ * @param mode the format mode that should be used.
+ * @see com.squareup.javapoet.LambdaMode LambdaMode.
+ * @param body the body of the function.
+ */
+ public Builder addLambda(Iterable parameters, LambdaMode mode, CodeBlock body) {
+ // check for specification of input type visibility
+ boolean emitTypes = mode.equals(LambdaMode.VISIBLE_TYPES);
+
+ Stream parameterStream = StreamSupport.stream(
+ parameters.spliterator(),
+ false
+ );
+
+ // the inputs of the lambda (left side)
+ String inputSide = parameterStream
+ .peek(p -> checkArgument(!p.type.equals(TypeName.VOID), // validate the input types
+ "lambda input parameters cannot be of void type")
+ )
+ .map(p -> emitTypes ? p.toString() : p.name)
+ .collect(Collectors.joining(", "));
+
+ // the count of given inputs
+ int paramsLen = inputSide.split(",").length;
+
+ // on 0 or more than 1 inputs, or in case of type emission,
+ // parentheses are mandatory
+ if (paramsLen > 1 || emitTypes || inputSide.isEmpty()) {
+ inputSide = "(" + inputSide + ")";
+ }
+
+ // the body of the lambda (right side)
+ String bodySide = body.toString().replaceAll("\n", "");
+
+ // in case of multiple statements, braces are mandatory
+ if (bodySide.contains(";")) {
+ bodySide = "{" + bodySide + "}";
+ }
+
+ // the full lambda structure
+ add(inputSide + " -> " + bodySide);
+ return this;
+ }
+
+ /**
+ * Structures a lambda function based on some inputs and a CodeBlock body.
+ * Will not emit the input types.
+ * Should be used with {@link #add(String, Object...) addCode},
+ * to provide specific behaviour such as
+ * methodcall((x, y) -> x + y, 5).
+ * @param parameters the input parameters of the function.
+ * @param body the body of the function.
+ */
+ public Builder addLambda(Iterable parameters, CodeBlock body) {
+ return addLambda(parameters, LambdaMode.DEFAULT, body);
+ }
+
+ /**
+ * Structures a lambda function based on some inputs and an expression body.
+ * Should be used with {@link #add(String, Object...) addCode},
+ * to provide specific behaviour such as
+ * methodcall((int x, int y) -> x + y, 5).
+ * @param parameters the input parameters of the function.
+ * @param mode the format mode that should be used.
+ * @see com.squareup.javapoet.LambdaMode LambdaMode.
+ * @param expressionFormat the format that should be used
+ * for the expression.
+ * @param args the values that should be placed in the holders
+ * of the format.
+ */
+ public Builder addLambda(Iterable parameters, LambdaMode mode,
+ String expressionFormat, Object... args) {
+ return addLambda(parameters, mode, CodeBlock.of(expressionFormat, args));
+ }
+
+ /**
+ * Structures a lambda function based on some inputs and an expression body.
+ * Will not emit the input types.
+ * Should be used with {@link #add(String, Object...) addCode},
+ * to provide specific behaviour such as
+ * methodcall((x, y) -> x + y, 5).
+ * @param parameters the input parameters of the function.
+ * @param expressionFormat the format that should be used
+ * for the expression.
+ * @param args the values that should be placed in the holders
+ * of the format.
+ */
+ public Builder addLambda(Iterable parameters, String expressionFormat, Object... args) {
+ return addLambda(parameters, LambdaMode.DEFAULT, CodeBlock.of(expressionFormat, args));
+ }
+
+ /**
+ * Structures a producer lambda function based on a CodeBlock body.
+ * Should be used with {@link #addCode(String, Object...) addCode},
+ * to provide specific behaviour such as
+ * methodcall(() -> 3 + 2, 5).
+ * @param mode the format mode that should be used.
+ * @see com.squareup.javapoet.LambdaMode LambdaMode.
+ * @param body the body of the lambda.
+ */
+ public Builder addLambda(LambdaMode mode, CodeBlock body) {
+ return addLambda(Collections.emptyList(), mode, body);
+ }
+
+ /**
+ * Structures a producer lambda function based on a CodeBlock body.
+ * Should be used with {@link #add(String, Object...) addCode},
+ * to provide specific behaviour such as
+ * methodcall(() -> 3 + 2, 5).
+ * @param body the body of the lambda.
+ */
+ public Builder addLambda(CodeBlock body) {
+ return addLambda(Collections.emptyList(), LambdaMode.DEFAULT, body);
+ }
+
+ /**
+ * Structures a producer lambda function based on an expression body.
+ * Should be used with {@link #addCode(String, Object...) addCode},
+ * to provide specific behaviour such as
+ * methodcall(() -> 3 + 2, 5).
+ * @param mode the format mode that should be used.
+ * @see com.squareup.javapoet.LambdaMode LambdaMode.
+ * @param expressionFormat the format that should be used
+ * for the expression.
+ * @param args the values that should be placed in the holders
+ * of the format.
+ */
+ public Builder addLambda(LambdaMode mode, String expressionFormat, Object... args) {
+ return addLambda(Collections.emptyList(), mode, expressionFormat, args);
+ }
+
+ /**
+ * Structures a producer lambda function based on an expression body.
+ * Should be used with {@link #add(String, Object...) addCode},
+ * to provide specific behaviour such as
+ * methodcall(() -> 3 + 2, 5).
+ * @param expressionFormat the format that should be used
+ * for the expression.
+ * @param args the values that should be placed in the holders
+ * of the format.
+ */
+ public Builder addLambda(String expressionFormat, Object... args) {
+ return addLambda(Collections.emptyList(), LambdaMode.DEFAULT, expressionFormat, args);
+ }
+
public Builder indent() {
this.formatParts.add("$>");
return this;
diff --git a/src/main/java/com/squareup/javapoet/LambdaMode.java b/src/main/java/com/squareup/javapoet/LambdaMode.java
new file mode 100644
index 0000000..503101b
--- /dev/null
+++ b/src/main/java/com/squareup/javapoet/LambdaMode.java
@@ -0,0 +1,11 @@
+package com.squareup.javapoet;
+
+/**
+ * The available format modes a lambda function can have.
+ * {@code DEFAULT} example: (x, y) -> x + y;
+ * {@code VISIBLE_TYPES} example: (int x, int y) -> x + y;
+*/
+public enum LambdaMode {
+ DEFAULT,
+ VISIBLE_TYPES
+}
diff --git a/src/main/java/com/squareup/javapoet/MethodSpec.java b/src/main/java/com/squareup/javapoet/MethodSpec.java
index 6914858..e258326 100644
--- a/src/main/java/com/squareup/javapoet/MethodSpec.java
+++ b/src/main/java/com/squareup/javapoet/MethodSpec.java
@@ -296,21 +296,25 @@ public static final class Builder {
private String name;
private final CodeBlock.Builder javadoc = CodeBlock.builder();
+ public final List annotations = new ArrayList<>();
+ public final List modifiers = new ArrayList<>();
+ public final List typeVariables = new ArrayList<>();
private TypeName returnType;
+ public final List parameters = new ArrayList<>();
+ private boolean varargs;
private final Set exceptions = new LinkedHashSet<>();
private final CodeBlock.Builder code = CodeBlock.builder();
- private boolean varargs;
private CodeBlock defaultValue;
- public final List typeVariables = new ArrayList<>();
- public final List annotations = new ArrayList<>();
- public final List modifiers = new ArrayList<>();
- public final List parameters = new ArrayList<>();
private Builder(String name) {
setName(name);
}
+ /**
+ * Sets a name for this builder.
+ * @param name the name to be set for the method.
+ */
public Builder setName(String name) {
checkNotNull(name, "name == null");
checkArgument(name.equals(CONSTRUCTOR) || SourceVersion.isName(name),
@@ -500,6 +504,10 @@ public Builder nextControlFlow(CodeBlock codeBlock) {
return nextControlFlow("$L", codeBlock);
}
+ /**
+ * Ends the last open control flow.
+ * Should be used once for every control flow.
+ */
public Builder endControlFlow() {
code.endControlFlow();
return this;
@@ -532,6 +540,130 @@ public Builder addStatement(CodeBlock codeBlock) {
return this;
}
+ /**
+ * Structures a lambda function based on some inputs and a CodeBlock body.
+ * Should be used with {@link #addCode(String, Object...) addCode},
+ * to provide specific behaviour such as
+ * methodcall((int x, int y) -> x + y, 5).
+ * @param parameters the input parameters of the function.
+ * @param mode the format mode that should be used.
+ * @see com.squareup.javapoet.LambdaMode LambdaMode.
+ * be emitted on the result.
+ * @param body the body of the function.
+ */
+ public Builder addLambda(Iterable parameters, LambdaMode mode, CodeBlock body) {
+ code.addLambda(parameters, mode, body);
+ return this;
+ }
+
+ /**
+ * Structures a lambda function based on some inputs and a CodeBlock body.
+ * Will not emit the input types.
+ * Should be used with {@link #addCode(String, Object...) addCode},
+ * to provide specific behaviour such as
+ * methodcall((x, y) -> x + y, 5).
+ * @param parameters the input parameters of the function.
+ * @param body the body of the function.
+ */
+ public Builder addLambda(Iterable parameters, CodeBlock body) {
+ code.addLambda(parameters, LambdaMode.DEFAULT, body);
+ return this;
+ }
+
+ /**
+ * Structures a lambda function based on some inputs and an expression body.
+ * Should be used with {@link #addCode(String, Object...) addCode},
+ * to provide specific behaviour such as
+ * methodcall((int x, int y) -> x + y, 5).
+ * @param parameters the input parameters of the function.
+ * @param mode the format mode that should be used.
+ * @see com.squareup.javapoet.LambdaMode LambdaMode.
+ * @param expressionFormat the format that should be used
+ * for the expression.
+ * @param args the values that should be placed in the holders
+ * of the format.
+ */
+ public Builder addLambda(Iterable parameters, LambdaMode mode,
+ String expressionFormat, Object... args) {
+ code.addLambda(parameters, mode, CodeBlock.of(expressionFormat, args));
+ return this;
+ }
+
+ /**
+ * Structures a lambda function based on some inputs and an expression body.
+ * Will not emit the input types.
+ * Should be used with {@link #addCode(String, Object...) addCode},
+ * to provide specific behaviour such as
+ * methodcall((x, y) -> x + y, 5).
+ * @param parameters the input parameters of the function.
+ * @param expressionFormat the format that should be used
+ * for the expression.
+ * @param args the values that should be placed in the holders
+ * of the format.
+ */
+ public Builder addLambda(Iterable parameters, String expressionFormat, Object... args) {
+ code.addLambda(parameters, LambdaMode.DEFAULT, CodeBlock.of(expressionFormat, args));
+ return this;
+ }
+
+ /**
+ * Structures a producer lambda function based on a CodeBlock body.
+ * Should be used with {@link #addCode(String, Object...) addCode},
+ * to provide specific behaviour such as
+ * methodcall(() -> 3 + 2, 5).
+ * @param mode the format mode that should be used.
+ * @see com.squareup.javapoet.LambdaMode LambdaMode.
+ * @param body the body of the lambda.
+ */
+ public Builder addLambda(LambdaMode mode, CodeBlock body) {
+ code.addLambda(Collections.emptyList(), mode, body);
+ return this;
+ }
+
+ /**
+ * Structures a producer lambda function based on a CodeBlock body.
+ * Should be used with {@link #addCode(String, Object...) addCode},
+ * to provide specific behaviour such as
+ * methodcall(() -> 3 + 2, 5).
+ * @param body the body of the lambda.
+ */
+ public Builder addLambda(CodeBlock body) {
+ code.addLambda(Collections.emptyList(), LambdaMode.DEFAULT, body);
+ return this;
+ }
+
+ /**
+ * Structures a producer lambda function based on an expression body.
+ * Should be used with {@link #addCode(String, Object...) addCode},
+ * to provide specific behaviour such as
+ * methodcall(() -> 3 + 2, 5).
+ * @param mode the format mode that should be used.
+ * @see com.squareup.javapoet.LambdaMode LambdaMode.
+ * @param expressionFormat the format that should be used
+ * for the expression.
+ * @param args the values that should be placed in the holders
+ * of the format.
+ */
+ public Builder addLambda(LambdaMode mode, String expressionFormat, Object... args) {
+ code.addLambda(Collections.emptyList(), mode, expressionFormat, args);
+ return this;
+ }
+
+ /**
+ * Structures a producer lambda function based on an expression body.
+ * Should be used with {@link #addCode(String, Object...) addCode},
+ * to provide specific behaviour such as
+ * methodcall(() -> 3 + 2, 5).
+ * @param expressionFormat the format that should be used
+ * for the expression.
+ * @param args the values that should be placed in the holders
+ * of the format.
+ */
+ public Builder addLambda(String expressionFormat, Object... args) {
+ code.addLambda(Collections.emptyList(), LambdaMode.DEFAULT, expressionFormat, args);
+ return this;
+ }
+
public MethodSpec build() {
return new MethodSpec(this);
}
diff --git a/src/test/java/com/squareup/javapoet/MethodSpecTest.java b/src/test/java/com/squareup/javapoet/MethodSpecTest.java
index 56cc3b7..50eb992 100644
--- a/src/test/java/com/squareup/javapoet/MethodSpecTest.java
+++ b/src/test/java/com/squareup/javapoet/MethodSpecTest.java
@@ -482,8 +482,123 @@ abstract static class AbstractClassWithPrivateAnnotation {
"}\n");
}
- private static CodeBlock named(String format, Map args){
+ private static CodeBlock named(String format, Map args) {
return CodeBlock.builder().addNamed(format, args).build();
}
+ @Test public void ensureLambdaTypeError() {
+ // parameter with void type - should be rejected
+ ParameterSpec p1 = ParameterSpec.builder(TypeName.VOID, "x").build();
+
+ try {
+ // check that void type will cause errors
+ CodeBlock.builder()
+ .addLambda(List.of(p1), "$N * 2", "x");
+
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().isEqualTo(
+ "lambda input parameters cannot be of void type"
+ );
+ }
+ }
+
+ @Test public void ensureLambdaMethodSpecLiability() {
+ // lambda inputs
+ ParameterSpec p1 = ParameterSpec.builder(TypeName.INT, "x").build();
+ ParameterSpec p2 = ParameterSpec.builder(TypeName.DOUBLE, "y").build();
+
+ // lambda body that considers input values
+ CodeBlock body1 = CodeBlock.of("int $3N = 3; return $1N + $2N + $3N;", "x", "y", "z");
+
+ // lambda body that does not consider input values
+ CodeBlock body2 = CodeBlock.of("int $1N = 3; int $2N = 5; return $1N + $2N;", "x", "y");
+
+ MethodSpec method = MethodSpec.methodBuilder("method")
+ .addCode("methodCall(")
+ .addLambda(List.of(p1, p2), LambdaMode.VISIBLE_TYPES, body1) // lambda with multiple inputs and (CodeBlock) body
+ .addCode(", ")
+ .addLambda(List.of(p2),
+ "$N + $N", "x", "y") // lambda with single input and (String) body
+ .addCode(", ")
+ .addLambda(body2) // lambda with no inputs and (CodeBlock) body
+ .addCode(", ")
+ .addLambda("5 + 7") // lambda with no inputs and (String) body
+ .addCode(", ")
+ .addLambda("method1(); method2();") // lambda with multiple statements
+ .addCode(", ")
+ .addLambda(List.of(p1), LambdaMode.VISIBLE_TYPES, "x + 5") // lambda with single input of emitted type
+ .addCode(");\n")
+ .build();
+
+ assertThat(method.toString()).isEqualTo(
+ "void method() {\n" +
+ " methodCall(" +
+ "(int x, double y) -> {int z = 3; return x + y + z;}, " +
+ "y -> x + y, " +
+ "() -> {int x = 3; int y = 5; return x + y;}, " +
+ "() -> 5 + 7, " +
+ "() -> {method1(); method2();}, " +
+ "(int x) -> x + 5" +
+ ");\n" +
+ "}\n"
+ );
+ }
+
+ @Test public void ensureLambdaCodeBlockLiability() {
+ // lambda body that does not consider input values
+ CodeBlock body = CodeBlock.of("int $1N = 3; int $2N = 5; return $1N + $2N;", "x", "y");
+
+ // lambda expression that does not consider input values
+ CodeBlock body2 = CodeBlock.of("5 + 3");
+
+ CodeBlock codeWithLambda = CodeBlock.builder()
+ .add("methodCall(")
+ .addLambda(body) // producer lambda
+ .add(", ")
+ .addLambda(body2)
+ .add(");")
+ .build();
+
+ MethodSpec method = MethodSpec.methodBuilder("method")
+ .addCode(codeWithLambda)
+ .build();
+
+ assertThat(method.toString()).isEqualTo(
+ "void method() {\n" +
+ " methodCall(" +
+ "() -> {int x = 3; int y = 5; return x + y;}, " +
+ "() -> 5 + 3" +
+ ");\n" +
+ "}\n"
+ );
+ }
+
+ @Test public void ensureLambdaModeLiability() {
+ // lambda body that does not consider input values
+ CodeBlock body = CodeBlock.of("int $1N = 3; int $2N = 5; return $1N + $2N;", "x", "y");
+
+ CodeBlock codeWithLambda = CodeBlock.builder()
+ .add("methodCall(")
+ .addLambda(LambdaMode.VISIBLE_TYPES, "5 + 3") // ensure that redundant mode specification doesnt break anything
+ .add(");\n")
+ .build();
+
+ MethodSpec method = MethodSpec.methodBuilder("method")
+ .addCode(codeWithLambda)
+ .addCode("Producer x = ")
+ .addLambda(LambdaMode.VISIBLE_TYPES, body) // ensure that redundant mode specification doesnt break anything
+ .addCode(";")
+ .build();
+
+ assertThat(method.toString()).isEqualTo(
+ "void method() {\n" +
+ " methodCall(" +
+ "() -> 5 + 3" +
+ ");\n" +
+ " Producer x = () -> {int x = 3; int y = 5; return x + y;};\n" +
+ "}\n"
+ );
+ }
+
}