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.Iterator;
24 import java.util.List;
25
26 import org.springframework.core.DefaultParameterNameDiscoverer;
27 import org.springframework.core.MethodParameter;
28 import org.springframework.core.ParameterNameDiscoverer;
29 import org.springframework.data.domain.Pageable;
30 import org.springframework.data.domain.Sort;
31 import org.springframework.data.util.Lazy;
32 import org.springframework.data.util.Streamable;
33 import org.springframework.util.Assert;
34
35
41 public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter> implements Streamable<T> {
42
43 public static final List<Class<?>> TYPES = Arrays.asList(Pageable.class, Sort.class);
44
45 private static final String PARAM_ON_SPECIAL = format("You must not user @%s on a parameter typed %s or %s",
46 Param.class.getSimpleName(), Pageable.class.getSimpleName(), Sort.class.getSimpleName());
47 private static final String ALL_OR_NOTHING = String.format(
48 "Either use @%s on all parameters except %s and %s typed once, or none at all!", Param.class.getSimpleName(),
49 Pageable.class.getSimpleName(), Sort.class.getSimpleName());
50
51 private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
52
53 private final int pageableIndex;
54 private final int sortIndex;
55 private final List<T> parameters;
56 private final Lazy<S> bindable;
57
58 private int dynamicProjectionIndex;
59
60
65 public Parameters(Method method) {
66
67 Assert.notNull(method, "Method must not be null!");
68
69 int parameterCount = method.getParameterCount();
70
71 this.parameters = new ArrayList<>(parameterCount);
72 this.dynamicProjectionIndex = -1;
73
74 int pageableIndex = -1;
75 int sortIndex = -1;
76
77 for (int i = 0; i < parameterCount; i++) {
78
79 MethodParameter methodParameter = new MethodParameter(method, i);
80 methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER);
81
82 T parameter = createParameter(methodParameter);
83
84 if (parameter.isSpecialParameter() && parameter.isNamedParameter()) {
85 throw new IllegalArgumentException(PARAM_ON_SPECIAL);
86 }
87
88 if (parameter.isDynamicProjectionParameter()) {
89 this.dynamicProjectionIndex = parameter.getIndex();
90 }
91
92 if (Pageable.class.isAssignableFrom(parameter.getType())) {
93 pageableIndex = i;
94 }
95
96 if (Sort.class.isAssignableFrom(parameter.getType())) {
97 sortIndex = i;
98 }
99
100 parameters.add(parameter);
101 }
102
103 this.pageableIndex = pageableIndex;
104 this.sortIndex = sortIndex;
105 this.bindable = Lazy.of(this::getBindable);
106
107 assertEitherAllParamAnnotatedOrNone();
108 }
109
110
115 protected Parameters(List<T> originals) {
116
117 this.parameters = new ArrayList<>(originals.size());
118
119 int pageableIndexTemp = -1;
120 int sortIndexTemp = -1;
121 int dynamicProjectionTemp = -1;
122
123 for (int i = 0; i < originals.size(); i++) {
124
125 T original = originals.get(i);
126 this.parameters.add(original);
127
128 pageableIndexTemp = original.isPageable() ? i : -1;
129 sortIndexTemp = original.isSort() ? i : -1;
130 dynamicProjectionTemp = original.isDynamicProjectionParameter() ? i : -1;
131 }
132
133 this.pageableIndex = pageableIndexTemp;
134 this.sortIndex = sortIndexTemp;
135 this.dynamicProjectionIndex = dynamicProjectionTemp;
136 this.bindable = Lazy.of(() -> (S) this);
137 }
138
139 private S getBindable() {
140
141 List<T> bindables = new ArrayList<>();
142
143 for (T candidate : this) {
144
145 if (candidate.isBindable()) {
146 bindables.add(candidate);
147 }
148 }
149
150 return createFrom(bindables);
151 }
152
153
159 protected abstract T createParameter(MethodParameter parameter);
160
161
166 public boolean hasPageableParameter() {
167 return pageableIndex != -1;
168 }
169
170
176 public int getPageableIndex() {
177 return pageableIndex;
178 }
179
180
186 public int getSortIndex() {
187 return sortIndex;
188 }
189
190
195 public boolean hasSortParameter() {
196 return sortIndex != -1;
197 }
198
199
205 public int getDynamicProjectionIndex() {
206 return dynamicProjectionIndex;
207 }
208
209
214 public boolean hasDynamicProjection() {
215 return dynamicProjectionIndex != -1;
216 }
217
218
223 public boolean potentiallySortsDynamically() {
224 return hasSortParameter() || hasPageableParameter();
225 }
226
227
233 public T getParameter(int index) {
234
235 try {
236 return parameters.get(index);
237 } catch (IndexOutOfBoundsException e) {
238 throw new ParameterOutOfBoundsException(
239 "Invalid parameter index! You seem to have declared too little query method parameters!", e);
240 }
241 }
242
243
249 public boolean hasParameterAt(int position) {
250
251 try {
252 return null != getParameter(position);
253 } catch (ParameterOutOfBoundsException e) {
254 return false;
255 }
256 }
257
258
263 public boolean hasSpecialParameter() {
264 return hasSortParameter() || hasPageableParameter();
265 }
266
267
272 public int getNumberOfParameters() {
273 return parameters.size();
274 }
275
276
283 public S getBindableParameters() {
284 return this.bindable.get();
285 }
286
287 protected abstract S createFrom(List<T> parameters);
288
289
297 public T getBindableParameter(int bindableIndex) {
298 return getBindableParameters().getParameter(bindableIndex);
299 }
300
301
307 private void assertEitherAllParamAnnotatedOrNone() {
308
309 boolean nameFound = false;
310 int index = 0;
311
312 for (T parameter : this.getBindableParameters()) {
313
314 if (parameter.isNamedParameter()) {
315 Assert.isTrue(nameFound || index == 0, ALL_OR_NOTHING);
316 nameFound = true;
317 } else {
318 Assert.isTrue(!nameFound, ALL_OR_NOTHING);
319 }
320
321 index++;
322 }
323 }
324
325
331 public static boolean isBindable(Class<?> type) {
332 return !TYPES.contains(type);
333 }
334
335
339 public Iterator<T> iterator() {
340 return parameters.iterator();
341 }
342 }
343