Skip to content
This repository was archived by the owner on Aug 11, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions src/main/java/com/squareup/javapoet/CodeBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -415,6 +418,156 @@ public Builder add(CodeBlock codeBlock) {
return this;
}

/**
* Structures a lambda function based on some inputs and a CodeBlock body.<br>
* Should be used with {@link #add(String, Object...) addCode},
* to provide specific behaviour such as
* <b>methodcall((int x, int y) -> x + y, 5)</b>.
* @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<ParameterSpec> parameters, LambdaMode mode, CodeBlock body) {
// check for specification of input type visibility
boolean emitTypes = mode.equals(LambdaMode.VISIBLE_TYPES);

Stream<ParameterSpec> 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", "");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems wrong. Why are we doing this? We should not reformat code that has already been specified by users.

Copy link
Contributor Author

@HliasMpGH HliasMpGH May 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wdyt about having a new line after the { so for body inputs like:

int x = 6;
int y = 7;
return x + y;

we get outputs like:

input -> {
int x = 6;
int y = 7;
return x + y;
}

(or with proper indents if you think thats better).
By leaving the formatted user input as is, without removing new lines, we might fall into outputs like:

input -> {int x =6;
int y = 7;
return x + y;
}

Copy link
Owner

@Randgalt Randgalt Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The braces placement should be customizable. No one will agree on where to put it. This is why I've been suggesting we have a LambdaSpec class. I don't see any way around it. If we had a LambdaSpec builder class we wouldn't need so many method overloads. This would also pay benefits for the switch PR. the body after the -> arm of a switch is just like a lambda and a builder would help there.

I'd like to see all these method overloads moved into a builder class, possibly with a bas class that can re-used for the switch PR. The placement of the lambda body braces should be configurable just as it is in ClodeBlock


// in case of multiple statements, braces are mandatory
if (bodySide.contains(";")) {
bodySide = "{" + bodySide + "}";
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is forcing a coding style. Users need to be able to specify newlines, indents, etc. Many people will not like the output of this.

Copy link
Contributor Author

@HliasMpGH HliasMpGH May 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The emitted braces are for lambdas that contain a full method body such as x -> {int y=5; return x + 5;} in contrast with the more common lambdas x -> x + 5 that dont have mutlitple statements. I dont exaclty understand what you mean about forcing a code style, since that braces in the first example are mandatory to producing valid java syntax. Are you saying that the user should be responsible for placing the braces in its code blocks if he wishes to have mutliple statements, so just remove lines 460-462? About the new lines and indents, since the body is a CodeBlock, the user can definitely place indents and new lines in the body if he wishes to, when creating the CodeBlock. Some further insights are very welcome!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indenting, newlines, etc. must all be configurable by users.

}

// the full lambda structure
add(inputSide + " -> " + bodySide);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming spaces around -> is wrong. Like all things, we must allow users to specify how code is formatted. FYI - this is why I suggested a separate LambdaBlock class. There are simply too many options to force into method arguments.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea was based on the rest of the codebase that seems to follow that kind of pattern. We have methods such as beginControlFlow() for example that does indeed emitt spaces before { to produce the output and doesnt allow user configuration. I take that this is coded that way because spaces before { on if statements and for loops, are a standard for java code.

return this;
}

/**
* Structures a lambda function based on some inputs and a CodeBlock body.<br>
* Will not emit the input types.<br>
* Should be used with {@link #add(String, Object...) addCode},
* to provide specific behaviour such as
* <b>methodcall((x, y) -> x + y, 5)</b>.
* @param parameters the input parameters of the function.
* @param body the body of the function.
*/
public Builder addLambda(Iterable<ParameterSpec> parameters, CodeBlock body) {
return addLambda(parameters, LambdaMode.DEFAULT, body);
}

