View Javadoc
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 }