Add MethodReference support
Add a `MethodReference` class which can be used to refer to a static or instance method. See gh-28414
This commit is contained in:
parent
1816c77c51
commit
c5c68a4662
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* Copyright 2002-2022 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.aot.generate;
|
||||
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A reference to a static or instance method.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class MethodReference {
|
||||
|
||||
private final Kind kind;
|
||||
|
||||
private final ClassName declaringClass;
|
||||
|
||||
private final String methodName;
|
||||
|
||||
|
||||
private MethodReference(Kind kind, @Nullable ClassName declaringClass,
|
||||
String methodName) {
|
||||
this.kind = kind;
|
||||
this.declaringClass = declaringClass;
|
||||
this.methodName = methodName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new method reference that refers to the given instance method.
|
||||
* @param methodName the method name
|
||||
* @return a new {@link MethodReference} instance
|
||||
*/
|
||||
public static MethodReference of(String methodName) {
|
||||
Assert.hasLength(methodName, "'methodName' must not be empty");
|
||||
return new MethodReference(Kind.INSTANCE, null, methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new method reference that refers to the given instance method.
|
||||
* @param declaringClass the declaring class
|
||||
* @param methodName the method name
|
||||
* @return a new {@link MethodReference} instance
|
||||
*/
|
||||
public static MethodReference of(Class<?> declaringClass, String methodName) {
|
||||
Assert.notNull(declaringClass, "'declaringClass' must not be null");
|
||||
Assert.hasLength(methodName, "'methodName' must not be empty");
|
||||
return new MethodReference(Kind.INSTANCE, ClassName.get(declaringClass),
|
||||
methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new method reference that refers to the given instance method.
|
||||
* @param declaringClass the declaring class
|
||||
* @param methodName the method name
|
||||
* @return a new {@link MethodReference} instance
|
||||
*/
|
||||
public static MethodReference of(ClassName declaringClass, String methodName) {
|
||||
Assert.notNull(declaringClass, "'declaringClass' must not be null");
|
||||
Assert.hasLength(methodName, "'methodName' must not be empty");
|
||||
return new MethodReference(Kind.INSTANCE, declaringClass, methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new method reference that refers to the given static method.
|
||||
* @param declaringClass the declaring class
|
||||
* @param methodName the method name
|
||||
* @return a new {@link MethodReference} instance
|
||||
*/
|
||||
public static MethodReference ofStatic(Class<?> declaringClass, String methodName) {
|
||||
Assert.notNull(declaringClass, "'declaringClass' must not be null");
|
||||
Assert.hasLength(methodName, "'methodName' must not be empty");
|
||||
return new MethodReference(Kind.STATIC, ClassName.get(declaringClass),
|
||||
methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new method reference that refers to the given static method.
|
||||
* @param declaringClass the declaring class
|
||||
* @param methodName the method name
|
||||
* @return a new {@link MethodReference} instance
|
||||
*/
|
||||
public static MethodReference ofStatic(ClassName declaringClass, String methodName) {
|
||||
Assert.notNull(declaringClass, "'declaringClass' must not be null");
|
||||
Assert.hasLength(methodName, "'methodName' must not be empty");
|
||||
return new MethodReference(Kind.STATIC, declaringClass, methodName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the referenced declaring class.
|
||||
* @return the declaring class
|
||||
*/
|
||||
public ClassName getDeclaringClass() {
|
||||
return this.declaringClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the referenced method name.
|
||||
* @return the method name
|
||||
*/
|
||||
public String getMethodName() {
|
||||
return this.methodName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this method reference as a {@link CodeBlock}. If the reference is
|
||||
* to an instance method then {@code this::<method name>} will be returned.
|
||||
* @return a code block for the method reference.
|
||||
* @see #toCodeBlock(String)
|
||||
*/
|
||||
public CodeBlock toCodeBlock() {
|
||||
return toCodeBlock(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this method reference as a {@link CodeBlock}. If the reference is
|
||||
* to an instance method and {@code instanceVariable} is {@code null} then
|
||||
* {@code this::<method name>} will be returned. No {@code instanceVariable}
|
||||
* can be specified for static method references.
|
||||
* @param instanceVariable the instance variable or {@code null}
|
||||
* @return a code block for the method reference.
|
||||
* @see #toCodeBlock(String)
|
||||
*/
|
||||
public CodeBlock toCodeBlock(@Nullable String instanceVariable) {
|
||||
return switch (this.kind) {
|
||||
case INSTANCE -> toCodeBlockForInstance(instanceVariable);
|
||||
case STATIC -> toCodeBlockForStatic(instanceVariable);
|
||||
};
|
||||
}
|
||||
|
||||
private CodeBlock toCodeBlockForInstance(String instanceVariable) {
|
||||
instanceVariable = (instanceVariable != null) ? instanceVariable : "this";
|
||||
return CodeBlock.of("$L::$L", instanceVariable, this.methodName);
|
||||
|
||||
}
|
||||
|
||||
private CodeBlock toCodeBlockForStatic(@Nullable String instanceVariable) {
|
||||
Assert.isTrue(instanceVariable == null,
|
||||
"'instanceVariable' must be null for static method references");
|
||||
return CodeBlock.of("$T::$L", this.declaringClass, this.methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this method reference as an invocation {@link CodeBlock}.
|
||||
* @param arguments the method arguments
|
||||
* @return a code back to invoke the method
|
||||
*/
|
||||
public CodeBlock toInvokeCodeBlock(CodeBlock... arguments) {
|
||||
return toInvokeCodeBlock(null, arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this method reference as an invocation {@link CodeBlock}.
|
||||
* @param instanceVariable the instance variable or {@code null}
|
||||
* @param arguments the method arguments
|
||||
* @return a code back to invoke the method
|
||||
*/
|
||||
public CodeBlock toInvokeCodeBlock(@Nullable String instanceVariable,
|
||||
CodeBlock... arguments) {
|
||||
|
||||
return switch (this.kind) {
|
||||
case INSTANCE -> toInvokeCodeBlockForInstance(instanceVariable, arguments);
|
||||
case STATIC -> toInvokeCodeBlockForStatic(instanceVariable, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
private CodeBlock toInvokeCodeBlockForInstance(@Nullable String instanceVariable,
|
||||
CodeBlock[] arguments) {
|
||||
|
||||
CodeBlock.Builder builder = CodeBlock.builder();
|
||||
if (instanceVariable != null) {
|
||||
builder.add("$L.", instanceVariable);
|
||||
}
|
||||
else if (this.declaringClass != null) {
|
||||
builder.add("new $T().", this.declaringClass);
|
||||
}
|
||||
builder.add("$L", this.methodName);
|
||||
addArguments(builder, arguments);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private CodeBlock toInvokeCodeBlockForStatic(@Nullable String instanceVariable,
|
||||
CodeBlock[] arguments) {
|
||||
|
||||
Assert.isTrue(instanceVariable == null,
|
||||
"'instanceVariable' must be null for static method references");
|
||||
CodeBlock.Builder builder = CodeBlock.builder();
|
||||
builder.add("$T.$L", this.declaringClass, this.methodName);
|
||||
addArguments(builder, arguments);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void addArguments(CodeBlock.Builder builder, CodeBlock[] arguments) {
|
||||
builder.add("(");
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
if (i != 0) {
|
||||
builder.add(", ");
|
||||
}
|
||||
builder.add(arguments[i]);
|
||||
}
|
||||
builder.add(")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return switch (this.kind) {
|
||||
case INSTANCE -> ((this.declaringClass != null) ? "<" + this.declaringClass + ">"
|
||||
: "<instance>") + "::" + this.methodName;
|
||||
case STATIC -> this.declaringClass + "::" + this.methodName;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private enum Kind {
|
||||
INSTANCE, STATIC
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
* Copyright 2002-2022 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.aot.generate;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.javapoet.ClassName;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link MethodReference}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class MethodReferenceTests {
|
||||
|
||||
private static final String EXPECTED_STATIC = "org.springframework.aot.generate.MethodReferenceTests::someMethod";
|
||||
|
||||
private static final String EXPECTED_ANONYMOUS_INSTANCE = "<instance>::someMethod";
|
||||
|
||||
private static final String EXPECTED_DECLARED_INSTANCE = "<org.springframework.aot.generate.MethodReferenceTests>::someMethod";
|
||||
|
||||
|
||||
@Test
|
||||
void ofWithStringWhenMethodNameIsNullThrowsException() {
|
||||
String methodName = null;
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> MethodReference.of(methodName))
|
||||
.withMessage("'methodName' must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithStringCreatesMethodReference() {
|
||||
String methodName = "someMethod";
|
||||
MethodReference reference = MethodReference.of(methodName);
|
||||
assertThat(reference).hasToString(EXPECTED_ANONYMOUS_INSTANCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithClassAndStringWhenDeclaringClassIsNullThrowsException() {
|
||||
Class<?> declaringClass = null;
|
||||
String methodName = "someMethod";
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> MethodReference.of(declaringClass, methodName))
|
||||
.withMessage("'declaringClass' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithClassAndStringWhenMethodNameIsNullThrowsException() {
|
||||
Class<?> declaringClass = MethodReferenceTests.class;
|
||||
String methodName = null;
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> MethodReference.of(declaringClass, methodName))
|
||||
.withMessage("'methodName' must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithClassAndStringCreatesMethodReference() {
|
||||
Class<?> declaringClass = MethodReferenceTests.class;
|
||||
String methodName = "someMethod";
|
||||
MethodReference reference = MethodReference.of(declaringClass, methodName);
|
||||
assertThat(reference).hasToString(EXPECTED_DECLARED_INSTANCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithClassNameAndStringWhenDeclaringClassIsNullThrowsException() {
|
||||
ClassName declaringClass = null;
|
||||
String methodName = "someMethod";
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> MethodReference.of(declaringClass, methodName))
|
||||
.withMessage("'declaringClass' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithClassNameAndStringWhenMethodNameIsNullThrowsException() {
|
||||
ClassName declaringClass = ClassName.get(MethodReferenceTests.class);
|
||||
String methodName = null;
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> MethodReference.of(declaringClass, methodName))
|
||||
.withMessage("'methodName' must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithClassNameAndStringCreateMethodReference() {
|
||||
ClassName declaringClass = ClassName.get(MethodReferenceTests.class);
|
||||
String methodName = "someMethod";
|
||||
MethodReference reference = MethodReference.of(declaringClass, methodName);
|
||||
assertThat(reference).hasToString(EXPECTED_DECLARED_INSTANCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofStaticWithClassAndStringWhenDeclaringClassIsNullThrowsException() {
|
||||
Class<?> declaringClass = null;
|
||||
String methodName = "someMethod";
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> MethodReference.ofStatic(declaringClass, methodName))
|
||||
.withMessage("'declaringClass' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofStaticWithClassAndStringWhenMethodNameIsEmptyThrowsException() {
|
||||
Class<?> declaringClass = MethodReferenceTests.class;
|
||||
String methodName = null;
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> MethodReference.ofStatic(declaringClass, methodName))
|
||||
.withMessage("'methodName' must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofStaticWithClassAndStringCreatesMethodReference() {
|
||||
Class<?> declaringClass = MethodReferenceTests.class;
|
||||
String methodName = "someMethod";
|
||||
MethodReference reference = MethodReference.ofStatic(declaringClass, methodName);
|
||||
assertThat(reference).hasToString(EXPECTED_STATIC);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofStaticWithClassNameAndGeneratedMethodNameWhenDeclaringClassIsNullThrowsException() {
|
||||
ClassName declaringClass = null;
|
||||
String methodName = "someMethod";
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> MethodReference.ofStatic(declaringClass, methodName))
|
||||
.withMessage("'declaringClass' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofStaticWithClassNameAndGeneratedMethodNameWhenMethodNameIsEmptyThrowsException() {
|
||||
ClassName declaringClass = ClassName.get(MethodReferenceTests.class);
|
||||
String methodName = null;
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> MethodReference.ofStatic(declaringClass, methodName))
|
||||
.withMessage("'methodName' must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofStaticWithClassNameAndGeneratedMethodNameCreatesMethodReference() {
|
||||
ClassName declaringClass = ClassName.get(MethodReferenceTests.class);
|
||||
String methodName = "someMethod";
|
||||
MethodReference reference = MethodReference.ofStatic(declaringClass, methodName);
|
||||
assertThat(reference).hasToString(EXPECTED_STATIC);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toCodeBlockWhenInstanceMethodReferenceAndInstanceVariableIsNull() {
|
||||
MethodReference reference = MethodReference.of("someMethod");
|
||||
assertThat(reference.toCodeBlock(null)).hasToString("this::someMethod");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toCodeBlockWhenInstanceMethodReferenceAndInstanceVariableIsNotNull() {
|
||||
MethodReference reference = MethodReference.of("someMethod");
|
||||
assertThat(reference.toCodeBlock("myInstance"))
|
||||
.hasToString("myInstance::someMethod");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toCodeBlockWhenStaticMethodReferenceAndInstanceVariableIsNull() {
|
||||
MethodReference reference = MethodReference.ofStatic(MethodReferenceTests.class,
|
||||
"someMethod");
|
||||
assertThat(reference.toCodeBlock(null)).hasToString(EXPECTED_STATIC);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toCodeBlockWhenStaticMethodReferenceAndInstanceVariableIsNotNullThrowsException() {
|
||||
MethodReference reference = MethodReference.ofStatic(MethodReferenceTests.class,
|
||||
"someMethod");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> reference.toCodeBlock("myInstance")).withMessage(
|
||||
"'instanceVariable' must be null for static method references");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toInvokeCodeBlockWhenInstanceMethodReferenceAndInstanceVariableIsNull() {
|
||||
MethodReference reference = MethodReference.of("someMethod");
|
||||
assertThat(reference.toInvokeCodeBlock()).hasToString("someMethod()");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toInvokeCodeBlockWhenInstanceMethodReferenceAndInstanceVariableIsNullAndHasDecalredClass() {
|
||||
MethodReference reference = MethodReference.of(MethodReferenceTests.class,
|
||||
"someMethod");
|
||||
assertThat(reference.toInvokeCodeBlock()).hasToString(
|
||||
"new org.springframework.aot.generate.MethodReferenceTests().someMethod()");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toInvokeCodeBlockWhenInstanceMethodReferenceAndInstanceVariableIsNotNull() {
|
||||
MethodReference reference = MethodReference.of("someMethod");
|
||||
assertThat(reference.toInvokeCodeBlock("myInstance"))
|
||||
.hasToString("myInstance.someMethod()");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toInvokeCodeBlockWhenStaticMethodReferenceAndInstanceVariableIsNull() {
|
||||
MethodReference reference = MethodReference.ofStatic(MethodReferenceTests.class,
|
||||
"someMethod");
|
||||
assertThat(reference.toInvokeCodeBlock()).hasToString(
|
||||
"org.springframework.aot.generate.MethodReferenceTests.someMethod()");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toInvokeCodeBlockWhenStaticMethodReferenceAndInstanceVariableIsNotNullThrowsException() {
|
||||
MethodReference reference = MethodReference.ofStatic(MethodReferenceTests.class,
|
||||
"someMethod");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> reference.toInvokeCodeBlock("myInstance")).withMessage(
|
||||
"'instanceVariable' must be null for static method references");
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue