Add LocalVariableNameFactory.
Add a variable name factory that considers predefined names and resolves name clashes. Expose variable name clash resolution via the generation context of a single method. Closes #3270 Original pull request: #3271
This commit is contained in:
committed by
Mark Paluch
parent
3bc89bb221
commit
6360ef83b4
@@ -19,12 +19,10 @@ import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotationSelectors;
|
||||
@@ -54,6 +52,7 @@ public class AotQueryMethodGenerationContext {
|
||||
private final AotRepositoryFragmentMetadata targetTypeMetadata;
|
||||
private final MethodMetadata targetMethodMetadata;
|
||||
private final CodeBlocks codeBlocks;
|
||||
private final VariableNameFactory variableNameFactory;
|
||||
|
||||
AotQueryMethodGenerationContext(RepositoryInformation repositoryInformation, Method method, QueryMethod queryMethod,
|
||||
AotRepositoryFragmentMetadata targetTypeMetadata) {
|
||||
@@ -64,6 +63,7 @@ public class AotQueryMethodGenerationContext {
|
||||
this.repositoryInformation = repositoryInformation;
|
||||
this.targetTypeMetadata = targetTypeMetadata;
|
||||
this.targetMethodMetadata = new MethodMetadata(repositoryInformation, method);
|
||||
this.variableNameFactory = LocalVariableNameFactory.forMethod(targetMethodMetadata);
|
||||
this.codeBlocks = new CodeBlocks(targetTypeMetadata);
|
||||
}
|
||||
|
||||
@@ -127,6 +127,16 @@ public class AotQueryMethodGenerationContext {
|
||||
return TypeName.get(getReturnType().getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggest naming clash free variant for the given intended variable name within the local method context.
|
||||
*
|
||||
* @param variableName the intended variable name.
|
||||
* @return the suggested VariableName
|
||||
*/
|
||||
public String suggestLocalVariableName(String variableName) {
|
||||
return variableNameFactory.generateName(variableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the required parameter name for the {@link Parameter#isBindable() bindable parameter} at the given
|
||||
* {@code parameterIndex} or throws {@link IllegalArgumentException} if the parameter cannot be determined by its
|
||||
@@ -227,7 +237,7 @@ public class AotQueryMethodGenerationContext {
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
for (Parameter parameter : queryMethod.getParameters().getBindableParameters()) {
|
||||
parameter.getName().map(result::add);
|
||||
getParameterName(parameter.getIndex());
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -237,14 +247,7 @@ public class AotQueryMethodGenerationContext {
|
||||
* @return list of all parameter names (including non-bindable special parameters).
|
||||
*/
|
||||
public List<String> getAllParameterNames() {
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
for (Parameter parameter : queryMethod.getParameters()) {
|
||||
parameter.getName().map(result::add);
|
||||
}
|
||||
|
||||
return result;
|
||||
return targetMethodMetadata.getMethodArguments().keySet().stream().toList();
|
||||
}
|
||||
|
||||
public boolean hasField(String fieldName) {
|
||||
@@ -269,17 +272,7 @@ public class AotQueryMethodGenerationContext {
|
||||
}
|
||||
|
||||
public @Nullable String getParameterName(int position) {
|
||||
|
||||
if (0 > position) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Entry<String, ParameterSpec>> entries = new ArrayList<>(
|
||||
targetMethodMetadata.getMethodArguments().entrySet());
|
||||
if (position < entries.size()) {
|
||||
return entries.get(position).getKey();
|
||||
}
|
||||
return null;
|
||||
return targetMethodMetadata.getParameterName(position);
|
||||
}
|
||||
|
||||
public void addParameter(ParameterSpec parameter) {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
package org.springframework.data.repository.aot.generate;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
@@ -24,13 +23,8 @@ import java.util.stream.Collectors;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.ParameterSpec;
|
||||
import org.springframework.javapoet.TypeName;
|
||||
import org.springframework.javapoet.TypeVariableName;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -50,25 +44,7 @@ class AotRepositoryMethodBuilder {
|
||||
private BiConsumer<AotQueryMethodGenerationContext, MethodSpec.Builder> customizer = (context, body) -> {};
|
||||
|
||||
AotRepositoryMethodBuilder(AotQueryMethodGenerationContext context) {
|
||||
|
||||
this.context = context;
|
||||
initParameters(context.getMethod(), context.getRepositoryInformation());
|
||||
}
|
||||
|
||||
private void initParameters(Method method, RepositoryInformation repositoryInformation) {
|
||||
|
||||
ResolvableType repositoryInterface = ResolvableType.forClass(repositoryInformation.getRepositoryInterface());
|
||||
|
||||
for (Parameter parameter : method.getParameters()) {
|
||||
|
||||
MethodParameter methodParameter = MethodParameter.forParameter(parameter);
|
||||
methodParameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
|
||||
ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(methodParameter, repositoryInterface);
|
||||
|
||||
TypeName parameterType = TypeName.get(resolvableParameterType.getType());
|
||||
|
||||
this.context.addParameter(ParameterSpec.builder(parameterType, methodParameter.getParameterName()).build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.Set;
|
||||
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Non thread safe {@link VariableNameFactory} implementation keeping track of defined names resolving name clashes
|
||||
* using internal counters appending {@code _%d} to a suggested name in case of a clash.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 4.0
|
||||
*/
|
||||
class LocalVariableNameFactory implements VariableNameFactory {
|
||||
|
||||
private final MultiValueMap<String, String> variables;
|
||||
|
||||
/**
|
||||
* Create a new {@link LocalVariableNameFactory} considering available {@link MethodMetadata#getMethodArguments()
|
||||
* method arguments}.
|
||||
*
|
||||
* @param methodMetadata source metadata
|
||||
* @return new instance of {@link LocalVariableNameFactory}.
|
||||
*/
|
||||
static LocalVariableNameFactory forMethod(MethodMetadata methodMetadata) {
|
||||
return of(methodMetadata.getMethodArguments().keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link LocalVariableNameFactory} with a predefined set of initial variable names.
|
||||
*
|
||||
* @param predefinedVariables variables already known to be used in the given context.
|
||||
* @return new instance of {@link LocalVariableNameFactory}.
|
||||
*/
|
||||
static LocalVariableNameFactory of(Set<String> predefinedVariables) {
|
||||
return new LocalVariableNameFactory(predefinedVariables);
|
||||
}
|
||||
|
||||
LocalVariableNameFactory(Iterable<String> predefinedVariableNames) {
|
||||
|
||||
variables = new LinkedMultiValueMap<>();
|
||||
predefinedVariableNames.forEach(varName -> variables.add(varName, varName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateName(String intendedVariableName) {
|
||||
|
||||
if (!variables.containsKey(intendedVariableName)) {
|
||||
variables.add(intendedVariableName, intendedVariableName);
|
||||
return intendedVariableName;
|
||||
}
|
||||
|
||||
String targetName = suggestTargetName(intendedVariableName);
|
||||
variables.add(intendedVariableName, targetName);
|
||||
variables.add(targetName, targetName);
|
||||
return targetName;
|
||||
}
|
||||
|
||||
String suggestTargetName(String suggested) {
|
||||
return suggestTargetName(suggested, 1);
|
||||
}
|
||||
|
||||
String suggestTargetName(String suggested, int counter) {
|
||||
|
||||
String targetName = "%s_%s".formatted(suggested, counter);
|
||||
if (!variables.containsKey(targetName)) {
|
||||
return targetName;
|
||||
}
|
||||
return suggestTargetName(suggested, counter + 1);
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,16 @@
|
||||
package org.springframework.data.repository.aot.generate;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.javapoet.ParameterSpec;
|
||||
@@ -31,6 +35,7 @@ import org.springframework.javapoet.TypeName;
|
||||
* Metadata about an AOT Repository method.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 4.0
|
||||
*/
|
||||
class MethodMetadata {
|
||||
|
||||
@@ -38,10 +43,11 @@ class MethodMetadata {
|
||||
private final ResolvableType actualReturnType;
|
||||
private final ResolvableType returnType;
|
||||
|
||||
public MethodMetadata(RepositoryInformation repositoryInformation, Method method) {
|
||||
MethodMetadata(RepositoryInformation repositoryInformation, Method method) {
|
||||
|
||||
this.returnType = repositoryInformation.getReturnType(method).toResolvableType();
|
||||
this.actualReturnType = ResolvableType.forType(repositoryInformation.getReturnedDomainClass(method));
|
||||
this.initParameters(repositoryInformation, method, new DefaultParameterNameDiscoverer());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -54,20 +60,50 @@ class MethodMetadata {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ResolvableType getReturnType() {
|
||||
ResolvableType getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
public ResolvableType getActualReturnType() {
|
||||
ResolvableType getActualReturnType() {
|
||||
return actualReturnType;
|
||||
}
|
||||
|
||||
public void addParameter(ParameterSpec parameterSpec) {
|
||||
void addParameter(ParameterSpec parameterSpec) {
|
||||
this.methodArguments.put(parameterSpec.name, parameterSpec);
|
||||
}
|
||||
|
||||
public Map<String, ParameterSpec> getMethodArguments() {
|
||||
Map<String, ParameterSpec> getMethodArguments() {
|
||||
return methodArguments;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getParameterName(int position) {
|
||||
|
||||
if (0 > position) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Entry<String, ParameterSpec>> entries = new ArrayList<>(methodArguments.entrySet());
|
||||
if (position < entries.size()) {
|
||||
return entries.get(position).getKey();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void initParameters(RepositoryInformation repositoryInformation, Method method,
|
||||
ParameterNameDiscoverer nameDiscoverer) {
|
||||
|
||||
ResolvableType repositoryInterface = ResolvableType.forClass(repositoryInformation.getRepositoryInterface());
|
||||
|
||||
for (java.lang.reflect.Parameter parameter : method.getParameters()) {
|
||||
|
||||
MethodParameter methodParameter = MethodParameter.forParameter(parameter);
|
||||
methodParameter.initParameterNameDiscovery(nameDiscoverer);
|
||||
ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(methodParameter, repositoryInterface);
|
||||
|
||||
TypeName parameterType = TypeName.get(resolvableParameterType.getType());
|
||||
|
||||
addParameter(ParameterSpec.builder(parameterType, methodParameter.getParameterName()).build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 org.springframework.lang.CheckReturnValue;
|
||||
|
||||
/**
|
||||
* Name factory for generating clash free variable names checking an intended name against predefined and already used
|
||||
* ones.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 4.0
|
||||
*/
|
||||
interface VariableNameFactory {
|
||||
|
||||
/**
|
||||
* Compare and potentially generate a new name for the given intended variable name.
|
||||
*
|
||||
* @param intendedVariableName must not be {@literal null}.
|
||||
* @return the {@literal intendedVariableName} if no naming clash detected or a clash free generated name.
|
||||
*/
|
||||
@CheckReturnValue
|
||||
String generateName(String intendedVariableName);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
* Tests targeting {@link AotQueryMethodGenerationContext}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
class AotQueryMethodGenerationContextUnitTests {
|
||||
|
||||
@Test // GH-3270
|
||||
void suggestLocalVariableNameConsidersMethodArguments() throws NoSuchMethodException {
|
||||
|
||||
AotQueryMethodGenerationContext ctx = ctxFor("reservedParameterMethod");
|
||||
|
||||
assertThat(ctx.suggestLocalVariableName("foo")).isEqualTo("foo");
|
||||
assertThat(ctx.suggestLocalVariableName("arg0")).isNotIn("arg0", "arg1", "arg2");
|
||||
}
|
||||
|
||||
AotQueryMethodGenerationContext ctxFor(String methodName) throws NoSuchMethodException {
|
||||
|
||||
Method target = null;
|
||||
for (Method m : DummyRepo.class.getMethods()) {
|
||||
if (m.getName().equals(methodName)) {
|
||||
target = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
|
||||
RepositoryInformation ri = Mockito.mock(RepositoryInformation.class);
|
||||
Mockito.doReturn(TypeInformation.of(target.getReturnType())).when(ri).getReturnType(eq(target));
|
||||
|
||||
return new AotQueryMethodGenerationContext(ri, target, Mockito.mock(QueryMethod.class),
|
||||
Mockito.mock(AotRepositoryFragmentMetadata.class));
|
||||
}
|
||||
|
||||
private interface DummyRepo {
|
||||
String reservedParameterMethod(Object arg0, Pageable arg1, Object arg2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
class LocalVariableNameFactoryUnitTests {
|
||||
|
||||
LocalVariableNameFactory variableNameFactory;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
variableNameFactory = LocalVariableNameFactory.of(Set.of("firstname", "lastname", "sort"));
|
||||
}
|
||||
|
||||
@Test // GH-3270
|
||||
void resolvesNameClashesInNames() {
|
||||
|
||||
assertThat(variableNameFactory.generateName("name")).isEqualTo("name");
|
||||
assertThat(variableNameFactory.generateName("name")).isEqualTo("name_1");
|
||||
assertThat(variableNameFactory.generateName("name")).isEqualTo("name_2");
|
||||
assertThat(variableNameFactory.generateName("name1")).isEqualTo("name1");
|
||||
assertThat(variableNameFactory.generateName("name3")).isEqualTo("name3");
|
||||
assertThat(variableNameFactory.generateName("name3")).isEqualTo("name3_1");
|
||||
assertThat(variableNameFactory.generateName("name4_1")).isEqualTo("name4_1");
|
||||
assertThat(variableNameFactory.generateName("name4")).isEqualTo("name4");
|
||||
assertThat(variableNameFactory.generateName("name4_1_1")).isEqualTo("name4_1_1");
|
||||
assertThat(variableNameFactory.generateName("name4_1")).isEqualTo("name4_1_2");
|
||||
assertThat(variableNameFactory.generateName("name4_1")).isEqualTo("name4_1_3");
|
||||
}
|
||||
|
||||
@Test // GH-3270
|
||||
void worksWithVariablesContainingUnderscores() {
|
||||
|
||||
assertThat(variableNameFactory.generateName("first_name")).isEqualTo("first_name");
|
||||
assertThat(variableNameFactory.generateName("first_name")).isEqualTo("first_name_1");
|
||||
assertThat(variableNameFactory.generateName("first_name")).isEqualTo("first_name_2");
|
||||
assertThat(variableNameFactory.generateName("first_name_3")).isEqualTo("first_name_3");
|
||||
assertThat(variableNameFactory.generateName("first_name")).isEqualTo("first_name_4");
|
||||
}
|
||||
|
||||
@Test // GH-3270
|
||||
void considersPredefinedNames() {
|
||||
assertThat(variableNameFactory.generateName("firstname")).isEqualTo("firstname_1");
|
||||
}
|
||||
|
||||
@Test // GH-3270
|
||||
void considersCase() {
|
||||
assertThat(variableNameFactory.generateName("firstName")).isEqualTo("firstName");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MethodMetadata}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
class MethodMetadataUnitTests {
|
||||
|
||||
@Test // GH-3270
|
||||
void getParameterNameByIndex() throws NoSuchMethodException {
|
||||
|
||||
MethodMetadata metadata = methodMetadataFor("threeArgsMethod");
|
||||
|
||||
assertThat(metadata.getParameterName(0)).isEqualTo("arg0");
|
||||
assertThat(metadata.getParameterName(1)).isEqualTo("arg1");
|
||||
assertThat(metadata.getParameterName(2)).isEqualTo("arg2");
|
||||
}
|
||||
|
||||
@Test // GH-3270
|
||||
void getParameterNameByNonExistingIndex() throws NoSuchMethodException {
|
||||
|
||||
MethodMetadata metadata = methodMetadataFor("threeArgsMethod");
|
||||
|
||||
assertThat(metadata.getParameterName(-1)).isNull();
|
||||
assertThat(metadata.getParameterName(3)).isNull();
|
||||
}
|
||||
|
||||
@Test // GH-3270
|
||||
void getParameterNameForNoArgsMethod() throws NoSuchMethodException {
|
||||
assertThat(methodMetadataFor("noArgsMethod").getParameterName(0)).isNull();
|
||||
}
|
||||
|
||||
static MethodMetadata methodMetadataFor(String methodName) throws NoSuchMethodException {
|
||||
|
||||
Method target = null;
|
||||
for (Method m : DummyRepo.class.getMethods()) {
|
||||
if (m.getName().equals(methodName)) {
|
||||
target = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
|
||||
RepositoryInformation ri = Mockito.mock(RepositoryInformation.class);
|
||||
Mockito.doReturn(TypeInformation.of(target.getReturnType())).when(ri).getReturnType(eq(target));
|
||||
return new MethodMetadata(ri, target);
|
||||
}
|
||||
|
||||
private interface DummyRepo {
|
||||
|
||||
String noArgsMethod();
|
||||
|
||||
String threeArgsMethod(Object arg0, Pageable arg1, Object arg2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user