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 public class ArrayElement extends AbstractElement {
46
47 /** The BSON type for an array. */
48 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 ourIndexes = new String[1000];
57
58 for (int i = 0; i < ourIndexes.length; ++i) {
59 ourIndexes[i] = Integer.toString(i).intern();
60 }
61 }
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 if ((0 <= index) && (index < ourIndexes.length)) {
73 return ourIndexes[index];
74 }
75 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 long result = 7; // type (1) + name null byte (1) + int length (4) +
91 // elements null byte (1).
92 result += StringEncoder.utf8Size(name);
93 if ((entries != null) && !entries.isEmpty()) {
94 for (final Element element : entries) {
95 result += element.size();
96 }
97 }
98
99 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 this(name, Arrays.asList(entries));
121 }
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 this(name, entries, computeSize(name, entries));
136 }
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 super(name, size);
155
156 if ((entries != null) && !entries.isEmpty()) {
157 // The names of the elements have to be a specific value.
158 final int length = entries.size();
159 final List<Element> elements = new ArrayList<Element>(length);
160 int index = 0;
161 for (final Element element : entries) {
162 final Element withCorrectName = element
163 .withName(nameFor(index));
164 elements.add(withCorrectName);
165 index += 1;
166 }
167
168 myEntries = Collections.unmodifiableList(elements);
169 }
170 else {
171 myEntries = Collections.emptyList();
172 }
173 }
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 if (visitor instanceof SizeAwareVisitor) {
184 ((SizeAwareVisitor) visitor).visitArray(getName(), getEntries(),
185 size());
186 }
187 else {
188 visitor.visitArray(getName(), getEntries());
189 }
190 }
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 int result = super.compareTo(otherElement);
202
203 if (result == 0) {
204 final ArrayElement other = (ArrayElement) otherElement;
205 final int length = Math.min(myEntries.size(),
206 other.myEntries.size());
207 for (int i = 0; i < length; ++i) {
208 result = myEntries.get(i).compareTo(other.myEntries.get(i));
209 if (result != 0) {
210 return result;
211 }
212 }
213
214 result = myEntries.size() - other.myEntries.size();
215 }
216
217 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 boolean result = false;
232 if (this == object) {
233 result = true;
234 }
235 else if ((object != null) && (getClass() == object.getClass())) {
236 final ArrayElement other = (ArrayElement) object;
237
238 result = super.equals(object) && myEntries.equals(other.myEntries);
239 }
240 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 if (0 < nameRegexs.length) {
256 final List<E> elements = new ArrayList<E>();
257 final String nameRegex = nameRegexs[0];
258 final String[] subNameRegexs = Arrays.copyOfRange(nameRegexs, 1,
259 nameRegexs.length);
260 try {
261 final Pattern pattern = PatternUtils.toPattern(nameRegex);
262 for (final Element element : myEntries) {
263 if (pattern.matcher(element.getName()).matches()) {
264 elements.addAll(element.find(clazz, subNameRegexs));
265 }
266 }
267 }
268 catch (final PatternSyntaxException pse) {
269 // Assume a non-pattern?
270 for (final Element element : myEntries) {
271 if (nameRegex.equals(element.getName())) {
272 elements.addAll(element.find(clazz, subNameRegexs));
273 }
274 }
275 }
276
277 return elements;
278 }
279
280 // End of the path -- are we the right type
281 if (clazz.isAssignableFrom(this.getClass())) {
282 return Collections.singletonList(clazz.cast(this));
283 }
284 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 E element = null;
300 if (0 < nameRegexs.length) {
301 final String nameRegex = nameRegexs[0];
302 final String[] subNameRegexs = Arrays.copyOfRange(nameRegexs, 1,
303 nameRegexs.length);
304
305 try {
306 final Pattern pattern = PatternUtils.toPattern(nameRegex);
307 final Iterator<Element> iter = myEntries.iterator();
308 while (iter.hasNext() && (element == null)) {
309 final Element docElement = iter.next();
310 if (pattern.matcher(docElement.getName()).matches()) {
311 element = docElement.findFirst(clazz, subNameRegexs);
312 }
313 }
314 }
315 catch (final PatternSyntaxException pse) {
316 // Assume a non-pattern?
317 final Iterator<Element> iter = myEntries.iterator();
318 while (iter.hasNext() && (element == null)) {
319 final Element docElement = iter.next();
320 if (nameRegex.equals(docElement.getName())) {
321 element = docElement.findFirst(clazz, subNameRegexs);
322 }
323 }
324 }
325 }
326 else {
327 // End of the path -- are we the right type/element?
328 if (clazz.isAssignableFrom(this.getClass())) {
329 element = clazz.cast(this);
330 }
331 }
332 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 return myEntries;
344 }
345
346 /**
347 * {@inheritDoc}
348 */
349 @Override
350 public ElementType getType() {
351 return TYPE;
352 }
353
354 /**
355 * {@inheritDoc}
356 * <p>
357 * Returns an Element[].
358 * </p>
359 */
360 @Override
361 public Element[] getValueAsObject() {
362 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 int result = 1;
373 result = (31 * result) + super.hashCode();
374 result = (31 * result) + myEntries.hashCode();
375 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 if (getName().equals(name)) {
387 return this;
388 }
389 return new ArrayElement(name, myEntries);
390 }
391
392 }