22
22
import java .nio .ByteBuffer ;
23
23
import java .util .Arrays ;
24
24
import java .util .Base64 ;
25
+ import java .util .Objects ;
25
26
26
27
/**
27
- * Describes something that can be used as a key in {@link java.util.Map} and that can be converted to a {@code byte[]}
28
- * and a Base64 string representation.
28
+ * Describes something that can be used as a key for {@link java.util.HashMap} and that can be converted to a
29
+ * {@code byte[]} and a Base64 string representation.
30
+ * <p>
31
+ * Note that {@link Opaque}s that are <em>stored</em> in {@link java.util.HashMap} need to be immutable. Call
32
+ * {@link #toImmutableOpaque()} when necessary (e.g., when using {@link java.util.HashMap#put(Object, Object)},
33
+ * {@link java.util.HashMap#computeIfAbsent(Object, java.util.function.Function)}, etc.
29
34
*/
30
35
public interface Opaque {
31
36
/**
32
- * Returns an {@link Opaque} instance based on a copy of the given bytes.
37
+ * Returns an immutable {@link Opaque} instance based on a copy of the given bytes.
33
38
*
34
39
* @param bytes The bytes.
35
40
* @return The {@link Opaque} instance.
@@ -39,7 +44,8 @@ static Opaque forBytes(byte[] bytes) {
39
44
}
40
45
41
46
/**
42
- * Returns an {@link Opaque} instance based on a copy of the {@code length} bytes from the given {@link ByteBuffer}.
47
+ * Returns an immutable {@link Opaque} instance based on a copy of the {@code length} bytes from the given
48
+ * {@link ByteBuffer}.
43
49
*
44
50
* @param buf The buffer.
45
51
* @param length The number of bytes.
@@ -52,6 +58,23 @@ static Opaque forBytes(ByteBuffer buf, int length) {
52
58
return new OpaqueImpl (bytes );
53
59
}
54
60
61
+ /**
62
+ * Returns a <em>mutable</em> {@link Opaque} instance backed on the byte contents of the given {@link ByteBuffer},
63
+ * for the given number of bytes starting from the given absolute index.
64
+ * <p>
65
+ * Note that the returned {@link Opaque} is typically not suitable for <em>storing</em> in a
66
+ * {@link java.util.HashMap}, but merely for lookups. Call {@link #toImmutableOpaque()} when necessary.
67
+ *
68
+ * @param buf The buffer backing the {@link Opaque}.
69
+ * @param index The absolute index to start from.
70
+ * @param length The number of bytes.
71
+ * @return The {@link Opaque} instance.
72
+ * @see #toImmutableOpaque()
73
+ */
74
+ static Opaque forMutableByteBuffer (ByteBuffer buf , int index , int length ) {
75
+ return new OpaqueBufferImpl (buf , index , length );
76
+ }
77
+
55
78
/**
56
79
* Default implementation for {@link #hashCode()}.
57
80
*
@@ -102,6 +125,13 @@ static boolean defaultEquals(Opaque obj, Object other) {
102
125
*/
103
126
String toBase64 ();
104
127
128
+ /**
129
+ * Returns an immutable {@link Opaque}, which may be the instance itself if it is already immutable.
130
+ *
131
+ * @return An immutable opaque.
132
+ */
133
+ Opaque toImmutableOpaque ();
134
+
105
135
/**
106
136
* Writes the bytes of this {@link Opaque} to the given {@link ByteBuffer}.
107
137
*
@@ -177,6 +207,19 @@ public boolean equals(Object o) {
177
207
178
208
if (o instanceof OpaqueImpl ) {
179
209
return Arrays .equals (_opaque , ((OpaqueImpl ) o )._opaque );
210
+ } else if (o instanceof OpaqueBufferImpl ) {
211
+ OpaqueBufferImpl other = (OpaqueBufferImpl ) o ;
212
+ if (other .numBytes () != _opaque .length ) {
213
+ return false ;
214
+ }
215
+ ByteBuffer otherBuf = other .buf ;
216
+ int otherIndex = other .index ;
217
+ for (int i = 0 , n = _opaque .length , oi = otherIndex ; i < n ; i ++, oi ++) {
218
+ if (_opaque [i ] != otherBuf .get (oi )) {
219
+ return false ;
220
+ }
221
+ }
222
+ return true ;
180
223
} else {
181
224
return Arrays .equals (_opaque , ((Opaque ) o ).toBytes ());
182
225
}
@@ -196,5 +239,95 @@ public String toString() {
196
239
public int numBytes () {
197
240
return _opaque .length ;
198
241
}
242
+
243
+ @ Override
244
+ public Opaque toImmutableOpaque () {
245
+ return this ;
246
+ }
247
+ }
248
+
249
+ final class OpaqueBufferImpl implements Opaque {
250
+ private final ByteBuffer buf ;
251
+ private final int index ;
252
+ private final int length ;
253
+
254
+ private OpaqueBufferImpl (ByteBuffer buf , int index , int length ) {
255
+ this .buf = Objects .requireNonNull (buf );
256
+ this .index = index ;
257
+ this .length = length ;
258
+ }
259
+
260
+ @ Override
261
+ public byte [] toBytes () {
262
+ byte [] bytes = new byte [length ];
263
+ buf .get (index , bytes );
264
+ return bytes ;
265
+ }
266
+
267
+ @ Override
268
+ public int numBytes () {
269
+ return length ;
270
+ }
271
+
272
+ @ Override
273
+ public String toBase64 () {
274
+ return Base64 .getEncoder ().withoutPadding ().encodeToString (toBytes ());
275
+ }
276
+
277
+ @ Override
278
+ public Opaque toImmutableOpaque () {
279
+ return Opaque .forBytes (toBytes ());
280
+ }
281
+
282
+ @ Override
283
+ public int hashCode () {
284
+ int result = 1 ;
285
+ for (int i = index , n = index + length ; i < n ; i ++) {
286
+ byte element = buf .get (i );
287
+ result = 31 * result + element ;
288
+ }
289
+
290
+ return result ;
291
+ }
292
+
293
+ @ Override
294
+ public boolean equals (Object o ) {
295
+ if (o == this ) {
296
+ return true ;
297
+ }
298
+ if (!(o instanceof Opaque )) {
299
+ return false ;
300
+ }
301
+ if (length != ((Opaque ) o ).numBytes ()) {
302
+ return false ;
303
+ }
304
+
305
+ if (o instanceof OpaqueImpl ) {
306
+ byte [] otherBytes = ((OpaqueImpl ) o )._opaque ;
307
+ for (int i = index , n = index + length , oi = 0 ; i < n ; i ++, oi ++) {
308
+ if (buf .get (i ) != otherBytes [oi ]) {
309
+ return false ;
310
+ }
311
+ }
312
+ return true ;
313
+ } else if (o instanceof OpaqueBufferImpl ) {
314
+ OpaqueBufferImpl other = (OpaqueBufferImpl ) o ;
315
+ ByteBuffer otherBuf = other .buf ;
316
+ int otherIndex = other .index ;
317
+ for (int i = index , n = index + length , oi = otherIndex ; i < n ; i ++, oi ++) {
318
+ if (buf .get (i ) != otherBuf .get (oi )) {
319
+ return false ;
320
+ }
321
+ }
322
+ return true ;
323
+ } else {
324
+ return toImmutableOpaque ().equals (o );
325
+ }
326
+ }
327
+
328
+ @ Override
329
+ public String toString () {
330
+ return super .toString () + "[" + toBase64 () + "]" ;
331
+ }
199
332
}
200
333
}
0 commit comments