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 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 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 OPTION_I = 0x01;
99 OPTION_L = 0x02;
100 OPTION_M = 0x04;
101 OPTION_S = 0x08;
102 OPTION_U = 0x10;
103 OPTION_X = 0x20;
104 OPTION_MASK = 0x3F;
105
106 CASE_INSENSITIVE = OPTION_I;
107 LOCALE_DEPENDENT = OPTION_L;
108 MULTILINE = OPTION_M;
109 DOT_ALL = OPTION_S;
110 UNICODE = OPTION_U;
111 VERBOSE = OPTION_X;
112
113 final String[] options = new String[OPTION_MASK + 1];
114
115 final StringBuilder builder = new StringBuilder();
116 for (int i = 0; i < (OPTION_MASK + 1); ++i) {
117 builder.setLength(0);
118
119 // Options must be in alphabetic order.
120 if ((i & OPTION_I) == OPTION_I) {
121 builder.append('i');
122 }
123 if ((i & OPTION_L) == OPTION_L) {
124 builder.append('l');
125 }
126 if ((i & OPTION_M) == OPTION_M) {
127 builder.append('m');
128 }
129 if ((i & OPTION_S) == OPTION_S) {
130 builder.append('s');
131 }
132 if ((i & OPTION_U) == OPTION_U) {
133 builder.append('u');
134 }
135 if ((i & OPTION_X) == OPTION_X) {
136 builder.append('x');
137 }
138 options[i] = builder.toString();
139 }
140
141 OPTIONS = options;
142
143 // New in Java7
144 PATTERN_UNICODE = 0x100;
145 }
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 int optInt = 0;
172
173 if (pattern != null) {
174 final int flags = pattern.flags();
175 if ((flags & Pattern.CASE_INSENSITIVE) == Pattern.CASE_INSENSITIVE) {
176 optInt |= CASE_INSENSITIVE;
177 }
178 if ((flags & Pattern.MULTILINE) == Pattern.MULTILINE) {
179 optInt |= MULTILINE;
180 }
181 if ((flags & Pattern.DOTALL) == Pattern.DOTALL) {
182 optInt |= DOT_ALL;
183 }
184 if ((flags & PATTERN_UNICODE) == PATTERN_UNICODE) {
185 optInt |= UNICODE;
186 }
187 }
188
189 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 int optInt = 0;
201
202 if (options != null) {
203 for (final char c : options.toCharArray()) {
204 if ((c == 'i') || (c == 'I')) {
205 optInt |= OPTION_I;
206 }
207 else if ((c == 'l') || (c == 'L')) {
208 optInt |= OPTION_L;
209 }
210 else if ((c == 'm') || (c == 'M')) {
211 optInt |= OPTION_M;
212 }
213 else if ((c == 's') || (c == 'S')) {
214 optInt |= OPTION_S;
215 }
216 else if ((c == 'u') || (c == 'U')) {
217 optInt |= OPTION_U;
218 }
219 else if ((c == 'x') || (c == 'X')) {
220 optInt |= OPTION_X;
221 }
222 else {
223 throw new IllegalArgumentException(
224 "Invalid regular expression option '" + c
225 + "' in options '" + options + "'.");
226 }
227 }
228 }
229
230 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 long result = 4; // type (1) + name null byte (1) +
248 // pattern null byte (1) + options null byte (1).
249 result += StringEncoder.utf8Size(name);
250 result += StringEncoder.utf8Size(pattern);
251 result += OPTIONS[options & OPTION_MASK].length(); // ASCII
252
253 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 this(name, (pattern != null) ? pattern.pattern() : null,
274 optionsAsInt(pattern));
275 }
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 super(name, computeSize(name, pattern, options));
292
293 assertNotNull(pattern,
294 "Regular Expression element's pattern cannot be null.");
295
296 myPattern = pattern;
297 myOptions = options;
298 }
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 super(name, size);
320
321 assertNotNull(pattern,
322 "Regular Expression element's pattern cannot be null.");
323
324 myPattern = pattern;
325 myOptions = options;
326 }
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 this(name, pattern, optionsAsInt(options));
343 }
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 this(name, pattern, optionsAsInt(options), size);
365 }
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 visitor.visitRegularExpression(getName(), getPattern(),
376 OPTIONS[getOptions() & OPTION_MASK]);
377 }
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 int result = super.compareTo(otherElement);
389
390 if (result == 0) {
391 final RegularExpressionElement other = (RegularExpressionElement) otherElement;
392
393 result = myPattern.compareTo(other.myPattern);
394 if (result == 0) {
395 result = compare(myOptions, other.myOptions);
396 }
397 }
398
399 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 boolean result = false;
414 if (this == object) {
415 result = true;
416 }
417 else if ((object != null) && (getClass() == object.getClass())) {
418 final RegularExpressionElement other = (RegularExpressionElement) object;
419
420 result = (myOptions == other.myOptions) && super.equals(object)
421 && nullSafeEquals(myPattern, other.myPattern);
422 }
423 return result;
424 }
425
426 /**
427 * Returns the regular expression options.
428 *
429 * @return The regular expression options.
430 */
431 public int getOptions() {
432 return myOptions;
433 }
434
435 /**
436 * Returns the regular expression pattern.
437 *
438 * @return The regular expression pattern.
439 */
440 public String getPattern() {
441 return myPattern;
442 }
443
444 /**
445 * {@inheritDoc}
446 */
447 @Override
448 public ElementType getType() {
449 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 int options = 0;
462 if ((myOptions & CASE_INSENSITIVE) == CASE_INSENSITIVE) {
463 options |= Pattern.CASE_INSENSITIVE;
464 }
465 if ((myOptions & MULTILINE) == MULTILINE) {
466 options |= Pattern.MULTILINE;
467 }
468 if ((myOptions & DOT_ALL) == DOT_ALL) {
469 options |= Pattern.DOTALL;
470 }
471 if ((myOptions & UNICODE) == UNICODE) {
472 options |= PATTERN_UNICODE;
473 }
474
475 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 int result = 1;
486 result = (31 * result) + super.hashCode();
487 result = (31 * result)
488 + ((myPattern != null) ? myPattern.hashCode() : 3);
489 result = (31 * result) + myOptions;
490 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 if (getName().equals(name)) {
502 return this;
503 }
504 return new RegularExpressionElement(name, myPattern, myOptions);
505 }
506 }