View Javadoc
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 }