Coverage Report - com.allanbank.mongodb.bson.element.RegularExpressionElement
 
Classes in this File Line Coverage Branch Coverage Complexity
RegularExpressionElement
100%
122/122
98%
67/68
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  
 }