View Javadoc
1   /*
2    * #%L
3    * StringElement.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 com.allanbank.mongodb.bson.Element;
25  import com.allanbank.mongodb.bson.ElementType;
26  import com.allanbank.mongodb.bson.Visitor;
27  import com.allanbank.mongodb.bson.io.StringEncoder;
28  
29  /**
30   * A wrapper for a BSON string.
31   * 
32   * @api.yes This class is part of the driver's API. Public and protected members
33   *          will be deprecated for at least 1 non-bugfix release (version
34   *          numbers are <major>.<minor>.<bugfix>) before being
35   *          removed or modified.
36   * @copyright 2011-2013, Allanbank Consulting, Inc., All Rights Reserved
37   */
38  public class StringElement extends AbstractElement {
39  
40      /** The BSON type for a string. */
41      public static final ElementType TYPE = ElementType.STRING;
42  
43      /** Serialization version for the class. */
44      private static final long serialVersionUID = 2279503881395893379L;
45  
46      /**
47       * Performs a comparison of the strings based strictly on the UTF-8 encoding
48       * of the two strings. Normal Java comparisons use a collator.
49       * 
50       * @param lhs
51       *            The left-hand-side of the comparison.
52       * @param rhs
53       *            The right-hand-side of the comparison.
54       * @return A value less than zero if the {@code lhs} is less than the
55       *         {@code rhs}. A value greater than zero if the {@code lhs} is
56       *         greater than the {@code rhs}. Zero if the {@code lhs} is equal to
57       *         the {@code rhs}.
58       */
59      /* package */static int utf8Compare(final String lhs, final String rhs) {
60  
61          final int lhsLength = lhs.length();
62          final int rhsLength = rhs.length();
63  
64          int lhsIndex = 0;
65          int rhsIndex = 0;
66          while ((lhsIndex < lhsLength) && (rhsIndex < rhsLength)) {
67              final int lhsCodePoint = Character.codePointAt(lhs, lhsIndex);
68              final int rhsCodePoint = Character.codePointAt(rhs, rhsIndex);
69  
70              final int compare = compare(lhsCodePoint, rhsCodePoint);
71              if (compare != 0) {
72                  return compare;
73              }
74  
75              // Move to the next character.
76              lhsIndex += Character.charCount(lhsCodePoint);
77              rhsIndex += Character.charCount(rhsCodePoint);
78          }
79  
80          // The shorter string is "less than".
81          return compare(lhsLength, rhsLength);
82      }
83  
84      /**
85       * Computes and returns the number of bytes that are used to encode the
86       * element.
87       * 
88       * @param name
89       *            The name for the element.
90       * @param value
91       *            The BSON string value.
92       * @return The size of the element when encoded in bytes.
93       */
94      private static long computeSize(final String name, final String value) {
95          long result = 7; // type (1) + name null byte (1) +
96          // value length (4) + value null byte (1)
97          result += StringEncoder.utf8Size(name);
98          result += StringEncoder.utf8Size(value);
99  
100         return result;
101     }
102 
103     /** The BSON string value. */
104     private final String myValue;
105 
106     /**
107      * Constructs a new {@link StringElement}.
108      * 
109      * @param name
110      *            The name for the BSON string.
111      * @param value
112      *            The BSON string value.
113      * @throws IllegalArgumentException
114      *             If the {@code name} or {@code value} is <code>null</code>.
115      */
116     public StringElement(final String name, final String value) {
117         this(name, value, computeSize(name, value));
118     }
119 
120     /**
121      * Constructs a new {@link StringElement}.
122      * 
123      * @param name
124      *            The name for the BSON string.
125      * @param value
126      *            The BSON string value.
127      * @param size
128      *            The size of the element when encoded in bytes. If not known
129      *            then use the
130      *            {@link StringElement#StringElement(String, String)}
131      *            constructor instead.
132      * @throws IllegalArgumentException
133      *             If the {@code name} or {@code value} is <code>null</code>.
134      */
135     public StringElement(final String name, final String value, final long size) {
136         super(name, size);
137 
138         assertNotNull(value, "String element's value cannot be null.");
139 
140         myValue = value;
141     }
142 
143     /**
144      * Accepts the visitor and calls the {@link Visitor#visitString} method.
145      * 
146      * @see Element#accept(Visitor)
147      */
148     @Override
149     public void accept(final Visitor visitor) {
150         visitor.visitString(getName(), getValue());
151     }
152 
153     /**
154      * {@inheritDoc}
155      * <p>
156      * Overridden to compare the string values if the base class comparison is
157      * equals.
158      * </p>
159      * <p>
160      * Note that for MongoDB {@link SymbolElement} and {@link StringElement}
161      * will return equal based on the type. Care is taken here to make sure that
162      * the values return the same value regardless of comparison order.
163      * </p>
164      * <p>
165      * Note: Comparison of strings in MongoDB does not use a collator. This
166      * class emulates the MongoDB behavior and orders the string elements based
167      * on the UTF-8 encoding of the strings.
168      * </p>
169      */
170     @Override
171     public int compareTo(final Element otherElement) {
172         int result = super.compareTo(otherElement);
173 
174         if (result == 0) {
175             // Might be a StringElement or SymbolElement.
176             final ElementType otherType = otherElement.getType();
177 
178             if (otherType == ElementType.SYMBOL) {
179                 result = utf8Compare(myValue,
180                         ((SymbolElement) otherElement).getSymbol());
181             }
182             else {
183                 result = utf8Compare(myValue,
184                         ((StringElement) otherElement).getValue());
185             }
186         }
187 
188         return result;
189     }
190 
191     /**
192      * Determines if the passed object is of this same type as this object and
193      * if so that its fields are equal.
194      * 
195      * @param object
196      *            The object to compare to.
197      * 
198      * @see java.lang.Object#equals(java.lang.Object)
199      */
200     @Override
201     public boolean equals(final Object object) {
202         boolean result = false;
203         if (this == object) {
204             result = true;
205         }
206         else if ((object != null) && (getClass() == object.getClass())) {
207             final StringElement other = (StringElement) object;
208 
209             result = super.equals(object)
210                     && nullSafeEquals(myValue, other.myValue);
211         }
212         return result;
213     }
214 
215     /**
216      * {@inheritDoc}
217      */
218     @Override
219     public ElementType getType() {
220         return TYPE;
221     }
222 
223     /**
224      * Returns the BSON string value.
225      * 
226      * @return The BSON string value.
227      */
228     public String getValue() {
229         return myValue;
230     }
231 
232     /**
233      * {@inheritDoc}
234      * <p>
235      * Returns the {@link String} value.
236      * </p>
237      */
238     @Override
239     public String getValueAsObject() {
240         return getValue();
241     }
242 
243     /**
244      * {@inheritDoc}
245      * <p>
246      * Returns the {@link String} value.
247      * </p>
248      */
249     @Override
250     public String getValueAsString() {
251         return getValue();
252     }
253 
254     /**
255      * Computes a reasonable hash code.
256      * 
257      * @return The hash code value.
258      */
259     @Override
260     public int hashCode() {
261         int result = 1;
262         result = (31 * result) + super.hashCode();
263         result = (31 * result) + ((myValue != null) ? myValue.hashCode() : 3);
264         return result;
265     }
266 
267     /**
268      * {@inheritDoc}
269      * <p>
270      * Returns a new {@link StringElement}.
271      * </p>
272      */
273     @Override
274     public StringElement withName(final String name) {
275         if (getName().equals(name)) {
276             return this;
277         }
278         return new StringElement(name, myValue);
279     }
280 }