Coverage Report - com.allanbank.mongodb.bson.element.ArrayElement
 
Classes in this File Line Coverage Branch Coverage Complexity
ArrayElement
98%
105/107
95%
61/64
3.533
 
 1  
 /*
 2  
  * #%L
 3  
  * ArrayElement.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 java.util.ArrayList;
 23  
 import java.util.Arrays;
 24  
 import java.util.Collections;
 25  
 import java.util.Iterator;
 26  
 import java.util.List;
 27  
 import java.util.regex.Pattern;
 28  
 import java.util.regex.PatternSyntaxException;
 29  
 
 30  
 import com.allanbank.mongodb.bson.Element;
 31  
 import com.allanbank.mongodb.bson.ElementType;
 32  
 import com.allanbank.mongodb.bson.Visitor;
 33  
 import com.allanbank.mongodb.bson.io.StringEncoder;
 34  
 import com.allanbank.mongodb.util.PatternUtils;
 35  
 
 36  
 /**
 37  
  * A wrapper for a BSON array.
 38  
  * 
 39  
  * @api.yes This class is part of the driver's API. Public and protected members
 40  
  *          will be deprecated for at least 1 non-bugfix release (version
 41  
  *          numbers are <major>.<minor>.<bugfix>) before being
 42  
  *          removed or modified.
 43  
  * @copyright 2011-2013, Allanbank Consulting, Inc., All Rights Reserved
 44  
  */
 45  1515
 public class ArrayElement extends AbstractElement {
 46  
 
 47  
     /** The BSON type for an array. */
 48  1
     public static final ElementType TYPE = ElementType.ARRAY;
 49  
 
 50  
     /** A cache of the names for the elements at an index to be used. */
 51  
     private static final String[] ourIndexes;
 52  
 
 53  
     /** Serialization version for the class. */
 54  
     private static final long serialVersionUID = -7363294574214059703L;
 55  
     static {
 56  1
         ourIndexes = new String[1000];
 57  
 
 58  1001
         for (int i = 0; i < ourIndexes.length; ++i) {
 59  1000
             ourIndexes[i] = Integer.toString(i).intern();
 60  
         }
 61  1
     }
 62  
 
 63  
     /**
 64  
      * Similar to the caching of Integer object values for a range we cache the
 65  
      * index names for an array's first 256 positions.
 66  
      * 
 67  
      * @param index
 68  
      *            The index for the array element.
 69  
      * @return The name of the value at that index.
 70  
      */
 71  
     public static final String nameFor(final int index) {
 72  1813554
         if ((0 <= index) && (index < ourIndexes.length)) {
 73  1615552
             return ourIndexes[index];
 74  
         }
 75  198002
         return Integer.toString(index);
 76  
     }
 77  
 
 78  
     /**
 79  
      * Computes and returns the number of bytes that are used to encode the
 80  
      * element.
 81  
      * 
 82  
      * @param name
 83  
      *            The name for the BSON array.
 84  
      * @param entries
 85  
      *            The entries in the array.
 86  
      * @return The size of the element when encoded in bytes.
 87  
      */
 88  
     private static long computeSize(final String name,
 89  
             final List<Element> entries) {
 90  4537
         long result = 7; // type (1) + name null byte (1) + int length (4) +
 91  
         // elements null byte (1).
 92  4537
         result += StringEncoder.utf8Size(name);
 93  4537
         if ((entries != null) && !entries.isEmpty()) {
 94  3586
             for (final Element element : entries) {
 95  907125
                 result += element.size();
 96  907125
             }
 97  
         }
 98  
 
 99  4537
         return result;
 100  
     }
 101  
 
 102  
     /**
 103  
      * The entries in the array. The name attribute will be ignored when
 104  
      * encoding the elements.
 105  
      */
 106  
     private final List<Element> myEntries;
 107  
 
 108  
     /**
 109  
      * Constructs a new {@link ArrayElement}.
 110  
      * 
 111  
      * @param name
 112  
      *            The name for the BSON array.
 113  
      * @param entries
 114  
      *            The entries in the array.
 115  
      * @throws IllegalArgumentException
 116  
      *             If the {@code name} is <code>null</code>.
 117  
      */
 118  
     public ArrayElement(final String name, final Element... entries)
 119  
             throws IllegalArgumentException {
 120  196
         this(name, Arrays.asList(entries));
 121  195
     }
 122  
 
 123  
     /**
 124  
      * Constructs a new {@link ArrayElement}.
 125  
      * 
 126  
      * @param name
 127  
      *            The name for the BSON array.
 128  
      * @param entries
 129  
      *            The entries in the array.
 130  
      * @throws IllegalArgumentException
 131  
      *             If the {@code name} is <code>null</code>.
 132  
      */
 133  
     public ArrayElement(final String name, final List<Element> entries)
 134  
             throws IllegalArgumentException {
 135  4537
         this(name, entries, computeSize(name, entries));
 136  4536
     }
 137  
 
 138  
     /**
 139  
      * Constructs a new {@link ArrayElement}.
 140  
      * 
 141  
      * @param name
 142  
      *            The name for the BSON array.
 143  
      * @param entries
 144  
      *            The entries in the array.
 145  
      * @param size
 146  
      *            The size of the element when encoded in bytes. If not known
 147  
      *            then use the {@link ArrayElement#ArrayElement(String, List)}
 148  
      *            constructor instead.
 149  
      * @throws IllegalArgumentException
 150  
      *             If the {@code name} is <code>null</code>.
 151  
      */
 152  
     public ArrayElement(final String name, final List<Element> entries,
 153  
             final long size) throws IllegalArgumentException {
 154  4656
         super(name, size);
 155  
 
 156  4655
         if ((entries != null) && !entries.isEmpty()) {
 157  
             // The names of the elements have to be a specific value.
 158  3704
             final int length = entries.size();
 159  3704
             final List<Element> elements = new ArrayList<Element>(length);
 160  3704
             int index = 0;
 161  3704
             for (final Element element : entries) {
 162  907509
                 final Element withCorrectName = element
 163  
                         .withName(nameFor(index));
 164  907509
                 elements.add(withCorrectName);
 165  907509
                 index += 1;
 166  907509
             }
 167  
 
 168  3704
             myEntries = Collections.unmodifiableList(elements);
 169  3704
         }
 170  
         else {
 171  951
             myEntries = Collections.emptyList();
 172  
         }
 173  4655
     }
 174  
 
 175  
     /**
 176  
      * Accepts the visitor and calls the
 177  
      * {@link Visitor#visitArray(String, List)} method.
 178  
      * 
 179  
      * @see Element#accept(Visitor)
 180  
      */
 181  
     @Override
 182  
     public void accept(final Visitor visitor) {
 183  2435
         if (visitor instanceof SizeAwareVisitor) {
 184  139
             ((SizeAwareVisitor) visitor).visitArray(getName(), getEntries(),
 185  
                     size());
 186  
         }
 187  
         else {
 188  2296
             visitor.visitArray(getName(), getEntries());
 189  
         }
 190  2435
     }
 191  
 
 192  
     /**
 193  
      * {@inheritDoc}
 194  
      * <p>
 195  
      * Overridden to compare the elements of the array if the base class
 196  
      * comparison is equals.
 197  
      * </p>
 198  
      */
 199  
     @Override
 200  
     public int compareTo(final Element otherElement) {
 201  8
         int result = super.compareTo(otherElement);
 202  
 
 203  8
         if (result == 0) {
 204  5
             final ArrayElement other = (ArrayElement) otherElement;
 205  5
             final int length = Math.min(myEntries.size(),
 206  
                     other.myEntries.size());
 207  8
             for (int i = 0; i < length; ++i) {
 208  5
                 result = myEntries.get(i).compareTo(other.myEntries.get(i));
 209  5
                 if (result != 0) {
 210  2
                     return result;
 211  
                 }
 212  
             }
 213  
 
 214  3
             result = myEntries.size() - other.myEntries.size();
 215  
         }
 216  
 
 217  6
         return result;
 218  
     }
 219  
 
 220  
     /**
 221  
      * Determines if the passed object is of this same type as this object and
 222  
      * if so that its fields are equal.
 223  
      * 
 224  
      * @param object
 225  
      *            The object to compare to.
 226  
      * 
 227  
      * @see java.lang.Object#equals(java.lang.Object)
 228  
      */
 229  
     @Override
 230  
     public boolean equals(final Object object) {
 231  2237
         boolean result = false;
 232  2237
         if (this == object) {
 233  6
             result = true;
 234  
         }
 235  2231
         else if ((object != null) && (getClass() == object.getClass())) {
 236  2215
             final ArrayElement other = (ArrayElement) object;
 237  
 
 238  2215
             result = super.equals(object) && myEntries.equals(other.myEntries);
 239  
         }
 240  2237
         return result;
 241  
     }
 242  
 
 243  
     /**
 244  
      * {@inheritDoc}
 245  
      * <p>
 246  
      * Searches this sub-elements for matching elements on the path and are of
 247  
      * the right type.
 248  
      * </p>
 249  
      * 
 250  
      * @see Element#find
 251  
      */
 252  
     @Override
 253  
     public <E extends Element> List<E> find(final Class<E> clazz,
 254  
             final String... nameRegexs) {
 255  197
         if (0 < nameRegexs.length) {
 256  162
             final List<E> elements = new ArrayList<E>();
 257  162
             final String nameRegex = nameRegexs[0];
 258  162
             final String[] subNameRegexs = Arrays.copyOfRange(nameRegexs, 1,
 259  
                     nameRegexs.length);
 260  
             try {
 261  162
                 final Pattern pattern = PatternUtils.toPattern(nameRegex);
 262  160
                 for (final Element element : myEntries) {
 263  379
                     if (pattern.matcher(element.getName()).matches()) {
 264  378
                         elements.addAll(element.find(clazz, subNameRegexs));
 265  
                     }
 266  379
                 }
 267  
             }
 268  2
             catch (final PatternSyntaxException pse) {
 269  
                 // Assume a non-pattern?
 270  2
                 for (final Element element : myEntries) {
 271  2
                     if (nameRegex.equals(element.getName())) {
 272  0
                         elements.addAll(element.find(clazz, subNameRegexs));
 273  
                     }
 274  2
                 }
 275  160
             }
 276  
 
 277  162
             return elements;
 278  
         }
 279  
 
 280  
         // End of the path -- are we the right type
 281  35
         if (clazz.isAssignableFrom(this.getClass())) {
 282  34
             return Collections.singletonList(clazz.cast(this));
 283  
         }
 284  1
         return Collections.emptyList();
 285  
     }
 286  
 
 287  
     /**
 288  
      * {@inheritDoc}
 289  
      * <p>
 290  
      * Searches this sub-elements for matching elements on the path and are of
 291  
      * the right type.
 292  
      * </p>
 293  
      * 
 294  
      * @see Element#findFirst
 295  
      */
 296  
     @Override
 297  
     public <E extends Element> E findFirst(final Class<E> clazz,
 298  
             final String... nameRegexs) {
 299  20
         E element = null;
 300  20
         if (0 < nameRegexs.length) {
 301  11
             final String nameRegex = nameRegexs[0];
 302  11
             final String[] subNameRegexs = Arrays.copyOfRange(nameRegexs, 1,
 303  
                     nameRegexs.length);
 304  
 
 305  
             try {
 306  11
                 final Pattern pattern = PatternUtils.toPattern(nameRegex);
 307  7
                 final Iterator<Element> iter = myEntries.iterator();
 308  16
                 while (iter.hasNext() && (element == null)) {
 309  9
                     final Element docElement = iter.next();
 310  9
                     if (pattern.matcher(docElement.getName()).matches()) {
 311  6
                         element = docElement.findFirst(clazz, subNameRegexs);
 312  
                     }
 313  9
                 }
 314  
             }
 315  4
             catch (final PatternSyntaxException pse) {
 316  
                 // Assume a non-pattern?
 317  4
                 final Iterator<Element> iter = myEntries.iterator();
 318  10
                 while (iter.hasNext() && (element == null)) {
 319  6
                     final Element docElement = iter.next();
 320  6
                     if (nameRegex.equals(docElement.getName())) {
 321  0
                         element = docElement.findFirst(clazz, subNameRegexs);
 322  
                     }
 323  6
                 }
 324  7
             }
 325  11
         }
 326  
         else {
 327  
             // End of the path -- are we the right type/element?
 328  9
             if (clazz.isAssignableFrom(this.getClass())) {
 329  8
                 element = clazz.cast(this);
 330  
             }
 331  
         }
 332  20
         return element;
 333  
     }
 334  
 
 335  
     /**
 336  
      * Returns the entries in the array. The name attribute will be ignored when
 337  
      * encoding the elements. When decoded the names will be the strings 0, 1,
 338  
      * 2, 3, etc..
 339  
      * 
 340  
      * @return The entries in the array.
 341  
      */
 342  
     public List<Element> getEntries() {
 343  2467
         return myEntries;
 344  
     }
 345  
 
 346  
     /**
 347  
      * {@inheritDoc}
 348  
      */
 349  
     @Override
 350  
     public ElementType getType() {
 351  20
         return TYPE;
 352  
     }
 353  
 
 354  
     /**
 355  
      * {@inheritDoc}
 356  
      * <p>
 357  
      * Returns an Element[].
 358  
      * </p>
 359  
      */
 360  
     @Override
 361  
     public Element[] getValueAsObject() {
 362  4
         return myEntries.toArray(new Element[myEntries.size()]);
 363  
     }
 364  
 
 365  
     /**
 366  
      * Computes a reasonable hash code.
 367  
      * 
 368  
      * @return The hash code value.
 369  
      */
 370  
     @Override
 371  
     public int hashCode() {
 372  2555
         int result = 1;
 373  2555
         result = (31 * result) + super.hashCode();
 374  2555
         result = (31 * result) + myEntries.hashCode();
 375  2555
         return result;
 376  
     }
 377  
 
 378  
     /**
 379  
      * {@inheritDoc}
 380  
      * <p>
 381  
      * Returns a new {@link ArrayElement}.
 382  
      * </p>
 383  
      */
 384  
     @Override
 385  
     public ArrayElement withName(final String name) {
 386  1517
         if (getName().equals(name)) {
 387  1451
             return this;
 388  
         }
 389  66
         return new ArrayElement(name, myEntries);
 390  
     }
 391  
 
 392  
 }