12
12
/**
13
13
* General purpose immutable wrapper for byte array data.
14
14
*
15
- * Can be encoded fully as a single Cell if 4096 bytes or less, otherwise needs to be
16
- * structures as a BlobTree.
15
+ * Can be encoded fully as a single Cell if 4096 bytes or less, otherwise needs
16
+ * to be structures as a BlobTree.
17
17
*
18
- * Encoding format is:
19
- * - Tag.BLOB tag byte
20
- * - VLC encoded Blob length in bytes (one or two bytes describing a length in range 0..4096)
21
- * - Byte data of the given length
18
+ * Encoding format is: - Tag.BLOB tag byte - VLC encoded Blob length in bytes
19
+ * (one or two bytes describing a length in range 0..4096) - Byte data of the
20
+ * given length
22
21
*/
23
22
public class Blob extends AArrayBlob {
24
23
public static final Blob EMPTY = Cells .intern (wrap (Utils .EMPTY_BYTES ));
25
- public static final Blob SINGLE_ZERO = Cells .intern (wrap (new byte [] {0 }));
26
- public static final Blob SINGLE_ONE = Cells .intern (wrap (new byte [] {1 }));
27
- public static final Blob SINGLE_A =wrap (new byte [] {0x41 });
24
+ public static final Blob SINGLE_ZERO = Cells .intern (wrap (new byte [] { 0 }));
25
+ public static final Blob SINGLE_ONE = Cells .intern (wrap (new byte [] { 1 }));
26
+ public static final Blob SINGLE_A = wrap (new byte [] { 0x41 });
27
+
28
+ public static final Blob NULL_ENCODING = Blob .wrap (new byte [] { Tag .NULL });
28
29
29
- public static final Blob NULL_ENCODING = Blob .wrap (new byte [] {Tag .NULL });
30
-
31
30
public static final int CHUNK_LENGTH = 4096 ;
32
-
33
- private static final byte [] EMPTY_CHUNK_BYTES = new byte [CHUNK_LENGTH ];
34
-
31
+
32
+ private static final byte [] EMPTY_CHUNK_BYTES = new byte [CHUNK_LENGTH ];
33
+
35
34
public static final Blob EMPTY_CHUNK = Cells .intern (wrap (EMPTY_CHUNK_BYTES ));
36
35
37
36
private Blob (byte [] bytes , int offset , int length ) {
@@ -41,14 +40,15 @@ private Blob(byte[] bytes, int offset, int length) {
41
40
/**
42
41
* Creates a new Blob using a copy of the specified byte range
43
42
*
44
- * @param data Byte array
43
+ * @param data Byte array
45
44
* @param offset Start offset in the byte array
46
45
* @param length Number of bytes to take from data array
47
46
* @return The new Data object
48
47
*/
49
48
public static Blob create (byte [] data , int offset , int length ) {
50
49
if (length <= 0 ) {
51
- if (length == 0 ) return EMPTY ;
50
+ if (length == 0 )
51
+ return EMPTY ;
52
52
throw new IllegalArgumentException (ErrorMessages .negativeLength (length ));
53
53
}
54
54
byte [] store = Arrays .copyOfRange (data , offset , offset + length );
@@ -64,16 +64,17 @@ public static Blob create(byte[] data, int offset, int length) {
64
64
public static Blob create (byte [] data ) {
65
65
return create (data , 0 , data .length );
66
66
}
67
-
67
+
68
68
/**
69
69
* Parses String input as a Blob. Converts from hex.
70
70
*
71
71
* @param data Byte array
72
72
* @return Blob with the same byte contents as the given array
73
73
*/
74
74
public static Blob parse (String data ) {
75
- ABlob b =Blobs .parse (data );
76
- if (b ==null ) return null ;
75
+ ABlob b = Blobs .parse (data );
76
+ if (b == null )
77
+ return null ;
77
78
return b .toFlatBlob ();
78
79
}
79
80
@@ -94,21 +95,23 @@ public static Blob wrap(byte[] data) {
94
95
* directly. Use only if no other references to the byte array are kept which
95
96
* might be mutated.
96
97
*
97
- * @param data Byte array
98
+ * @param data Byte array
98
99
* @param offset Offset into byte array
99
100
* @param length Length of byte array to wrap
100
101
* @return Blob wrapping the given byte array segment
101
102
*/
102
103
public static Blob wrap (byte [] data , int offset , int length ) {
103
- if (length < 0 ) throw new IllegalArgumentException (ErrorMessages .negativeLength (length ));
104
+ if (length < 0 )
105
+ throw new IllegalArgumentException (ErrorMessages .negativeLength (length ));
104
106
if ((offset < 0 ) || (offset + length > data .length ))
105
- throw new IndexOutOfBoundsException (ErrorMessages .badRange (offset , offset +length ));
106
- if (length ==0 ) return Blob .EMPTY ;
107
- Blob b = new Blob (data , offset , length );
108
-
107
+ throw new IndexOutOfBoundsException (ErrorMessages .badRange (offset , offset + length ));
108
+ if (length == 0 )
109
+ return Blob .EMPTY ;
110
+ Blob b = new Blob (data , offset , length );
111
+
109
112
// optimisation to re-use Blob encoding if present
110
- if ((offset >= 2 )&& (length < 128 )&& (data [offset - 1 ]== (byte )length )&& (data [offset - 2 ]== Tag .BLOB )) {
111
- b .attachEncoding (Blob .wrap (data ,offset - 2 , length + 2 ));
113
+ if ((offset >= 2 ) && (length < 128 ) && (data [offset - 1 ] == (byte ) length ) && (data [offset - 2 ] == Tag .BLOB )) {
114
+ b .attachEncoding (Blob .wrap (data , offset - 2 , length + 2 ));
112
115
}
113
116
return b ;
114
117
}
@@ -120,55 +123,61 @@ public Blob toFlatBlob() {
120
123
121
124
@ Override
122
125
public Blob slice (long start , long end ) {
123
- if (start < 0 ) return null ;
126
+ if (start < 0 ) return null ;
124
127
if (end > this .count ) return null ;
125
- long length = end - start ;
126
- int size = (int )length ;
127
- if (size != length ) return null ; // int overflow, too big for valid Blob slice!
128
+ long length = end - start ;
129
+ int size = (int ) length ;
130
+ if (size != length ) return null ; // int overflow, i.e. too big for valid Blob slice!
128
131
if (length < 0 ) return null ;
129
132
if (length == 0 ) return EMPTY ;
130
- if (length == this .count ) return this ;
133
+ if (length == this .count ) return this ;
131
134
return Blob .wrap (store , Utils .checkedInt (start + offset ), size );
132
135
}
133
-
136
+
134
137
@ Override
135
138
public Blob slice (long start ) {
136
139
return slice (start , count ());
137
140
}
138
141
139
142
@ Override
140
143
public boolean equals (ABlob a ) {
141
- if (a ==this ) return true ;
142
- if (a instanceof AArrayBlob ) return equals ((AArrayBlob ) a );
143
- long n =count ();
144
- if (a .count ()!=n ) return false ;
145
- if (!(a .getType ()==Types .BLOB )) return false ;
146
- if (n <=CHUNK_LENGTH ) {
144
+ if (a == this )
145
+ return true ;
146
+ if (a instanceof AArrayBlob )
147
+ return equals ((AArrayBlob ) a );
148
+ long n = count ();
149
+ if (a .count () != n )
150
+ return false ;
151
+ if (!(a .getType () == Types .BLOB ))
152
+ return false ;
153
+ if (n <= CHUNK_LENGTH ) {
154
+ // Fast byte comparison, this is the normal fast path
147
155
return a .equalsBytes (this .store , this .offset );
148
156
} else {
149
- // this must be a non-canonical Blob
157
+ // this must be a non-canonical Blob, i.e. bigger than CHUNK_LENGTH
150
158
// we coerce encoding, since might have hash, and probably needed anyway
159
+ // Expensive encoding might happen, but at least this gets cached
151
160
return getEncoding ().equals (a .getEncoding ());
152
161
}
153
162
}
154
163
155
-
156
-
157
164
/**
158
165
* Constructs a Blob object from a hex string
159
166
*
160
167
* @param hexString Hex String to read
161
168
* @return Blob with the provided hex value, or null if not a valid blob
162
169
*/
163
170
public static Blob fromHex (String hexString ) {
164
- byte [] bs =Utils .hexToBytes (hexString );
165
- if (bs ==null ) return null ;
166
- if (bs .length ==0 ) return EMPTY ;
171
+ byte [] bs = Utils .hexToBytes (hexString );
172
+ if (bs == null )
173
+ return null ;
174
+ if (bs .length == 0 )
175
+ return EMPTY ;
167
176
return wrap (bs );
168
177
}
169
-
178
+
170
179
public static Blob forByte (byte b ) {
171
- return wrap (new byte [] {b });
180
+ return wrap (new byte [] { b });
172
181
}
173
182
174
183
/**
@@ -184,34 +193,35 @@ public static Blob fromByteBuffer(ByteBuffer bb) {
184
193
return Blob .wrap (bs );
185
194
}
186
195
187
-
188
-
189
196
/**
190
- * Fast read of a Blob from its encoding inside another Blob object.
191
- * Assumes count is correct at start of encoding (pos+1)
197
+ * Fast read of a Blob from its encoding inside another Blob object. Assumes
198
+ * count is correct at start of encoding (pos+1)
192
199
*
193
200
* @param source Source Blob object.
194
- * @param pos Position in source to start reading from (location of tag byte)
195
- * @param count Length in bytes to take from the source Blob
201
+ * @param pos Position in source to start reading from (location of tag byte)
202
+ * @param count Length in bytes to take from the source Blob
196
203
* @return Blob read from the source
197
204
* @throws BadFormatException If encoding is invalid
198
205
*/
199
206
public static Blob read (Blob source , int pos , long count ) throws BadFormatException {
200
- if (count ==0 ) return EMPTY ; // important! Don't want to allocate new empty Blobs or mess with EMPTY encoding
201
- if (count >CHUNK_LENGTH ) throw new BadFormatException ("Trying to read flat blob with count = " +count );
202
-
207
+ if (count == 0 )
208
+ return EMPTY ; // important! Don't want to allocate new empty Blobs or mess with EMPTY encoding
209
+ if (count > CHUNK_LENGTH )
210
+ throw new BadFormatException ("Trying to read flat blob with count = " + count );
211
+
203
212
// compute data length, excluding tag and encoded length
204
213
int headerLength = (1 + Format .getVLQCountLength (count ));
205
- long start = pos + headerLength ;
206
- if (start + count > source .count ()) {
214
+ long start = pos + headerLength ;
215
+ if (start + count > source .count ()) {
207
216
throw new BadFormatException ("Insufficient bytes to read Blob required count =" + count );
208
217
}
209
218
210
- Blob result = source .slice (start , start +count );
211
- if (result ==null ) throw new IllegalArgumentException ("Failed to slice Blob source" );
212
- if (source .byteAtUnchecked (pos )==Tag .BLOB ) {
219
+ Blob result = source .slice (start , start + count );
220
+ if (result == null )
221
+ throw new IllegalArgumentException ("Failed to slice Blob source" );
222
+ if (source .byteAtUnchecked (pos ) == Tag .BLOB ) {
213
223
// Only attach encoding if we were reading a genuine Blob
214
- result .attachEncoding (source .slice (pos ,pos + (headerLength + count )));
224
+ result .attachEncoding (source .slice (pos , pos + (headerLength + count )));
215
225
}
216
226
return result ;
217
227
}
@@ -222,22 +232,21 @@ public int encodeRaw(byte[] bs, int pos) {
222
232
// We aren't canonical, so need to encode canonical representation
223
233
return getCanonical ().encodeRaw (bs , pos );
224
234
} else {
225
- pos = super .encodeRaw (bs ,pos );
235
+ pos = super .encodeRaw (bs , pos );
226
236
return pos ;
227
237
}
228
238
}
229
-
239
+
230
240
@ Override
231
241
public int estimatedEncodingSize () {
232
242
// space for tag, generous VLC length, plus raw data
233
243
return 1 + Format .MAX_VLQ_LONG_LENGTH + size ();
234
244
}
235
-
245
+
236
246
/**
237
247
* Maximum encoding size for a regular Blob
238
248
*/
239
- public static final int MAX_ENCODING_LENGTH =1 +Format .getVLQCountLength (CHUNK_LENGTH )+CHUNK_LENGTH ;
240
-
249
+ public static final int MAX_ENCODING_LENGTH = 1 + Format .getVLQCountLength (CHUNK_LENGTH ) + CHUNK_LENGTH ;
241
250
242
251
@ Override
243
252
public boolean isCanonical () {
@@ -259,21 +268,22 @@ public static Blob createRandom(Random random, long length) {
259
268
260
269
@ Override
261
270
public Blob getChunk (long i ) {
262
- if ((i == 0 ) && (count <= CHUNK_LENGTH )) return this ;
271
+ if ((i == 0 ) && (count <= CHUNK_LENGTH ))
272
+ return this ;
263
273
long start = i * CHUNK_LENGTH ;
264
- long take = Math .min (CHUNK_LENGTH , count - start );
265
- return slice (start , start + take );
274
+ long take = Math .min (CHUNK_LENGTH , count - start );
275
+ return slice (start , start + take );
266
276
}
267
277
268
278
public void attachContentHash (Hash hash ) {
269
- if (contentHash == null ) contentHash = hash ;
279
+ if (contentHash == null ) contentHash = hash ;
270
280
}
271
281
272
282
@ Override
273
283
public ABlob toCanonical () {
274
- if (isCanonical ()) return this ;
284
+ if (isCanonical ())
285
+ return this ;
275
286
return Blobs .toCanonical (this );
276
287
}
277
-
278
288
279
289
}
0 commit comments