forked from spring-projects/spring-data-commons
/
Part.java
322 lines (253 loc) · 7.32 KB
/
Part.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
/*
* Copyright 2008-2010 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
*
* http://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.query.parser;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.util.StringUtils;
/**
* A single part of a method name that has to be transformed into a query part. The actual transformation is defined by
* a {@link Type} that is determined from inspecting the given part. The query part can then be looked up via
* {@link #getQueryPart()}.
*
* @author Oliver Gierke
*/
public class Part {
private static final Pattern IGNORE_CASE = Pattern.compile("Ignor(ing|e)Case");
private final Property property;
private final Part.Type type;
private IgnoreCaseType ignoreCase = IgnoreCaseType.NEVER;
/**
* Creates a new {@link Part} from the given method name part, the {@link Class} the part originates from and the
* start parameter index.
*
* @param part must not be {@literal null}.
* @param clazz must not be {@l
*/
public Part(String part, Class<?> clazz) {
this(part, clazz, false);
}
/**
* Creates a new {@link Part} from the given method name part, the {@link Class} the part originates from and the
* start parameter index.
*
* @param part must not be {@literal null}.
* @param clazz must not be {@literal null}.
* @param alwaysIgnoreCase
*/
public Part(String part, Class<?> clazz, boolean alwaysIgnoreCase) {
String partToUse = detectAndSetIgnoreCase(part);
if (alwaysIgnoreCase && ignoreCase != IgnoreCaseType.ALWAYS) {
this.ignoreCase = IgnoreCaseType.WHEN_POSSIBLE;
}
this.type = Type.fromProperty(partToUse);
this.property = Property.from(type.extractProperty(partToUse), clazz);
}
private String detectAndSetIgnoreCase(String part) {
Matcher matcher = IGNORE_CASE.matcher(part);
String result = part;
if (matcher.find()) {
ignoreCase = IgnoreCaseType.ALWAYS;
result = part.substring(0, matcher.start()) + part.substring(matcher.end(), part.length());
}
return result;
}
public boolean getParameterRequired() {
return getNumberOfArguments() > 0;
}
/**
* Returns how many method parameters are bound by this part.
*
* @return
*/
public int getNumberOfArguments() {
return type.getNumberOfArguments();
}
/**
* @return the property
*/
public Property getProperty() {
return property;
}
/**
* @return the type
*/
public Part.Type getType() {
return type;
}
/**
* Returns whether the {@link Property} referenced should be matched ignoring case.
*
* @return
*/
public IgnoreCaseType shouldIgnoreCase() {
return ignoreCase;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || !getClass().equals(obj.getClass())) {
return false;
}
Part that = (Part) obj;
return this.property.equals(that.property) && this.type.equals(that.type);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = 37;
result += 17 * property.hashCode();
result += 17 * type.hashCode();
return result;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("%s %s", property.getName(), type);
}
/**
* The type of a method name part. Used to create query parts in various ways.
*
* @author Oliver Gierke
*/
public static enum Type {
BETWEEN(2, "Between"),
IS_NOT_NULL(0, "IsNotNull", "NotNull"),
IS_NULL(0, "IsNull", "Null"),
LESS_THAN("LessThan"),
LESS_THAN_EQUAL("LessThanEqual"),
GREATER_THAN("GreaterThan"),
GREATER_THAN_EQUAL("GreaterThanEqual"),
NOT_LIKE("NotLike"),
LIKE("Like"),
NOT_IN("NotIn"),
IN("In"),
NEAR("Near"),
WITHIN("Within"),
NEGATING_SIMPLE_PROPERTY("Not"),
SIMPLE_PROPERTY;
// Need to list them again explicitly as the order is important
// (esp. for IS_NULL, IS_NOT_NULL)
private static final List<Part.Type> ALL = Arrays.asList(IS_NOT_NULL, IS_NULL, BETWEEN, LESS_THAN, LESS_THAN_EQUAL, GREATER_THAN, GREATER_THAN_EQUAL,
NOT_LIKE, LIKE, NOT_IN, IN, NEAR, WITHIN, NEGATING_SIMPLE_PROPERTY, SIMPLE_PROPERTY);
private List<String> keywords;
private int numberOfArguments;
/**
* Creates a new {@link Type} using the given keyword, number of arguments to be bound and operator. Keyword and
* operator can be {@literal null}.
*
* @param numberOfArguments
* @param keywords
*/
private Type(int numberOfArguments, String... keywords) {
this.numberOfArguments = numberOfArguments;
this.keywords = Arrays.asList(keywords);
}
private Type(String... keywords) {
this(1, keywords);
}
/**
* Returns the {@link Type} of the {@link Part} for the given raw property. This will
* try to detect e.g. keywords contained in the raw property that trigger special query creation. Returns
* {@link #SIMPLE_PROPERTY} by default.
*
* @param rawProperty
* @return
*/
public static Part.Type fromProperty(String rawProperty) {
for (Part.Type type : ALL) {
if (type.supports(rawProperty)) {
return type;
}
}
return SIMPLE_PROPERTY;
}
/**
* Returns whether the the type supports the given raw property. Default implementation checks whether the property
* ends with the registered keyword. Does not support the keyword if the property is a valid field as is.
*
* @param property
* @return
*/
protected boolean supports(String property) {
if (keywords == null) {
return true;
}
for (String keyword : keywords) {
if (property.endsWith(keyword)) {
return true;
}
}
return false;
}
/**
* Returns the number of arguments the property binds. By default this exactly one argument.
*
* @return
*/
public int getNumberOfArguments() {
return numberOfArguments;
}
/**
* Callback method to extract the actual property to be bound from the given part. Strips the keyword from the
* part's end if available.
*
* @param part
* @return
*/
public String extractProperty(String part) {
String candidate = StringUtils.uncapitalize(part);
for (String keyword : keywords) {
if (candidate.endsWith(keyword)) {
return candidate.substring(0, candidate.indexOf(keyword));
}
}
return candidate;
}
}
/**
* The various types of ignore case that are supported.
*
* @author Phillip Webb
*/
public enum IgnoreCaseType {
/**
* Should not ignore the sentence case.
*/
NEVER,
/**
* Should ignore the sentence case, throwing an exception if this is not possible.
*/
ALWAYS,
/**
* Should ignore the sentence case when possible to do so, silently ignoring the option when not possible.
*/
WHEN_POSSIBLE
}
}