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 }