Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
RegularExpressionElement |
|
| 3.0588235294117645;3.059 |
1 | /* | |
2 | * #%L | |
3 | * RegularExpressionElement.java - mongodb-async-driver - Allanbank Consulting, Inc. | |
4 | * %% | |
5 | * Copyright (C) 2011 - 2014 Allanbank Consulting, Inc. | |
6 | * %% | |
7 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
8 | * you may not use this file except in compliance with the License. | |
9 | * You may obtain a copy of the License at | |
10 | * | |
11 | * http://www.apache.org/licenses/LICENSE-2.0 | |
12 | * | |
13 | * Unless required by applicable law or agreed to in writing, software | |
14 | * distributed under the License is distributed on an "AS IS" BASIS, | |
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
16 | * See the License for the specific language governing permissions and | |
17 | * limitations under the License. | |
18 | * #L% | |
19 | */ | |
20 | package com.allanbank.mongodb.bson.element; | |
21 | ||
22 | import static com.allanbank.mongodb.util.Assertions.assertNotNull; | |
23 | ||
24 | import java.util.regex.Pattern; | |
25 | ||
26 | import com.allanbank.mongodb.bson.Element; | |
27 | import com.allanbank.mongodb.bson.ElementType; | |
28 | import com.allanbank.mongodb.bson.Visitor; | |
29 | import com.allanbank.mongodb.bson.io.StringEncoder; | |
30 | ||
31 | /** | |
32 | * A wrapper for a BSON regular expression. | |
33 | * | |
34 | * @api.yes This class is part of the driver's API. Public and protected members | |
35 | * will be deprecated for at least 1 non-bugfix release (version | |
36 | * numbers are <major>.<minor>.<bugfix>) before being | |
37 | * removed or modified. | |
38 | * @copyright 2011-2013, Allanbank Consulting, Inc., All Rights Reserved | |
39 | */ | |
40 | 62 | public class RegularExpressionElement extends AbstractElement { |
41 | ||
42 | /** Option for case insensitive matching. */ | |
43 | public static final int CASE_INSENSITIVE; | |
44 | ||
45 | /** Option for dotall mode ('.' matches everything). */ | |
46 | public static final int DOT_ALL; | |
47 | ||
48 | /** Option to make \w, \W, etc. locale dependent. */ | |
49 | public static final int LOCALE_DEPENDENT; | |
50 | ||
51 | /** Option for multiline matching. */ | |
52 | public static final int MULTILINE; | |
53 | ||
54 | /** Option for case insensitive matching. */ | |
55 | public static final int OPTION_I; | |
56 | ||
57 | /** Option to make \w, \W, etc. locale dependent. */ | |
58 | public static final int OPTION_L; | |
59 | ||
60 | /** Option for multiline matching. */ | |
61 | public static final int OPTION_M; | |
62 | ||
63 | /** Option for verbose mode. */ | |
64 | public static final int OPTION_MASK; | |
65 | ||
66 | /** Option for dotall mode ('.' matches everything). */ | |
67 | public static final int OPTION_S; | |
68 | ||
69 | /** Option to make \w, \W, etc. match unicode. */ | |
70 | public static final int OPTION_U; | |
71 | ||
72 | /** Option for verbose mode. */ | |
73 | public static final int OPTION_X; | |
74 | ||
75 | /** The BSON type for a string. */ | |
76 | 1 | public static final ElementType TYPE = ElementType.REGEX; |
77 | ||
78 | /** Option to make \w, \W, etc. match unicode. */ | |
79 | public static final int UNICODE; | |
80 | ||
81 | /** Option for verbose mode. */ | |
82 | public static final int VERBOSE; | |
83 | ||
84 | /** | |
85 | * Option to make \w, \W, etc. match unicode from the pattern class. Added | |
86 | * in Java7 | |
87 | */ | |
88 | protected static final int PATTERN_UNICODE; | |
89 | ||
90 | /** The options for each possible bit field. */ | |
91 | private static final String[] OPTIONS; | |
92 | ||
93 | /** Serialization version for the class. */ | |
94 | private static final long serialVersionUID = 7842839168833403380L; | |
95 | ||
96 | static { | |
97 | ||
98 | 1 | OPTION_I = 0x01; |
99 | 1 | OPTION_L = 0x02; |
100 | 1 | OPTION_M = 0x04; |
101 | 1 | OPTION_S = 0x08; |
102 | 1 | OPTION_U = 0x10; |
103 | 1 | OPTION_X = 0x20; |
104 | 1 | OPTION_MASK = 0x3F; |
105 | ||
106 | 1 | CASE_INSENSITIVE = OPTION_I; |
107 | 1 | LOCALE_DEPENDENT = OPTION_L; |
108 | 1 | MULTILINE = OPTION_M; |
109 | 1 | DOT_ALL = OPTION_S; |
110 | 1 | UNICODE = OPTION_U; |
111 | 1 | VERBOSE = OPTION_X; |
112 | ||
113 | 1 | final String[] options = new String[OPTION_MASK + 1]; |
114 | ||
115 | 1 | final StringBuilder builder = new StringBuilder(); |
116 | 65 | for (int i = 0; i < (OPTION_MASK + 1); ++i) { |
117 | 64 | builder.setLength(0); |
118 | ||
119 | // Options must be in alphabetic order. | |
120 | 64 | if ((i & OPTION_I) == OPTION_I) { |
121 | 32 | builder.append('i'); |
122 | } | |
123 | 64 | if ((i & OPTION_L) == OPTION_L) { |
124 | 32 | builder.append('l'); |
125 | } | |
126 | 64 | if ((i & OPTION_M) == OPTION_M) { |
127 | 32 | builder.append('m'); |
128 | } | |
129 | 64 | if ((i & OPTION_S) == OPTION_S) { |
130 | 32 | builder.append('s'); |
131 | } | |
132 | 64 | if ((i & OPTION_U) == OPTION_U) { |
133 | 32 | builder.append('u'); |
134 | } | |
135 | 64 | if ((i & OPTION_X) == OPTION_X) { |
136 | 32 | builder.append('x'); |
137 | } | |
138 | 64 | options[i] = builder.toString(); |
139 | } | |
140 | ||
141 | 1 | OPTIONS = options; |
142 | ||
143 | // New in Java7 | |
144 | 1 | PATTERN_UNICODE = 0x100; |
145 | 1 | } |
146 | ||
147 | /** | |
148 | * Converts the {@link Pattern#flags() pattern flags} into a options value. | |
149 | * <p> | |
150 | * Note that the {@link #VERBOSE} and {@link #LOCALE_DEPENDENT} do not have | |
151 | * {@link Pattern} equivalent flags. | |
152 | * </p> | |
153 | * <p> | |
154 | * <blockquote> | |
155 | * | |
156 | * <pre> | |
157 | * {@link Pattern#CASE_INSENSITIVE} ==> {@link #CASE_INSENSITIVE} | |
158 | * {@link Pattern#MULTILINE} ==> {@link #MULTILINE} | |
159 | * {@link Pattern#DOTALL} ==> {@link #DOT_ALL} | |
160 | * {@link Pattern#UNICODE_CHARACTER_CLASS} ==> {@link #UNICODE} | |
161 | * </pre> | |
162 | * | |
163 | * </blockquote> | |
164 | * | |
165 | * @param pattern | |
166 | * The pattern to extract the options from. | |
167 | * @return The options integer value. | |
168 | */ | |
169 | @SuppressWarnings("javadoc") | |
170 | protected static int optionsAsInt(final Pattern pattern) { | |
171 | 53 | int optInt = 0; |
172 | ||
173 | 53 | if (pattern != null) { |
174 | 51 | final int flags = pattern.flags(); |
175 | 51 | if ((flags & Pattern.CASE_INSENSITIVE) == Pattern.CASE_INSENSITIVE) { |
176 | 2 | optInt |= CASE_INSENSITIVE; |
177 | } | |
178 | 51 | if ((flags & Pattern.MULTILINE) == Pattern.MULTILINE) { |
179 | 2 | optInt |= MULTILINE; |
180 | } | |
181 | 51 | if ((flags & Pattern.DOTALL) == Pattern.DOTALL) { |
182 | 2 | optInt |= DOT_ALL; |
183 | } | |
184 | 51 | if ((flags & PATTERN_UNICODE) == PATTERN_UNICODE) { |
185 | 2 | optInt |= UNICODE; |
186 | } | |
187 | } | |
188 | ||
189 | 53 | return optInt; |
190 | } | |
191 | ||
192 | /** | |
193 | * Converts the options string into a options value. | |
194 | * | |
195 | * @param options | |
196 | * The possibly non-normalized options string. | |
197 | * @return The options integer value. | |
198 | */ | |
199 | protected static int optionsAsInt(final String options) { | |
200 | 65735 | int optInt = 0; |
201 | ||
202 | 65735 | if (options != null) { |
203 | 66045 | for (final char c : options.toCharArray()) { |
204 | 65884 | if ((c == 'i') || (c == 'I')) { |
205 | 111 | optInt |= OPTION_I; |
206 | } | |
207 | 65773 | else if ((c == 'l') || (c == 'L')) { |
208 | 50 | optInt |= OPTION_L; |
209 | } | |
210 | 65723 | else if ((c == 'm') || (c == 'M')) { |
211 | 50 | optInt |= OPTION_M; |
212 | } | |
213 | 65673 | else if ((c == 's') || (c == 'S')) { |
214 | 50 | optInt |= OPTION_S; |
215 | } | |
216 | 65623 | else if ((c == 'u') || (c == 'U')) { |
217 | 50 | optInt |= OPTION_U; |
218 | } | |
219 | 65573 | else if ((c == 'x') || (c == 'X')) { |
220 | 50 | optInt |= OPTION_X; |
221 | } | |
222 | else { | |
223 | 65523 | throw new IllegalArgumentException( |
224 | "Invalid regular expression option '" + c | |
225 | + "' in options '" + options + "'."); | |
226 | } | |
227 | } | |
228 | } | |
229 | ||
230 | 212 | return optInt; |
231 | } | |
232 | ||
233 | /** | |
234 | * Computes and returns the number of bytes that are used to encode the | |
235 | * element. | |
236 | * | |
237 | * @param name | |
238 | * The name for the element. | |
239 | * @param pattern | |
240 | * The BSON regular expression pattern. | |
241 | * @param options | |
242 | * The BSON regular expression options. | |
243 | * @return The size of the element when encoded in bytes. | |
244 | */ | |
245 | private static long computeSize(final String name, final String pattern, | |
246 | final int options) { | |
247 | 309 | long result = 4; // type (1) + name null byte (1) + |
248 | // pattern null byte (1) + options null byte (1). | |
249 | 309 | result += StringEncoder.utf8Size(name); |
250 | 309 | result += StringEncoder.utf8Size(pattern); |
251 | 309 | result += OPTIONS[options & OPTION_MASK].length(); // ASCII |
252 | ||
253 | 309 | return result; |
254 | } | |
255 | ||
256 | /** The BSON regular expression options. */ | |
257 | private final int myOptions; | |
258 | ||
259 | /** The BSON regular expression pattern. */ | |
260 | private final String myPattern; | |
261 | ||
262 | /** | |
263 | * Constructs a new {@link RegularExpressionElement}. | |
264 | * | |
265 | * @param name | |
266 | * The name for the BSON string. | |
267 | * @param pattern | |
268 | * The regular expression {@link Pattern}. | |
269 | * @throws IllegalArgumentException | |
270 | * If the {@code name} or {@code pattern} is <code>null</code>. | |
271 | */ | |
272 | public RegularExpressionElement(final String name, final Pattern pattern) { | |
273 | 52 | this(name, (pattern != null) ? pattern.pattern() : null, |
274 | optionsAsInt(pattern)); | |
275 | 49 | } |
276 | ||
277 | /** | |
278 | * Constructs a new {@link RegularExpressionElement}. | |
279 | * | |
280 | * @param name | |
281 | * The name for the BSON string. | |
282 | * @param pattern | |
283 | * The BSON regular expression pattern. | |
284 | * @param options | |
285 | * The BSON regular expression options. | |
286 | * @throws IllegalArgumentException | |
287 | * If the {@code name} or {@code pattern} is <code>null</code>. | |
288 | */ | |
289 | public RegularExpressionElement(final String name, final String pattern, | |
290 | final int options) { | |
291 | 309 | super(name, computeSize(name, pattern, options)); |
292 | ||
293 | 307 | assertNotNull(pattern, |
294 | "Regular Expression element's pattern cannot be null."); | |
295 | ||
296 | 305 | myPattern = pattern; |
297 | 305 | myOptions = options; |
298 | 305 | } |
299 | ||
300 | /** | |
301 | * Constructs a new {@link RegularExpressionElement}. | |
302 | * | |
303 | * @param name | |
304 | * The name for the BSON string. | |
305 | * @param pattern | |
306 | * The BSON regular expression pattern. | |
307 | * @param options | |
308 | * The BSON regular expression options. | |
309 | * @param size | |
310 | * The size of the element when encoded in bytes. If not known | |
311 | * then use the | |
312 | * {@link RegularExpressionElement#RegularExpressionElement(String, String, int)} | |
313 | * constructor instead. | |
314 | * @throws IllegalArgumentException | |
315 | * If the {@code name} or {@code pattern} is <code>null</code>. | |
316 | */ | |
317 | public RegularExpressionElement(final String name, final String pattern, | |
318 | final int options, final long size) { | |
319 | 14 | super(name, size); |
320 | ||
321 | 14 | assertNotNull(pattern, |
322 | "Regular Expression element's pattern cannot be null."); | |
323 | ||
324 | 14 | myPattern = pattern; |
325 | 14 | myOptions = options; |
326 | 14 | } |
327 | ||
328 | /** | |
329 | * Constructs a new {@link RegularExpressionElement}. | |
330 | * | |
331 | * @param name | |
332 | * The name for the BSON string. | |
333 | * @param pattern | |
334 | * The BSON regular expression pattern. | |
335 | * @param options | |
336 | * The BSON regular expression options. | |
337 | * @throws IllegalArgumentException | |
338 | * If the {@code name} or {@code pattern} is <code>null</code>. | |
339 | */ | |
340 | public RegularExpressionElement(final String name, final String pattern, | |
341 | final String options) { | |
342 | 65721 | this(name, pattern, optionsAsInt(options)); |
343 | 197 | } |
344 | ||
345 | /** | |
346 | * Constructs a new {@link RegularExpressionElement}. | |
347 | * | |
348 | * @param name | |
349 | * The name for the BSON string. | |
350 | * @param pattern | |
351 | * The BSON regular expression pattern. | |
352 | * @param options | |
353 | * The BSON regular expression options. | |
354 | * @param size | |
355 | * The size of the element when encoded in bytes. If not known | |
356 | * then use the | |
357 | * {@link RegularExpressionElement#RegularExpressionElement(String, String, String)} | |
358 | * constructor instead. | |
359 | * @throws IllegalArgumentException | |
360 | * If the {@code name} or {@code pattern} is <code>null</code>. | |
361 | */ | |
362 | public RegularExpressionElement(final String name, final String pattern, | |
363 | final String options, final long size) { | |
364 | 14 | this(name, pattern, optionsAsInt(options), size); |
365 | 14 | } |
366 | ||
367 | /** | |
368 | * Accepts the visitor and calls the {@link Visitor#visitRegularExpression} | |
369 | * method. | |
370 | * | |
371 | * @see Element#accept(Visitor) | |
372 | */ | |
373 | @Override | |
374 | public void accept(final Visitor visitor) { | |
375 | 41 | visitor.visitRegularExpression(getName(), getPattern(), |
376 | OPTIONS[getOptions() & OPTION_MASK]); | |
377 | 41 | } |
378 | ||
379 | /** | |
380 | * {@inheritDoc} | |
381 | * <p> | |
382 | * Overridden to compare the expressions (as strings) if the base class | |
383 | * comparison is equals. | |
384 | * </p> | |
385 | */ | |
386 | @Override | |
387 | public int compareTo(final Element otherElement) { | |
388 | 8 | int result = super.compareTo(otherElement); |
389 | ||
390 | 8 | if (result == 0) { |
391 | 5 | final RegularExpressionElement other = (RegularExpressionElement) otherElement; |
392 | ||
393 | 5 | result = myPattern.compareTo(other.myPattern); |
394 | 5 | if (result == 0) { |
395 | 3 | result = compare(myOptions, other.myOptions); |
396 | } | |
397 | } | |
398 | ||
399 | 8 | return result; |
400 | } | |
401 | ||
402 | /** | |
403 | * Determines if the passed object is of this same type as this object and | |
404 | * if so that its fields are equal. | |
405 | * | |
406 | * @param object | |
407 | * The object to compare to. | |
408 | * | |
409 | * @see java.lang.Object#equals(java.lang.Object) | |
410 | */ | |
411 | @Override | |
412 | public boolean equals(final Object object) { | |
413 | 5514 | boolean result = false; |
414 | 5514 | if (this == object) { |
415 | 115 | result = true; |
416 | } | |
417 | 5399 | else if ((object != null) && (getClass() == object.getClass())) { |
418 | 5092 | final RegularExpressionElement other = (RegularExpressionElement) object; |
419 | ||
420 | 5092 | result = (myOptions == other.myOptions) && super.equals(object) |
421 | && nullSafeEquals(myPattern, other.myPattern); | |
422 | } | |
423 | 5514 | return result; |
424 | } | |
425 | ||
426 | /** | |
427 | * Returns the regular expression options. | |
428 | * | |
429 | * @return The regular expression options. | |
430 | */ | |
431 | public int getOptions() { | |
432 | 50 | return myOptions; |
433 | } | |
434 | ||
435 | /** | |
436 | * Returns the regular expression pattern. | |
437 | * | |
438 | * @return The regular expression pattern. | |
439 | */ | |
440 | public String getPattern() { | |
441 | 50 | return myPattern; |
442 | } | |
443 | ||
444 | /** | |
445 | * {@inheritDoc} | |
446 | */ | |
447 | @Override | |
448 | public ElementType getType() { | |
449 | 29 | return TYPE; |
450 | } | |
451 | ||
452 | /** | |
453 | * {@inheritDoc} | |
454 | * <p> | |
455 | * Returns the {@link Pattern}. | |
456 | * </p> | |
457 | */ | |
458 | @Override | |
459 | public Pattern getValueAsObject() { | |
460 | ||
461 | 4 | int options = 0; |
462 | 4 | if ((myOptions & CASE_INSENSITIVE) == CASE_INSENSITIVE) { |
463 | 2 | options |= Pattern.CASE_INSENSITIVE; |
464 | } | |
465 | 4 | if ((myOptions & MULTILINE) == MULTILINE) { |
466 | 2 | options |= Pattern.MULTILINE; |
467 | } | |
468 | 4 | if ((myOptions & DOT_ALL) == DOT_ALL) { |
469 | 2 | options |= Pattern.DOTALL; |
470 | } | |
471 | 4 | if ((myOptions & UNICODE) == UNICODE) { |
472 | 2 | options |= PATTERN_UNICODE; |
473 | } | |
474 | ||
475 | 4 | return Pattern.compile(myPattern, options); |
476 | } | |
477 | ||
478 | /** | |
479 | * Computes a reasonable hash code. | |
480 | * | |
481 | * @return The hash code value. | |
482 | */ | |
483 | @Override | |
484 | public int hashCode() { | |
485 | 10225 | int result = 1; |
486 | 10225 | result = (31 * result) + super.hashCode(); |
487 | 10225 | result = (31 * result) |
488 | + ((myPattern != null) ? myPattern.hashCode() : 3); | |
489 | 10225 | result = (31 * result) + myOptions; |
490 | 10225 | return result; |
491 | } | |
492 | ||
493 | /** | |
494 | * {@inheritDoc} | |
495 | * <p> | |
496 | * Returns a new {@link RegularExpressionElement}. | |
497 | * </p> | |
498 | */ | |
499 | @Override | |
500 | public RegularExpressionElement withName(final String name) { | |
501 | 64 | if (getName().equals(name)) { |
502 | 61 | return this; |
503 | } | |
504 | 3 | return new RegularExpressionElement(name, myPattern, myOptions); |
505 | } | |
506 | } |