/**
* Structures a lambda function based on some inputs and an expression body.<br>
* Should be used with {@link #add(String, Object...) addCode},
* to provide specific behaviour such as
* <b>methodcall((int x, int y) -> x + y, 5)</b>.
* @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<ParameterSpec> 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.<br>
* Will not emit the input types.<br>
* Should be used with {@link #add(String, Object...) addCode},
* to provide specific behaviour such as
* <b>methodcall((x, y) -> x + y, 5)</b>.
* @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<ParameterSpec> parameters, String expressionFormat, Object... args) {
return addLambda(parameters, LambdaMode.DEFAULT, CodeBlock.of(expressionFormat, args));
}

/**
* Structures a producer lambda function based on a CodeBlock body.<br>
* Should be used with {@link #addCode(String, Object...) addCode},
* to provide specific behaviour such as
* <b>methodcall(() -> 3 + 2, 5)</b>.
* @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.<br>
* Should be used with {@link #add(String, Object...) addCode},
* to provide specific behaviour such as
* <b>methodcall(() -> 3 + 2, 5)</b>.
* @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.<br>
* Should be used with {@link #addCode(String, Object...) addCode},
* to provide specific behaviour such as
* <b>methodcall(() -> 3 + 2, 5)</b>.
* @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.<br>
* Should be used with {@link #add(String, Object...) addCode},
* to provide specific behaviour such as
* <b>methodcall(() -> 3 + 2, 5)</b>.
* @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;
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/squareup/javapoet/LambdaMode.java
Original file line number Diff line number Diff line change
@@ -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
}
142 changes: 137 additions & 5 deletions src/main/java/com/squareup/javapoet/MethodSpec.java
Original file line number Diff line number Diff line change
Expand Up @@ -296,21 +296,25 @@ public static final class Builder {
private String name;

private final CodeBlock.Builder javadoc = CodeBlock.builder();
public final List<AnnotationSpec> annotations = new ArrayList<>();
public final List<Modifier> modifiers = new ArrayList<>();
public final List<TypeVariableName> typeVariables = new ArrayList<>();
private TypeName returnType;
public final List<ParameterSpec> parameters = new ArrayList<>();
private boolean varargs;
private final Set<TypeName> exceptions = new LinkedHashSet<>();
private final CodeBlock.Builder code = CodeBlock.builder();
private boolean varargs;
private CodeBlock defaultValue;

public final List<TypeVariableName> typeVariables = new ArrayList<>();
public final List<AnnotationSpec> annotations = new ArrayList<>();
public final List<Modifier> modifiers = new ArrayList<>();
public final List<ParameterSpec> 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),
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -532,6 +540,130 @@ public Builder addStatement(CodeBlock codeBlock) {
return this;
}

/**
* Structures a lambda function based on some inputs and a CodeBlock body.<br>
* Should be used with {@link #addCode(String, Object...) addCode},
* to provide specific behaviour such as
* <b>methodcall((int x, int y) -> x + y, 5)</b>.
* @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<ParameterSpec> parameters, LambdaMode mode, CodeBlock body) {
code.addLambda(parameters, mode, body);
return this;
}

/**
* Structures a lambda function based on some inputs and a CodeBlock body.<br>
* Will not emit the input types.<br>
* Should be used with {@link #addCode(String, Object...) addCode},
* to provide specific behaviour such as
* <b>methodcall((x, y) -> x + y, 5)</b>.
* @param parameters the input parameters of the function.
* @param body the body of the function.
*/
public Builder addLambda(Iterable<ParameterSpec> parameters, CodeBlock body) {
code.addLambda(parameters, LambdaMode.DEFAULT, body);
return this;
}

/**
* Structures a lambda function based on some inputs and an expression body.<br>
* Should be used with {@link #addCode(String, Object...) addCode},
* to provide specific behaviour such as
* <b>methodcall((int x, int y) -> x + y, 5)</b>.
* @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<ParameterSpec> 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.<br>
* Will not emit the input types.<br>
* Should be used with {@link #addCode(String, Object...) addCode},
* to provide specific behaviour such as
* <b>methodcall((x, y) -> x + y, 5)</b>.
* @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<ParameterSpec> 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.<br>
* Should be used with {@link #addCode(String, Object...) addCode},
* to provide specific behaviour such as
* <b>methodcall(() -> 3 + 2, 5)</b>.
* @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.<br>
* Should be used with {@link #addCode(String, Object...) addCode},
* to provide specific behaviour such as
* <b>methodcall(() -> 3 + 2, 5)</b>.
* @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.<br>
* Should be used with {@link #addCode(String, Object...) addCode},
* to provide specific behaviour such as
* <b>methodcall(() -> 3 + 2, 5)</b>.
* @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.<br>
* Should be used with {@link #addCode(String, Object...) addCode},
* to provide specific behaviour such as
* <b>methodcall(() -> 3 + 2, 5)</b>.
* @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);
}
Expand Down
Loading