1 /*
2 * #%L
3 * BinaryElement.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.io.IOException;
25 import java.util.Arrays;
26
27 import com.allanbank.mongodb.bson.Element;
28 import com.allanbank.mongodb.bson.ElementType;
29 import com.allanbank.mongodb.bson.Visitor;
30 import com.allanbank.mongodb.bson.io.BsonInputStream;
31 import com.allanbank.mongodb.bson.io.StringEncoder;
32
33 /**
34 * A wrapper for a BSON binary.
35 *
36 * @api.yes This class is part of the driver's API. Public and protected members
37 * will be deprecated for at least 1 non-bugfix release (version
38 * numbers are <major>.<minor>.<bugfix>) before being
39 * removed or modified.
40 * @copyright 2011-2013, Allanbank Consulting, Inc., All Rights Reserved
41 */
42 public class BinaryElement extends AbstractElement {
43
44 /** The sub type used when no sub type is specified. */
45 public static final byte DEFAULT_SUB_TYPE = 0;
46
47 /** The BSON type for a binary. */
48 public static final ElementType TYPE = ElementType.BINARY;
49
50 /** Serialization version for the class. */
51 private static final long serialVersionUID = 5864918707454038001L;
52
53 /**
54 * Computes and returns the number of bytes that are used to encode the
55 * element.
56 *
57 * @param name
58 * The name for the BSON array.
59 * @param subType
60 * The sub-type of the binary data.
61 * @param bytesLength
62 * The length of the binary array.
63 * @return The size of the element when encoded in bytes.
64 */
65 private static long computeSize(final String name, final byte subType,
66 final int bytesLength) {
67 long result = 7; // type (1) + name null byte (1) + data length (4) +
68 // subtype (1).
69 result += StringEncoder.utf8Size(name);
70 result += bytesLength;
71
72 if (subType == 2) {
73 // Extra length field in subtype 2.
74 result += 4;
75 }
76
77 return result;
78 }
79
80 /** The sub-type of the binary data. */
81 private final byte mySubType;
82
83 /** The BSON binary value. */
84 private final byte[] myValue;
85
86 /**
87 * Constructs a new {@link BinaryElement}.
88 *
89 * @param name
90 * The name for the BSON binary.
91 * @param subType
92 * The sub-type of the binary data.
93 * @param input
94 * The stream to read the data from.
95 * @param length
96 * The number of bytes of data to read.
97 * @throws IllegalArgumentException
98 * If the {@code name} is <code>null</code>.
99 * @throws IOException
100 * If there is an error reading from {@code input} stream.
101 */
102 public BinaryElement(final String name, final byte subType,
103 final BsonInputStream input, final int length) throws IOException {
104 this(name, subType, input, length, computeSize(name, subType, length));
105 }
106
107 /**
108 * Constructs a new {@link BinaryElement}.
109 *
110 * @param name
111 * The name for the BSON binary.
112 * @param subType
113 * The sub-type of the binary data.
114 * @param input
115 * The stream to read the data from.
116 * @param length
117 * The number of bytes of data to read.
118 * @param size
119 * The size of the element when encoded in bytes. If not known
120 * then use the
121 * {@link BinaryElement#BinaryElement(String, byte, BsonInputStream, int)}
122 * constructor instead.
123 * @throws IllegalArgumentException
124 * If the {@code name} is <code>null</code>.
125 * @throws IOException
126 * If there is an error reading from {@code input} stream.
127 */
128 public BinaryElement(final String name, final byte subType,
129 final BsonInputStream input, final int length, final long size)
130 throws IOException {
131 super(name, size);
132
133 mySubType = subType;
134 myValue = new byte[length];
135
136 input.readFully(myValue);
137 }
138
139 /**
140 * Constructs a new {@link BinaryElement}.
141 *
142 * @param name
143 * The name for the BSON binary.
144 * @param subType
145 * The sub-type of the binary data.
146 * @param value
147 * The BSON binary value.
148 * @throws IllegalArgumentException
149 * If the {@code name} or {@code value} is <code>null</code>.
150 */
151 public BinaryElement(final String name, final byte subType,
152 final byte[] value) {
153 this(name, subType, value, computeSize(name, subType,
154 (value == null) ? 0 : value.length));
155 }
156
157 /**
158 * Constructs a new {@link BinaryElement}.
159 *
160 * @param name
161 * The name for the BSON binary.
162 * @param subType
163 * The sub-type of the binary data.
164 * @param value
165 * The BSON binary value.
166 * @param size
167 * The size of the element when encoded in bytes. If not known
168 * then use the
169 * {@link BinaryElement#BinaryElement(String, byte, byte[])}
170 * constructor instead.
171 * @throws IllegalArgumentException
172 * If the {@code name} or {@code value} is <code>null</code>.
173 */
174 public BinaryElement(final String name, final byte subType,
175 final byte[] value, final long size) {
176 super(name, size);
177
178 assertNotNull(value,
179 "Binary element's value cannot be null. Add a null element instead.");
180
181 mySubType = subType;
182 myValue = value.clone();
183 }
184
185 /**
186 * Constructs a new {@link BinaryElement}. Uses the
187 * {@link #DEFAULT_SUB_TYPE}.
188 *
189 * @param name
190 * The name for the BSON binary.
191 * @param value
192 * The BSON binary value.
193 * @throws IllegalArgumentException
194 * If the {@code name} or {@code value} is <code>null</code>.
195 */
196 public BinaryElement(final String name, final byte[] value) {
197 this(name, DEFAULT_SUB_TYPE, value);
198 }
199
200 /**
201 * Accepts the visitor and calls the {@link Visitor#visitBinary} method.
202 *
203 * @see Element#accept(Visitor)
204 */
205 @Override
206 public void accept(final Visitor visitor) {
207 visitor.visitBinary(getName(), getSubType(), getValue());
208 }
209
210 /**
211 * {@inheritDoc}
212 * <p>
213 * Overridden to compare the sub-types and bytes if the base class
214 * comparison is equals.
215 * </p>
216 */
217 @Override
218 public int compareTo(final Element otherElement) {
219 int result = super.compareTo(otherElement);
220
221 if (result == 0) {
222 final BinaryElement other = (BinaryElement) otherElement;
223
224 result = mySubType - other.mySubType;
225 if (result == 0) {
226 final int length = Math.min(myValue.length,
227 other.myValue.length);
228 for (int i = 0; i < length; ++i) {
229 result = myValue[i] - other.myValue[i];
230 if (result != 0) {
231 return result;
232 }
233 }
234
235 result = myValue.length - other.myValue.length;
236 }
237 }
238
239 return result;
240 }
241
242 /**
243 * Determines if the passed object is of this same type as this object and
244 * if so that its fields are equal.
245 *
246 * @param object
247 * The object to compare to.
248 *
249 * @see java.lang.Object#equals(java.lang.Object)
250 */
251 @Override
252 public boolean equals(final Object object) {
253 boolean result = false;
254 if (this == object) {
255 result = true;
256 }
257 else if ((object != null) && (getClass() == object.getClass())) {
258 final BinaryElement other = (BinaryElement) object;
259
260 result = super.equals(object) && (mySubType == other.mySubType)
261 && Arrays.equals(myValue, other.myValue);
262 }
263 return result;
264 }
265
266 /**
267 * Returns the byte value at the specified offset.
268 *
269 * @param offset
270 * The offset of the desired value.
271 * @return The byte value at the offset.
272 * @throws ArrayIndexOutOfBoundsException
273 * If the offset is not in the range [0, {@link #length()}).
274 */
275 public final byte get(final int offset) {
276 return myValue[offset];
277 }
278
279 /**
280 * Return the binary sub-type.
281 *
282 * @return The binary sub-type.
283 */
284 public byte getSubType() {
285 return mySubType;
286 }
287
288 /**
289 * {@inheritDoc}
290 */
291 @Override
292 public ElementType getType() {
293 return TYPE;
294 }
295
296 /**
297 * Returns the BSON binary value. For safety reasons this method clones the
298 * internal byte array. To avoid the copying of the bytes use the
299 * {@link #length()} and {@link #get(int)} methods to access each byte
300 * value.
301 *
302 * @return The BSON binary value.
303 */
304 public byte[] getValue() {
305 return myValue.clone();
306 }
307
308 /**
309 * {@inheritDoc}
310 * <p>
311 * Returns a byte[].
312 * </p>
313 * <p>
314 * <b>Note:</b> This value will not be recreated is a Object-->Element
315 * conversion. The sub type is lost in this conversion to an {@link Object}.
316 * </p>
317 * <p>
318 * <em>Implementation Note:</em> The return type cannot be a byte[] here as
319 * {@link UuidElement} returns a {@link java.util.UUID}.
320 * </p>
321 */
322 @Override
323 public Object getValueAsObject() {
324 return getValue();
325 }
326
327 /**
328 * Computes a reasonable hash code.
329 *
330 * @return The hash code value.
331 */
332 @Override
333 public int hashCode() {
334 int result = 1;
335 result = (31 * result) + super.hashCode();
336 result = (31 * result) + mySubType;
337 result = (31 * result) + Arrays.hashCode(myValue);
338 return result;
339 }
340
341 /**
342 * Returns the length of the contained byte array.
343 *
344 * @return The length of the contained byte array.
345 */
346 public final int length() {
347 return myValue.length;
348 }
349
350 /**
351 * {@inheritDoc}
352 * <p>
353 * Returns a new {@link BinaryElement}.
354 * </p>
355 */
356 @Override
357 public BinaryElement withName(final String name) {
358 if (getName().equals(name)) {
359 return this;
360 }
361 return new BinaryElement(name, mySubType, myValue);
362 }
363 }