Browse Source
Add opinionated builders for return statements and invocations, add introspection type for MethodReturns to reduce checks for e.g. `Optional` and `void` and utilities to construct type names. Closes: #3357pull/3363/head
6 changed files with 1298 additions and 1 deletions
@ -0,0 +1,973 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2025 the original author or authors. |
||||||
|
* |
||||||
|
* 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 |
||||||
|
* |
||||||
|
* https://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 org.springframework.data.javapoet; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Optional; |
||||||
|
import java.util.function.BiConsumer; |
||||||
|
import java.util.function.Consumer; |
||||||
|
import java.util.function.Function; |
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable; |
||||||
|
|
||||||
|
import org.springframework.core.ResolvableType; |
||||||
|
import org.springframework.data.util.ReflectionUtils; |
||||||
|
import org.springframework.javapoet.CodeBlock; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
import org.springframework.util.ClassUtils; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* Utility class for generating Java code blocks using a fluent API. This class provides a structured and extensible |
||||||
|
* programming model to simplify the creation of method calls, return statements, and complex code structures. It is |
||||||
|
* designed to reduce conditional nesting and improve readability in code generation scenarios. |
||||||
|
* <p> |
||||||
|
* Built on top of JavaPoet, this class introduces additional abstractions such as {@link CodeBlockBuilder}, |
||||||
|
* {@link InvocationBuilder}, and {@link TypedReturnBuilder} to facilitate the construction of dynamic code blocks. |
||||||
|
* These abstractions enable developers to create code with conditional logic, argument concatenation, and control flow |
||||||
|
* in a declarative and intuitive manner. |
||||||
|
* <p> |
||||||
|
* This class is intended for internal use within the framework and is not meant to be used directly by application |
||||||
|
* developers. |
||||||
|
* |
||||||
|
* @author Mark Paluch |
||||||
|
* @since 4.0 |
||||||
|
*/ |
||||||
|
public abstract class LordOfTheStrings { |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new {@code CodeBlockBuilder} instance. |
||||||
|
* |
||||||
|
* @return a new {@code CodeBlockBuilder}. |
||||||
|
*/ |
||||||
|
public static CodeBlockBuilder builder() { |
||||||
|
return new CodeBlockBuilder(CodeBlock.builder()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new {@code CodeBlockBuilder} instance using the given {@link CodeBlock.Builder}. |
||||||
|
* |
||||||
|
* @param builder the {@link CodeBlock.Builder} to use. |
||||||
|
* @return a new {@code CodeBlockBuilder}. |
||||||
|
*/ |
||||||
|
public static CodeBlockBuilder builder(CodeBlock.Builder builder) { |
||||||
|
return new CodeBlockBuilder(builder); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new {@code CodeBlockBuilder} instance with an initial format and arguments. |
||||||
|
* |
||||||
|
* @param format the format string. |
||||||
|
* @param args the arguments for the format string. |
||||||
|
* @return a new initialized {@code CodeBlockBuilder}. |
||||||
|
*/ |
||||||
|
public static CodeBlockBuilder builder(String format, @Nullable Object... args) { |
||||||
|
return new CodeBlockBuilder(CodeBlock.builder().add(format, args)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a {@link InvocationBuilder} for building method invocation code. |
||||||
|
* <p> |
||||||
|
* The given {@code methodCall} may contain format specifiers as defined in Java Poet. It must additionally contain a |
||||||
|
* format specifier (last position) that is used to expand the method arguments, for example: |
||||||
|
* |
||||||
|
* <pre class="code"> |
||||||
|
* Sort sort = …; |
||||||
|
* MethodCallBuilder method = PoetrySlam.method("$T.by($L)", Sort.class); |
||||||
|
* |
||||||
|
* method.arguments(sort, (order, builder) -> { |
||||||
|
* builder.add("$T.asc($S)", Sort.Order.class, order.getProperty()); |
||||||
|
* }); |
||||||
|
* |
||||||
|
* method.build(); |
||||||
|
* </pre> |
||||||
|
* |
||||||
|
* @param methodCall the invocation (or method call) format string. |
||||||
|
* @param arguments the arguments for the method call. |
||||||
|
* @return a new {@code MethodCallBuilder}. |
||||||
|
*/ |
||||||
|
public static InvocationBuilder invoke(String methodCall, Object... arguments) { |
||||||
|
return new InvocationBuilder(methodCall, arguments); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a builder for a return statement targeting the given return type. |
||||||
|
* |
||||||
|
* @param returnType the method return type. |
||||||
|
* @return a new {@code ReturnStatementBuilder}. |
||||||
|
*/ |
||||||
|
public static TypedReturnBuilder returning(ResolvableType returnType) { |
||||||
|
return new TypedReturnBuilder(returnType); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a builder for a return statement targeting the given return type. |
||||||
|
* |
||||||
|
* @param returnType the method return type. |
||||||
|
* @return a new {@code ReturnStatementBuilder}. |
||||||
|
*/ |
||||||
|
public static TypedReturnBuilder returning(Class<?> returnType) { |
||||||
|
return new TypedReturnBuilder(ResolvableType.forType(returnType)); |
||||||
|
} |
||||||
|
|
||||||
|
private LordOfTheStrings() { |
||||||
|
// you shall not instantiate
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builder to create method invocation code supporting argument concatenation. |
||||||
|
*/ |
||||||
|
public static class InvocationBuilder { |
||||||
|
|
||||||
|
private final String name; |
||||||
|
private final List<Object> nameArguments; |
||||||
|
private final List<CodeTuple> arguments = new ArrayList<>(); |
||||||
|
|
||||||
|
InvocationBuilder(String name, Object... arguments) { |
||||||
|
this.name = name; |
||||||
|
this.nameArguments = List.of(arguments); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a single argument to the method call. |
||||||
|
* |
||||||
|
* @param argument the argument to add. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public InvocationBuilder argument(String argument) { |
||||||
|
|
||||||
|
Assert.hasText(argument, "Argument must not be null or empty"); |
||||||
|
return argument("$L", argument); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add multiple arguments to the method call creating a literal for each argument. |
||||||
|
* |
||||||
|
* @param arguments the collection of arguments to add. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public InvocationBuilder arguments(Iterable<?> arguments) { |
||||||
|
|
||||||
|
for (Object argument : arguments) { |
||||||
|
argument("$L", argument); |
||||||
|
} |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add multiple arguments to the method call, applying a builder customizer for each argument. |
||||||
|
* |
||||||
|
* @param arguments the iterable of arguments to add. |
||||||
|
* @param consumer the consumer to apply to each argument. |
||||||
|
* @param <T> the type of the arguments. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public <T> InvocationBuilder arguments(Iterable<? extends T> arguments, Function<? super T, CodeBlock> consumer) { |
||||||
|
|
||||||
|
for (T argument : arguments) { |
||||||
|
argument(consumer.apply(argument)); |
||||||
|
} |
||||||
|
|
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a {@link CodeBlock} as an argument to the method call. |
||||||
|
* |
||||||
|
* @param argument the {@link CodeBlock} to add. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public InvocationBuilder argument(CodeBlock argument) { |
||||||
|
|
||||||
|
Assert.notNull(argument, "CodeBlock must not be null"); |
||||||
|
|
||||||
|
if (argument.isEmpty()) { |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
return argument("$L", argument); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a formatted argument to the method call. |
||||||
|
* |
||||||
|
* @param format the format string. |
||||||
|
* @param args the arguments for the format string. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public InvocationBuilder argument(String format, @Nullable Object... args) { |
||||||
|
|
||||||
|
Assert.hasText(format, "Format must not be null or empty"); |
||||||
|
this.arguments.add(new CodeTuple(format, args)); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Build the {@link CodeBlock} representing the method call. The resulting CodeBlock can be used inline or as a |
||||||
|
* {@link CodeBlock.Builder#addStatement(CodeBlock) statement}. |
||||||
|
* |
||||||
|
* @return the constructed {@link CodeBlock}. |
||||||
|
*/ |
||||||
|
public CodeBlock build() { |
||||||
|
|
||||||
|
CodeBlock.Builder builder = CodeBlock.builder(); |
||||||
|
buildCall(builder); |
||||||
|
|
||||||
|
return builder.build(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Build the {@link CodeBlock} representing the method call and assign it to the given variable, for example: |
||||||
|
* |
||||||
|
* <pre class="code"> |
||||||
|
* CodeBlock.Builder builder = …; |
||||||
|
* InvocationBuilder invoke = LordOfTheStrings.invoke("getJdbcOperations().update($L)", …); |
||||||
|
* builder.addStatement(invoke.assignTo("int $L", result)); |
||||||
|
* </pre> |
||||||
|
* |
||||||
|
* The resulting CodeBlock should be used as {@link CodeBlock.Builder#addStatement(CodeBlock) statement}. |
||||||
|
* |
||||||
|
* @param format the format string for the assignment. |
||||||
|
* @param args the arguments for the format string. |
||||||
|
* @return the constructed {@link CodeBlock}. |
||||||
|
*/ |
||||||
|
public CodeBlock assignTo(String format, @Nullable Object... args) { |
||||||
|
|
||||||
|
CodeBlock.Builder builder = CodeBlock.builder(); |
||||||
|
|
||||||
|
builder.add(format.trim() + " = ", args); |
||||||
|
buildCall(builder); |
||||||
|
|
||||||
|
return builder.build(); |
||||||
|
} |
||||||
|
|
||||||
|
private void buildCall(CodeBlock.Builder builder) { |
||||||
|
|
||||||
|
boolean first = true; |
||||||
|
|
||||||
|
CodeBlock.Builder argsBuilder = CodeBlock.builder(); |
||||||
|
for (CodeTuple argument : arguments) { |
||||||
|
|
||||||
|
if (first) { |
||||||
|
first = false; |
||||||
|
} else { |
||||||
|
argsBuilder.add(", "); |
||||||
|
} |
||||||
|
|
||||||
|
argsBuilder.add(argument.format(), argument.args()); |
||||||
|
} |
||||||
|
|
||||||
|
List<Object> allArguments = new ArrayList<>(nameArguments); |
||||||
|
|
||||||
|
if (!argsBuilder.isEmpty()) { |
||||||
|
allArguments.add(argsBuilder.build()); |
||||||
|
} |
||||||
|
|
||||||
|
builder.add(name, allArguments.toArray()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* An extended variant of {@link CodeBlock.Builder} that supports building statements in a fluent way and extended for |
||||||
|
* functional {@link #addStatement(Consumer) statement creation}. |
||||||
|
* <p> |
||||||
|
* This builder provides additional methods for creating and managing code blocks, including support for control flow, |
||||||
|
* named arguments, and conditional statements. It is designed to enhance the readability and flexibility of code |
||||||
|
* block construction. |
||||||
|
* <p> |
||||||
|
* Use this builder to create complex code structures in a fluent and intuitive manner. |
||||||
|
* |
||||||
|
* @see CodeBlock.Builder |
||||||
|
*/ |
||||||
|
public static class CodeBlockBuilder { |
||||||
|
|
||||||
|
private final CodeBlock.Builder builder; |
||||||
|
|
||||||
|
CodeBlockBuilder(CodeBlock.Builder builder) { |
||||||
|
this.builder = builder; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Determine whether this builder is empty. |
||||||
|
* |
||||||
|
* @return {@code true} if the builder is empty; {@code false} otherwise. |
||||||
|
* @see CodeBlock.Builder#isEmpty() |
||||||
|
*/ |
||||||
|
public boolean isEmpty() { |
||||||
|
return builder.isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a formatted statement to the code block. |
||||||
|
* |
||||||
|
* @param format the format string. |
||||||
|
* @param args the arguments for the format string. |
||||||
|
* @return {@code this} builder. |
||||||
|
* @see CodeBlock.Builder#add(String, Object...) |
||||||
|
*/ |
||||||
|
public CodeBlockBuilder add(String format, @Nullable Object... args) { |
||||||
|
|
||||||
|
builder.add(format, args); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a {@link CodeBlock} as a statement to the code block. |
||||||
|
* |
||||||
|
* @param codeBlock the {@link CodeBlock} to add. |
||||||
|
* @return {@code this} builder. |
||||||
|
* @see CodeBlock.Builder#addStatement(CodeBlock) |
||||||
|
*/ |
||||||
|
public CodeBlockBuilder addStatement(CodeBlock codeBlock) { |
||||||
|
|
||||||
|
builder.addStatement(codeBlock); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a statement to the code block using a {@link Consumer} to configure it. |
||||||
|
* |
||||||
|
* @param consumer the {@link Consumer} to configure the statement. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public CodeBlockBuilder addStatement(Consumer<StatementBuilder> consumer) { |
||||||
|
|
||||||
|
StatementBuilder statementBuilder = new StatementBuilder(); |
||||||
|
consumer.accept(statementBuilder); |
||||||
|
|
||||||
|
if (!statementBuilder.isEmpty()) { |
||||||
|
|
||||||
|
this.add("$["); |
||||||
|
|
||||||
|
for (CodeTuple tuple : statementBuilder.tuples) { |
||||||
|
builder.add(tuple.format(), tuple.args()); |
||||||
|
} |
||||||
|
|
||||||
|
this.add(";\n$]"); |
||||||
|
|
||||||
|
} |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a {@link CodeBlock} to the code block. |
||||||
|
* |
||||||
|
* @param codeBlock the {@link CodeBlock} to add. |
||||||
|
* @return {@code this} builder. |
||||||
|
* @see CodeBlock.Builder#addStatement(CodeBlock) |
||||||
|
*/ |
||||||
|
public CodeBlockBuilder add(CodeBlock codeBlock) { |
||||||
|
|
||||||
|
builder.add(codeBlock); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a formatted statement to the code block. |
||||||
|
* |
||||||
|
* @param format the format string. |
||||||
|
* @param args the arguments for the format string. |
||||||
|
* @return {@code this} builder. |
||||||
|
* @see CodeBlock.Builder#addStatement(String, Object...) |
||||||
|
*/ |
||||||
|
public CodeBlockBuilder addStatement(String format, @Nullable Object... args) { |
||||||
|
|
||||||
|
builder.addStatement(format, args); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add named arguments to the code block. |
||||||
|
* |
||||||
|
* @param format the format string. |
||||||
|
* @param arguments the named arguments. |
||||||
|
* @return {@code this} builder. |
||||||
|
* @see CodeBlock.Builder#addNamed(String, Map) |
||||||
|
*/ |
||||||
|
public CodeBlockBuilder addNamed(String format, Map<String, ?> arguments) { |
||||||
|
|
||||||
|
builder.addNamed(format, arguments); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Begin a control flow block with the specified format and arguments. |
||||||
|
* |
||||||
|
* @param controlFlow the control flow format string. |
||||||
|
* @param args the arguments for the control flow format string. |
||||||
|
* @return {@code this} builder. |
||||||
|
* @see CodeBlock.Builder#beginControlFlow(String, Object...) |
||||||
|
*/ |
||||||
|
public CodeBlockBuilder beginControlFlow(String controlFlow, @Nullable Object... args) { |
||||||
|
|
||||||
|
builder.beginControlFlow(controlFlow, args); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* End the current control flow block with the specified format and arguments. |
||||||
|
* |
||||||
|
* @param controlFlow the control flow format string. |
||||||
|
* @param args the arguments for the control flow format string. |
||||||
|
* @return {@code this} builder. |
||||||
|
* @see CodeBlock.Builder#endControlFlow(String, Object...) |
||||||
|
*/ |
||||||
|
public CodeBlockBuilder endControlFlow(String controlFlow, @Nullable Object... args) { |
||||||
|
|
||||||
|
builder.endControlFlow(controlFlow, args); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* End the current control flow block. |
||||||
|
* |
||||||
|
* @return {@code this} builder. |
||||||
|
* @see CodeBlock.Builder#endControlFlow() |
||||||
|
*/ |
||||||
|
public CodeBlockBuilder endControlFlow() { |
||||||
|
|
||||||
|
builder.endControlFlow(); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Begin the next control flow block with the specified format and arguments. |
||||||
|
* |
||||||
|
* @param controlFlow the control flow format string. |
||||||
|
* @param args the arguments for the control flow format string. |
||||||
|
* @return {@code this} builder. |
||||||
|
* @see CodeBlock.Builder#nextControlFlow(String, Object...) |
||||||
|
*/ |
||||||
|
public CodeBlockBuilder nextControlFlow(String controlFlow, @Nullable Object... args) { |
||||||
|
|
||||||
|
builder.nextControlFlow(controlFlow, args); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Indent the current code block. |
||||||
|
* |
||||||
|
* @return {@code this} builder. |
||||||
|
* @see CodeBlock.Builder#indent() |
||||||
|
*/ |
||||||
|
public CodeBlockBuilder indent() { |
||||||
|
|
||||||
|
builder.indent(); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Unindent the current code block. |
||||||
|
* |
||||||
|
* @return {@code this} builder. |
||||||
|
* @see CodeBlock.Builder#unindent() |
||||||
|
*/ |
||||||
|
public CodeBlockBuilder unindent() { |
||||||
|
|
||||||
|
builder.unindent(); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Build the {@link CodeBlock} from the current state of the builder. |
||||||
|
* |
||||||
|
* @return the constructed {@link CodeBlock}. |
||||||
|
*/ |
||||||
|
public CodeBlock build() { |
||||||
|
return builder.build(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Clear the current state of the builder. |
||||||
|
* |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public CodeBlockBuilder clear() { |
||||||
|
|
||||||
|
builder.clear(); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builder for creating statements including conditional and concatenated variants. |
||||||
|
* <p> |
||||||
|
* This builder allows for the creation of complex statements with conditional logic and concatenated elements. It is |
||||||
|
* designed to simplify the construction of dynamic code blocks. |
||||||
|
* <p> |
||||||
|
* Use this builder to handle conditional inclusion in a structured and fluent manner instead of excessive conditional |
||||||
|
* nesting that would be required otherwise in the calling code. |
||||||
|
*/ |
||||||
|
public static class StatementBuilder { |
||||||
|
|
||||||
|
private final List<CodeTuple> tuples = new ArrayList<>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Determine whether this builder is empty. |
||||||
|
* |
||||||
|
* @return {@code true} if the builder is empty; {@code false} otherwise. |
||||||
|
*/ |
||||||
|
public boolean isEmpty() { |
||||||
|
return tuples.isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a conditional statement to the builder if the condition <b>is</b> met. |
||||||
|
* |
||||||
|
* @param state the condition to evaluate. |
||||||
|
* @return a {@link ConditionalStatementStep} for further configuration. |
||||||
|
*/ |
||||||
|
public ConditionalStatementStep when(boolean state) { |
||||||
|
return whenNot(!state); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a conditional statement to the builder if the condition is <b>not</b> met. |
||||||
|
* |
||||||
|
* @param state the condition to evaluate. |
||||||
|
* @return a {@link ConditionalStatementStep} for further configuration. |
||||||
|
*/ |
||||||
|
public ConditionalStatementStep whenNot(boolean state) { |
||||||
|
|
||||||
|
return (format, args) -> { |
||||||
|
|
||||||
|
if (state) { |
||||||
|
add(format, args); |
||||||
|
} |
||||||
|
return this; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a formatted statement to the builder. |
||||||
|
* |
||||||
|
* @param format the format string. |
||||||
|
* @param args the arguments for the format string. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public StatementBuilder add(String format, @Nullable Object... args) { |
||||||
|
tuples.add(new CodeTuple(format, args)); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Concatenate elements into the builder with a delimiter. |
||||||
|
* |
||||||
|
* @param elements the elements to concatenate. |
||||||
|
* @param delim the delimiter to use between elements. |
||||||
|
* @param builderCustomizer the consumer to apply to each element and {@link CodeBlockBuilder}. |
||||||
|
* @param <T> the type of the elements. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public <T> StatementBuilder addAll(Iterable<? extends T> elements, String delim, |
||||||
|
BiConsumer<? super T, CodeBlockBuilder> builderCustomizer) { |
||||||
|
return addAll(elements, t -> delim, builderCustomizer); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Concatenate elements into the builder with a custom delimiter function. |
||||||
|
* |
||||||
|
* @param elements the elements to concatenate. |
||||||
|
* @param delim the function to determine the delimiter for each element. Delimiters are applied beginning with the |
||||||
|
* second iteration element and obtain from the current element. |
||||||
|
* @param builderCustomizer the consumer to apply to each element and {@link CodeBlockBuilder}. |
||||||
|
* @param <T> the type of the elements. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public <T> StatementBuilder addAll(Iterable<? extends T> elements, Function<? super T, String> delim, |
||||||
|
BiConsumer<? super T, CodeBlockBuilder> builderCustomizer) { |
||||||
|
|
||||||
|
boolean first = true; |
||||||
|
for (T element : elements) { |
||||||
|
|
||||||
|
if (first) { |
||||||
|
first = false; |
||||||
|
} else { |
||||||
|
tuples.add(new CodeTuple(delim.apply(element))); |
||||||
|
} |
||||||
|
|
||||||
|
CodeBlockBuilder builder = new CodeBlockBuilder(CodeBlock.builder()); |
||||||
|
builderCustomizer.accept(element, builder); |
||||||
|
|
||||||
|
tuples.add(new CodeTuple("$L", builder.build())); |
||||||
|
} |
||||||
|
|
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Functional interface for conditional statement steps. |
||||||
|
*/ |
||||||
|
public interface ConditionalStatementStep { |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a statement to the builder if the condition is met. |
||||||
|
* |
||||||
|
* @param format the format string. |
||||||
|
* @param args the arguments for the format string. |
||||||
|
* @return the {@link StatementBuilder}. |
||||||
|
*/ |
||||||
|
StatementBuilder then(String format, @Nullable Object... args); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builder for constructing return statements based on the target return type. The resulting {@link #build() |
||||||
|
* CodeBlock} must be added as a {@link CodeBlock.Builder#addStatement(CodeBlock)}. |
||||||
|
*/ |
||||||
|
public abstract static class ReturnBuilderSupport { |
||||||
|
|
||||||
|
private final List<ReturnRule> rules = new ArrayList<>(); |
||||||
|
private final List<ReturnRule> fallback = new ArrayList<>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new builder. |
||||||
|
*/ |
||||||
|
private ReturnBuilderSupport() {} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a return statement if the given condition is {@code true}. |
||||||
|
* |
||||||
|
* @param condition the condition to evaluate. |
||||||
|
* @param format the code format string. |
||||||
|
* @param args the format arguments. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public ReturnBuilderSupport when(boolean condition, String format, @Nullable Object... args) { |
||||||
|
this.rules.add(ruleOf(condition, format, args)); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a fallback return statement if no previous return statement was added. |
||||||
|
* |
||||||
|
* @param format the code format string. |
||||||
|
* @param args the format arguments. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public ReturnBuilderSupport otherwise(String format, @Nullable Object... args) { |
||||||
|
this.fallback.add(ruleOf(true, format, args)); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a fallback return statement if no previous return statement was added. |
||||||
|
* |
||||||
|
* @param builderConsumer the code block builder consumer to apply. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
ReturnBuilderSupport otherwise(Consumer<CodeBlock.Builder> builderConsumer) { |
||||||
|
this.fallback.add(new ReturnRule(true, "", new Object[] {}, builderConsumer)); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Build the code block representing the return statement. |
||||||
|
* |
||||||
|
* @return the resulting {@code CodeBlock} |
||||||
|
*/ |
||||||
|
public CodeBlock build() { |
||||||
|
|
||||||
|
CodeBlock.Builder builder = CodeBlock.builder(); |
||||||
|
|
||||||
|
for (ReturnRule rule : rules) { |
||||||
|
if (rule.condition()) { |
||||||
|
builder.add("return"); |
||||||
|
rule.accept(builder); |
||||||
|
return builder.build(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (ReturnRule rule : fallback) { |
||||||
|
if (rule.condition()) { |
||||||
|
builder.add("return"); |
||||||
|
rule.accept(builder); |
||||||
|
return builder.build(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return builder.build(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a return statement if the given condition is {@code true}. |
||||||
|
* |
||||||
|
* @param condition the condition to evaluate. |
||||||
|
* @param format the code format string. |
||||||
|
* @param args the format arguments. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
static ReturnRule ruleOf(boolean condition, String format, @Nullable Object... args) { |
||||||
|
|
||||||
|
if (format.startsWith("return")) { |
||||||
|
throw new IllegalArgumentException("Return value format '%s' must not contain 'return'".formatted(format)); |
||||||
|
} |
||||||
|
|
||||||
|
return new ReturnRule(condition, format, args, null); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
record ReturnRule(boolean condition, String format, @Nullable Object[] args, |
||||||
|
@Nullable Consumer<CodeBlock.Builder> builderCustomizer) { |
||||||
|
|
||||||
|
public void accept(CodeBlock.Builder builder) { |
||||||
|
|
||||||
|
if (StringUtils.hasText(format()) || builderCustomizer() != null) { |
||||||
|
|
||||||
|
builder.add(" "); |
||||||
|
|
||||||
|
if (StringUtils.hasText(format())) { |
||||||
|
builder.add(format(), args()); |
||||||
|
} |
||||||
|
|
||||||
|
if (builderCustomizer() != null) { |
||||||
|
builderCustomizer().accept(builder); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builder for constructing return statements based on the target return type. The resulting {@link #build() |
||||||
|
* CodeBlock} must be added as a {@link CodeBlock.Builder#addStatement(CodeBlock)}. |
||||||
|
*/ |
||||||
|
public static class TypedReturnBuilder extends ReturnBuilderSupport { |
||||||
|
|
||||||
|
private final ResolvableType returnType; |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new builder for the given return type. |
||||||
|
* |
||||||
|
* @param returnType the method return type |
||||||
|
*/ |
||||||
|
private TypedReturnBuilder(ResolvableType returnType) { |
||||||
|
|
||||||
|
this.returnType = returnType; |
||||||
|
|
||||||
|
// consider early return cases for Void and void.
|
||||||
|
whenBoxed(Void.class, "null"); |
||||||
|
when(ReflectionUtils.isVoid(returnType.toClass()), ""); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add return statements for numeric types if the given {@code resultToReturn} points to a {@link Number}. Considers |
||||||
|
* primitive and boxed {@code int} and {@code long} type return paths and that {@code resultToReturn} can be |
||||||
|
* {@literal null}. |
||||||
|
* |
||||||
|
* @param resultToReturn the argument or variable name holding the result. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public TypedReturnBuilder number(String resultToReturn) { |
||||||
|
return whenBoxedLong("$1L != null ? $1L.longValue() : null", resultToReturn) |
||||||
|
.whenLong("$1L != null ? $1L.longValue() : 0L", resultToReturn) |
||||||
|
.whenBoxedInteger("$1L != null ? $1L.intValue() : null", resultToReturn) |
||||||
|
.whenInt("$1L != null ? $1L.intValue() : 0", resultToReturn); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a return statement if the return type is boolean (primitive or box type) returning {@code returnName}. |
||||||
|
* |
||||||
|
* @param returnName the argument or variable name holding the result. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public TypedReturnBuilder whenBooleanReturn(String returnName) { |
||||||
|
return whenBoolean("$L", returnName); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a return statement if the return type is boolean (primitive or box type). |
||||||
|
* |
||||||
|
* @param format the code format string. |
||||||
|
* @param args the format arguments. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public TypedReturnBuilder whenBoolean(String format, @Nullable Object... args) { |
||||||
|
return when(returnType.isAssignableFrom(boolean.class) || returnType.isAssignableFrom(Boolean.class), format, |
||||||
|
args); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a return statement if the return type is {@link Long} (boxed {@code long} type). |
||||||
|
* |
||||||
|
* @param format the code format string. |
||||||
|
* @param args the format arguments. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public TypedReturnBuilder whenBoxedLong(String format, @Nullable Object... args) { |
||||||
|
return whenBoxed(long.class, format, args); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a return statement if the return type is a primitive {@code long} type. |
||||||
|
* |
||||||
|
* @param format the code format string. |
||||||
|
* @param args the format arguments. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public TypedReturnBuilder whenLong(String format, @Nullable Object... args) { |
||||||
|
return when(returnType.toClass() == long.class, format, args); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a return statement if the return type is {@link Integer} (boxed {@code int} type). |
||||||
|
* |
||||||
|
* @param format the code format string. |
||||||
|
* @param args the format arguments. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public TypedReturnBuilder whenBoxedInteger(String format, @Nullable Object... args) { |
||||||
|
return whenBoxed(int.class, format, args); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a return statement if the return type is a primitive {@code int} type. |
||||||
|
* |
||||||
|
* @param format the code format string. |
||||||
|
* @param args the format arguments. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public TypedReturnBuilder whenInt(String format, @Nullable Object... args) { |
||||||
|
return when(returnType.toClass() == int.class, format, args); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a return statement if the return type matches the given boxed wrapper type. |
||||||
|
* |
||||||
|
* @param primitiveOrWrapper the primitive or wrapper type. |
||||||
|
* @param format the code format string. |
||||||
|
* @param args the format arguments. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public TypedReturnBuilder whenBoxed(Class<?> primitiveOrWrapper, String format, @Nullable Object... args) { |
||||||
|
Class<?> primitiveWrapper = ClassUtils.resolvePrimitiveIfNecessary(primitiveOrWrapper); |
||||||
|
return when(returnType.toClass() == primitiveWrapper, format, args); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a return statement if the return type matches the given primitive or boxed wrapper type. |
||||||
|
* |
||||||
|
* @param primitiveType the primitive or wrapper type. |
||||||
|
* @param format the code format string. |
||||||
|
* @param args the format arguments. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public TypedReturnBuilder whenPrimitiveOrBoxed(Class<?> primitiveType, String format, @Nullable Object... args) { |
||||||
|
|
||||||
|
Class<?> primitiveWrapper = ClassUtils.resolvePrimitiveIfNecessary(primitiveType); |
||||||
|
return when( |
||||||
|
ClassUtils.isAssignable(ClassUtils.resolvePrimitiveIfNecessary(returnType.toClass()), primitiveWrapper), |
||||||
|
format, args); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a return statement if the declared return type is assignable from the given {@code returnType}. |
||||||
|
* |
||||||
|
* @param returnType the candidate return type. |
||||||
|
* @param format the code format string. |
||||||
|
* @param args the format arguments |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public TypedReturnBuilder when(Class<?> returnType, String format, @Nullable Object... args) { |
||||||
|
return when(this.returnType.isAssignableFrom(returnType), format, args); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a return statement if the given condition is {@code true}. |
||||||
|
* |
||||||
|
* @param condition the condition to evaluate. |
||||||
|
* @param format the code format string. |
||||||
|
* @param args the format arguments. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public TypedReturnBuilder when(boolean condition, String format, @Nullable Object... args) { |
||||||
|
super.when(condition, format, args); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a fallback return statement considering that the returned value might be nullable and apply conditionally |
||||||
|
* {@link Optional#ofNullable(Object)} wrapping if the return type is {@code Optional}. |
||||||
|
* |
||||||
|
* @param codeBlock the code block result to be returned. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public TypedReturnBuilder optional(CodeBlock codeBlock) { |
||||||
|
return optional("$L", codeBlock); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a fallback return statement considering that the returned value might be nullable and apply conditionally |
||||||
|
* {@link Optional#ofNullable(Object)} wrapping if the return type is {@code Optional}. |
||||||
|
* |
||||||
|
* @param format the code format string. |
||||||
|
* @param args the format arguments. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public TypedReturnBuilder optional(String format, @Nullable Object... args) { |
||||||
|
|
||||||
|
if (Optional.class.isAssignableFrom(returnType.toClass())) { |
||||||
|
|
||||||
|
if (format.startsWith("return")) { |
||||||
|
throw new IllegalArgumentException("Return value format '%s' must not contain 'return'".formatted(format)); |
||||||
|
} |
||||||
|
|
||||||
|
otherwise(builder -> { |
||||||
|
|
||||||
|
builder.add("$T.ofNullable(", Optional.class); |
||||||
|
builder.add(format, args); |
||||||
|
builder.add(")"); |
||||||
|
}); |
||||||
|
|
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
return otherwise(format, args); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a fallback return statement if no previous return statement was added. |
||||||
|
* |
||||||
|
* @param codeBlock the code block result to be returned. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public TypedReturnBuilder otherwise(CodeBlock codeBlock) { |
||||||
|
return otherwise("$L", codeBlock); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a fallback return statement if no previous return statement was added. |
||||||
|
* |
||||||
|
* @param format the code format string. |
||||||
|
* @param args the format arguments. |
||||||
|
* @return {@code this} builder. |
||||||
|
*/ |
||||||
|
public TypedReturnBuilder otherwise(String format, @Nullable Object... args) { |
||||||
|
super.otherwise(format, args); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
record CodeTuple(String format, @Nullable Object... args) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,106 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2025 the original author or authors. |
||||||
|
* |
||||||
|
* 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 |
||||||
|
* |
||||||
|
* https://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 org.springframework.data.javapoet; |
||||||
|
|
||||||
|
import org.springframework.core.ResolvableType; |
||||||
|
import org.springframework.javapoet.TypeName; |
||||||
|
import org.springframework.util.ClassUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* Collection of {@link org.springframework.javapoet.TypeName} transformation utilities. |
||||||
|
* <p> |
||||||
|
* This class delivers some simple functionality that should be provided by the JavaPoet framework. It also provides |
||||||
|
* easy-to-use methods to convert between types. |
||||||
|
* <p> |
||||||
|
* Mainly for internal use within the framework |
||||||
|
* |
||||||
|
* @author Mark Paluch |
||||||
|
* @since 4.0 |
||||||
|
*/ |
||||||
|
public abstract class TypeNames { |
||||||
|
|
||||||
|
/** |
||||||
|
* Obtain a {@link TypeName class name} for the given type, resolving primitive wrappers as necessary. |
||||||
|
* |
||||||
|
* @param type the class to use. |
||||||
|
* @return the corresponding {@link TypeName}. |
||||||
|
*/ |
||||||
|
public static TypeName classNameOrWrapper(Class<?> type) { |
||||||
|
return ClassUtils.isPrimitiveOrWrapper(type) ? TypeName.get(ClassUtils.resolvePrimitiveIfNecessary(type)) |
||||||
|
: TypeName.get(type); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Obtain a {@link TypeName class name} for the given {@link ResolvableType}, resolving primitive wrappers as |
||||||
|
* necessary. Ideal to represent a type name used as {@code Class} value as generic parameters are not considered. |
||||||
|
* |
||||||
|
* @param resolvableType the resolvable type to use. |
||||||
|
* @return the corresponding {@link TypeName}. |
||||||
|
*/ |
||||||
|
public static TypeName classNameOrWrapper(ResolvableType resolvableType) { |
||||||
|
return classNameOrWrapper(resolvableType.toClass()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Obtain a {@link TypeName} for the given {@link ResolvableType}. Ideal to represent a type name used as |
||||||
|
* {@code Class} value as generic parameters are not considered. |
||||||
|
* |
||||||
|
* @param resolvableType the resolvable type to use. |
||||||
|
* @return the corresponding {@link TypeName}. |
||||||
|
*/ |
||||||
|
public static TypeName className(ResolvableType resolvableType) { |
||||||
|
return TypeName.get(resolvableType.toClass()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Obtain a {@link TypeName} for the underlying type of the given {@link ResolvableType}. Can render a class name, a |
||||||
|
* type signature or a generic type variable. |
||||||
|
* |
||||||
|
* @param resolvableType the resolvable type represent. |
||||||
|
* @return the corresponding {@link TypeName}. |
||||||
|
*/ |
||||||
|
public static TypeName typeName(ResolvableType resolvableType) { |
||||||
|
return TypeName.get(resolvableType.getType()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Obtain a {@link TypeName} for the given type, resolving primitive wrappers as necessary. Ideal to represent a type |
||||||
|
* parameter for parametrized types as primitive boxing is considered. |
||||||
|
* |
||||||
|
* @param type the class to be represented. |
||||||
|
* @return the corresponding {@link TypeName}. |
||||||
|
*/ |
||||||
|
public static TypeName typeNameOrWrapper(Class<?> type) { |
||||||
|
return typeNameOrWrapper(ResolvableType.forClass(type)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Obtain a {@link TypeName} for the given {@link ResolvableType}, resolving primitive wrappers as necessary. Can |
||||||
|
* render a class name, a type signature or a generic type variable. Ideal to represent a type parameter for |
||||||
|
* parametrized types as primitive boxing is considered. |
||||||
|
* |
||||||
|
* @param resolvableType the resolvable type to be represented. |
||||||
|
* @return the corresponding {@link TypeName}. |
||||||
|
*/ |
||||||
|
public static TypeName typeNameOrWrapper(ResolvableType resolvableType) { |
||||||
|
return ClassUtils.isPrimitiveOrWrapper(resolvableType.toClass()) |
||||||
|
? TypeName.get(ClassUtils.resolvePrimitiveIfNecessary(resolvableType.toClass())) |
||||||
|
: typeName(resolvableType); |
||||||
|
} |
||||||
|
|
||||||
|
private TypeNames() {} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
/** |
||||||
|
* Opinionated extensions to JavaPoet to support Spring Data specific use cases. |
||||||
|
*/ |
||||||
|
@org.jspecify.annotations.NullMarked |
||||||
|
package org.springframework.data.javapoet; |
||||||
@ -0,0 +1,196 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2025 the original author or authors. |
||||||
|
* |
||||||
|
* 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 |
||||||
|
* |
||||||
|
* https://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 org.springframework.data.repository.aot.generate; |
||||||
|
|
||||||
|
import java.util.Optional; |
||||||
|
import java.util.stream.Stream; |
||||||
|
|
||||||
|
import org.springframework.core.ResolvableType; |
||||||
|
import org.springframework.data.repository.query.ReturnedType; |
||||||
|
import org.springframework.data.util.ReflectionUtils; |
||||||
|
import org.springframework.data.util.TypeInformation; |
||||||
|
import org.springframework.javapoet.TypeName; |
||||||
|
|
||||||
|
/** |
||||||
|
* Value object that encapsulates introspection of a method's return type, providing convenient access to its |
||||||
|
* characteristics such as projection, optionality, array status, and actual type information. |
||||||
|
* <p> |
||||||
|
* Designed to support repository method analysis in the context of Ahead-of-Time (AOT) processing, this class leverages |
||||||
|
* {@link ReturnedType}, {@link ResolvableType}, and {@link TypeInformation} to expose both the declared and actual |
||||||
|
* return types, including handling of wrapper types, projections, and primitive types. |
||||||
|
* <p> |
||||||
|
* Typical usage involves querying the return type characteristics to drive code generation or runtime behavior in |
||||||
|
* repository infrastructure. |
||||||
|
* |
||||||
|
* @author Mark Paluch |
||||||
|
* @since 4.0 |
||||||
|
*/ |
||||||
|
public class MethodReturn { |
||||||
|
|
||||||
|
private final ReturnedType returnedType; |
||||||
|
private final Class<?> actualReturnClass; |
||||||
|
private final ResolvableType returnType; |
||||||
|
private final ResolvableType actualType; |
||||||
|
private final TypeName typeName; |
||||||
|
private final TypeName className; |
||||||
|
private final TypeName actualTypeName; |
||||||
|
private final TypeName actualClassName; |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new {@code MethodReturn} instance based on the given {@link ReturnedType} and its {@link ResolvableType |
||||||
|
* method return type}. |
||||||
|
* |
||||||
|
* @param returnedType the returned type to inspect. |
||||||
|
* @param returnType the method return type. |
||||||
|
*/ |
||||||
|
public MethodReturn(ReturnedType returnedType, ResolvableType returnType) { |
||||||
|
|
||||||
|
this.returnedType = returnedType; |
||||||
|
this.returnType = returnType; |
||||||
|
Class<?> returnClass = returnType.toClass(); |
||||||
|
|
||||||
|
this.typeName = TypeName.get(returnType.getType()); |
||||||
|
this.className = TypeName.get(returnClass); |
||||||
|
|
||||||
|
TypeInformation<?> typeInformation = TypeInformation.of(returnType); |
||||||
|
TypeInformation<?> actualType = typeInformation.isMap() ? typeInformation |
||||||
|
: (typeInformation.getType().equals(Stream.class) ? typeInformation.getComponentType() |
||||||
|
: typeInformation.getActualType()); |
||||||
|
|
||||||
|
if (actualType != null) { |
||||||
|
|
||||||
|
this.actualType = actualType.toResolvableType(); |
||||||
|
this.actualTypeName = TypeName.get(actualType.toResolvableType().getType()); |
||||||
|
this.actualClassName = TypeName.get(actualType.getType()); |
||||||
|
this.actualReturnClass = actualType.getType(); |
||||||
|
} else { |
||||||
|
this.actualType = returnType; |
||||||
|
this.actualTypeName = typeName; |
||||||
|
this.actualClassName = className; |
||||||
|
this.actualReturnClass = returnClass; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns whether the method return type is a projection. Query projections (e.g. returning {@code String} or |
||||||
|
* {@code int} are not considered. |
||||||
|
* |
||||||
|
* @return {@literal true} if the return type is a projection. |
||||||
|
*/ |
||||||
|
public boolean isProjecting() { |
||||||
|
return returnedType.isProjecting(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns whether the method return type is an interface-based projection. |
||||||
|
* |
||||||
|
* @return {@literal true} if the return type is an interface-based projection. |
||||||
|
*/ |
||||||
|
public boolean isInterfaceProjection() { |
||||||
|
return isProjecting() && returnedType.getReturnedType().isInterface(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns whether the method return type is {@code Optional}. |
||||||
|
* |
||||||
|
* @return {@literal true} if the return type is {@code Optional}. |
||||||
|
*/ |
||||||
|
public boolean isOptional() { |
||||||
|
return Optional.class.isAssignableFrom(toClass()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns whether the method return type is an array. |
||||||
|
* |
||||||
|
* @return {@literal true} if the return type is an array. |
||||||
|
*/ |
||||||
|
public boolean isArray() { |
||||||
|
return toClass().isArray(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns whether the method return type is {@code void}. Considers also {@link Void} and Kotlin's {@code Unit}. |
||||||
|
* |
||||||
|
* @return {@literal true} if the return type is {@code void}. |
||||||
|
*/ |
||||||
|
public boolean isVoid() { |
||||||
|
return ReflectionUtils.isVoid(toClass()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the {@link Class} representing the declared return type. |
||||||
|
* |
||||||
|
* @return the declared return class. |
||||||
|
*/ |
||||||
|
public Class<?> toClass() { |
||||||
|
return returnType.toClass(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the actual type (i.e. component type of a collection). |
||||||
|
* |
||||||
|
* @return the actual type. |
||||||
|
*/ |
||||||
|
public ResolvableType getActualType() { |
||||||
|
return actualType; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the {@link TypeName} representing the declared return type. |
||||||
|
* |
||||||
|
* @return the declared return type name. |
||||||
|
*/ |
||||||
|
public TypeName getTypeName() { |
||||||
|
return typeName; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the {@link TypeName} representing the declared return class (i.e. without generics). |
||||||
|
* |
||||||
|
* @return the declared return class name. |
||||||
|
*/ |
||||||
|
public TypeName getClassName() { |
||||||
|
return className; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the actual {@link TypeName} representing the declared return type (component type of collections). |
||||||
|
* |
||||||
|
* @return the actual return type name. |
||||||
|
*/ |
||||||
|
public TypeName getActualTypeName() { |
||||||
|
return actualTypeName; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the actual {@link TypeName} representing the declared return class (component type of collections). |
||||||
|
* |
||||||
|
* @return the actual return class name. |
||||||
|
*/ |
||||||
|
public TypeName getActualClassName() { |
||||||
|
return actualClassName; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the {@link Class} representing the actual return type. |
||||||
|
* |
||||||
|
* @return the actual return class. |
||||||
|
*/ |
||||||
|
public Class<?> getActualReturnClass() { |
||||||
|
return actualReturnClass; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue