Browse Source
Add a `MethodReference` class which can be used to refer to a static or instance method. See gh-28414pull/28422/head
2 changed files with 463 additions and 0 deletions
@ -0,0 +1,237 @@
@@ -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 @@
@@ -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