1
16 package org.springframework.data.repository.query;
17
18 import static java.lang.String.*;
19
20 import java.lang.reflect.Method;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.Optional;
26
27 import org.springframework.core.MethodParameter;
28 import org.springframework.core.ResolvableType;
29 import org.springframework.data.domain.Pageable;
30 import org.springframework.data.domain.Sort;
31 import org.springframework.data.repository.util.ClassUtils;
32 import org.springframework.data.repository.util.QueryExecutionConverters;
33 import org.springframework.data.util.ClassTypeInformation;
34 import org.springframework.data.util.Lazy;
35 import org.springframework.data.util.TypeInformation;
36 import org.springframework.util.Assert;
37
38
45 public class Parameter {
46
47 static final List<Class<?>> TYPES;
48
49 private static final String NAMED_PARAMETER_TEMPLATE = ":%s";
50 private static final String POSITION_PARAMETER_TEMPLATE = "?%s";
51
52 private final MethodParameter parameter;
53 private final Class<?> parameterType;
54 private final boolean isDynamicProjectionParameter;
55 private final Lazy<Optional<String>> name;
56
57 static {
58
59 List<Class<?>> types = new ArrayList<>(Arrays.asList(Pageable.class, Sort.class));
60
61
62
63
64 ClassUtils.ifPresent("kotlin.coroutines.Continuation", Parameter.class.getClassLoader(), types::add);
65
66 TYPES = Collections.unmodifiableList(types);
67 }
68
69
74 protected Parameter(MethodParameter parameter) {
75
76 Assert.notNull(parameter, "MethodParameter must not be null!");
77
78 this.parameter = parameter;
79 this.parameterType = potentiallyUnwrapParameterType(parameter);
80 this.isDynamicProjectionParameter = isDynamicProjectionParameter(parameter);
81 this.name = TYPES.contains(parameter.getParameterType()) ? Lazy.of(Optional.empty()) : Lazy.of(() -> {
82 Param annotation = parameter.getParameterAnnotation(Param.class);
83 return Optional.ofNullable(annotation == null ? parameter.getParameterName() : annotation.value());
84 });
85 }
86
87
93 public boolean isSpecialParameter() {
94 return isDynamicProjectionParameter || TYPES.contains(parameter.getParameterType());
95 }
96
97
102 public boolean isBindable() {
103 return !isSpecialParameter();
104 }
105
106
111 public boolean isDynamicProjectionParameter() {
112 return isDynamicProjectionParameter;
113 }
114
115
120 public String getPlaceholder() {
121
122 if (isNamedParameter()) {
123 return format(NAMED_PARAMETER_TEMPLATE, getName().get());
124 } else {
125 return format(POSITION_PARAMETER_TEMPLATE, getIndex());
126 }
127 }
128
129
134 public int getIndex() {
135 return parameter.getParameterIndex();
136 }
137
138
143 public boolean isNamedParameter() {
144 return !isSpecialParameter() && getName().isPresent();
145 }
146
147
152 public Optional<String> getName() {
153 return this.name.get();
154 }
155
156
161 public Class<?> getType() {
162 return parameterType;
163 }
164
165
171 public boolean isExplicitlyNamed() {
172 return parameter.hasParameterAnnotation(Param.class);
173 }
174
175
179 @Override
180 public String toString() {
181 return format("%s:%s", isNamedParameter() ? getName() : "#" + getIndex(), getType().getName());
182 }
183
184
189 boolean isPageable() {
190 return Pageable.class.isAssignableFrom(getType());
191 }
192
193
198 boolean isSort() {
199 return Sort.class.isAssignableFrom(getType());
200 }
201
202
213 private static boolean isDynamicProjectionParameter(MethodParameter parameter) {
214
215 Method method = parameter.getMethod();
216
217 if (method == null) {
218 throw new IllegalStateException(String.format("Method parameter %s is not backed by a method!", parameter));
219 }
220
221 ClassTypeInformation<?> ownerType = ClassTypeInformation.from(parameter.getDeclaringClass());
222 TypeInformation<?> parameterTypes = ownerType.getParameterTypes(method).get(parameter.getParameterIndex());
223
224 if (!parameterTypes.getType().equals(Class.class)) {
225 return false;
226 }
227
228 TypeInformation<?> bound = parameterTypes.getTypeArguments().get(0);
229 TypeInformation<Object> returnType = ClassTypeInformation.fromReturnTypeOf(method);
230
231 return bound.equals(QueryExecutionConverters.unwrapWrapperTypes(returnType));
232 }
233
234
241 private static boolean isWrapped(MethodParameter parameter) {
242 return QueryExecutionConverters.supports(parameter.getParameterType());
243 }
244
245
252 private static boolean shouldUnwrap(MethodParameter parameter) {
253 return QueryExecutionConverters.supportsUnwrapping(parameter.getParameterType());
254 }
255
256
263 private static Class<?> potentiallyUnwrapParameterType(MethodParameter parameter) {
264
265 Class<?> originalType = parameter.getParameterType();
266
267 if (isWrapped(parameter) && shouldUnwrap(parameter)) {
268 return ResolvableType.forMethodParameter(parameter).getGeneric(0).resolve(Object.class);
269 }
270
271 return originalType;
272 }
273 }
274