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.
36
41
*/
37
42
static Opaque forBytes (byte [] bytes ) {
38
- return new OpaqueImpl (bytes .clone ());
43
+ return new OpaqueImmutableImpl (bytes .clone ());
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 mutable {@link Opaque} instance based on the given byte array.
48
+ * <p>
49
+ * Note that the returned {@link Opaque} is typically not suitable for <em>storing</em> in a
50
+ * {@link java.util.HashMap}, but merely for lookups. Call {@link #toImmutableOpaque()} when necessary.
51
+ *
52
+ * @param bytes The bytes.
53
+ * @return The {@link Opaque} instance.
54
+ */
55
+ static Opaque forMutableByteArray (byte [] bytes ) {
56
+ return new OpaqueImpl (bytes );
57
+ }
58
+
59
+ /**
60
+ * Returns an immutable {@link Opaque} instance based on a copy of the {@code length} bytes from the given
61
+ * {@link ByteBuffer}.
43
62
*
44
63
* @param buf The buffer.
45
64
* @param length The number of bytes.
@@ -49,7 +68,24 @@ static Opaque forBytes(ByteBuffer buf, int length) {
49
68
byte [] bytes = new byte [length ];
50
69
buf .get (bytes );
51
70
52
- return new OpaqueImpl (bytes );
71
+ return new OpaqueImmutableImpl (bytes );
72
+ }
73
+
74
+ /**
75
+ * Returns a <em>mutable</em> {@link Opaque} instance backed on the byte contents of the given {@link ByteBuffer},
76
+ * for the given number of bytes starting from the given absolute index.
77
+ * <p>
78
+ * Note that the returned {@link Opaque} is typically not suitable for <em>storing</em> in a
79
+ * {@link java.util.HashMap}, but merely for lookups. Call {@link #toImmutableOpaque()} when necessary.
80
+ *
81
+ * @param buf The buffer backing the {@link Opaque}.
82
+ * @param index The absolute index to start from.
83
+ * @param length The number of bytes.
84
+ * @return The {@link Opaque} instance.
85
+ * @see #toImmutableOpaque()
86
+ */
87
+ static Opaque forMutableByteBuffer (ByteBuffer buf , int index , int length ) {
88
+ return new OpaqueBufferImpl (buf , index , length );
53
89
}
54
90
55
91
/**
@@ -102,6 +138,13 @@ static boolean defaultEquals(Opaque obj, Object other) {
102
138
*/
103
139
String toBase64 ();
104
140
141
+ /**
142
+ * Returns an immutable {@link Opaque}, which may be the instance itself if it is already immutable.
143
+ *
144
+ * @return An immutable opaque.
145
+ */
146
+ Opaque toImmutableOpaque ();
147
+
105
148
/**
106
149
* Writes the bytes of this {@link Opaque} to the given {@link ByteBuffer}.
107
150
*
@@ -131,11 +174,10 @@ default void putBytes(ByteBuffer buf) {
131
174
@ Override
132
175
boolean equals (Object o );
133
176
134
- final class OpaqueImpl implements Opaque {
135
- private final byte [] _opaque ;
136
- private String base64 = null ;
177
+ class OpaqueImpl implements Opaque {
178
+ final byte [] _opaque ;
137
179
138
- private OpaqueImpl (byte [] opaque ) {
180
+ OpaqueImpl (byte [] opaque ) {
139
181
_opaque = opaque ;
140
182
}
141
183
@@ -145,21 +187,22 @@ public byte[] toBytes() {
145
187
}
146
188
147
189
@ Override
148
- public String toBase64 () {
149
- if (base64 == null ) {
150
- base64 = Base64 .getEncoder ().withoutPadding ().encodeToString (_opaque );
151
- }
152
- return base64 ;
190
+ public int hashCode () {
191
+ return Arrays .hashCode (_opaque );
153
192
}
154
193
155
194
@ Override
156
- public void putBytes (ByteBuffer buf ) {
157
- buf .put (_opaque );
195
+ public String toBase64 () {
196
+ return toBase64Impl ();
197
+ }
198
+
199
+ protected String toBase64Impl () {
200
+ return Base64 .getEncoder ().withoutPadding ().encodeToString (_opaque );
158
201
}
159
202
160
203
@ Override
161
- public int hashCode ( ) {
162
- return Arrays . hashCode (_opaque );
204
+ public void putBytes ( ByteBuffer buf ) {
205
+ buf . put (_opaque );
163
206
}
164
207
165
208
@ Override
@@ -173,6 +216,19 @@ public boolean equals(Object o) {
173
216
174
217
if (o instanceof OpaqueImpl ) {
175
218
return Arrays .equals (_opaque , ((OpaqueImpl ) o )._opaque );
219
+ } else if (o instanceof OpaqueBufferImpl ) {
220
+ OpaqueBufferImpl other = (OpaqueBufferImpl ) o ;
221
+ if (other .numBytes () != _opaque .length ) {
222
+ return false ;
223
+ }
224
+ ByteBuffer otherBuf = other .buf ;
225
+ int otherIndex = other .index ;
226
+ for (int i = 0 , n = _opaque .length , oi = otherIndex ; i < n ; i ++, oi ++) {
227
+ if (_opaque [i ] != otherBuf .get (oi )) {
228
+ return false ;
229
+ }
230
+ }
231
+ return true ;
176
232
} else {
177
233
return Arrays .equals (_opaque , ((Opaque ) o ).toBytes ());
178
234
}
@@ -192,5 +248,125 @@ public String toString() {
192
248
public int numBytes () {
193
249
return _opaque .length ;
194
250
}
251
+
252
+ @ Override
253
+ public Opaque toImmutableOpaque () {
254
+ return Opaque .forBytes (_opaque );
255
+ }
256
+ }
257
+
258
+ final class OpaqueImmutableImpl extends OpaqueImpl {
259
+ private String base64 = null ;
260
+ private int hashCode ;
261
+
262
+ protected OpaqueImmutableImpl (byte [] opaque ) {
263
+ super (opaque );
264
+ }
265
+
266
+ @ Override
267
+ public int hashCode () {
268
+ if (hashCode == 0 ) {
269
+ hashCode = Arrays .hashCode (_opaque );
270
+ }
271
+ return hashCode ;
272
+ }
273
+
274
+ @ Override
275
+ public String toBase64 () {
276
+ if (base64 == null ) {
277
+ base64 = toBase64Impl ();
278
+ }
279
+ return base64 ;
280
+ }
281
+
282
+ @ Override
283
+ public Opaque toImmutableOpaque () {
284
+ return this ;
285
+ }
286
+ }
287
+
288
+ final class OpaqueBufferImpl implements Opaque {
289
+ private final ByteBuffer buf ;
290
+ private final int index ;
291
+ private final int length ;
292
+
293
+ private OpaqueBufferImpl (ByteBuffer buf , int index , int length ) {
294
+ this .buf = Objects .requireNonNull (buf );
295
+ this .index = index ;
296
+ this .length = length ;
297
+ }
298
+
299
+ @ Override
300
+ public byte [] toBytes () {
301
+ byte [] bytes = new byte [length ];
302
+ buf .get (index , bytes );
303
+ return bytes ;
304
+ }
305
+
306
+ @ Override
307
+ public int numBytes () {
308
+ return length ;
309
+ }
310
+
311
+ @ Override
312
+ public String toBase64 () {
313
+ return Base64 .getEncoder ().withoutPadding ().encodeToString (toBytes ());
314
+ }
315
+
316
+ @ Override
317
+ public Opaque toImmutableOpaque () {
318
+ return Opaque .forBytes (toBytes ());
319
+ }
320
+
321
+ @ Override
322
+ public int hashCode () {
323
+ int result = 1 ;
324
+ for (int i = index , n = index + length ; i < n ; i ++) {
325
+ byte element = buf .get (i );
326
+ result = 31 * result + element ;
327
+ }
328
+
329
+ return result ;
330
+ }
331
+
332
+ @ Override
333
+ public boolean equals (Object o ) {
334
+ if (o == this ) {
335
+ return true ;
336
+ }
337
+ if (!(o instanceof Opaque )) {
338
+ return false ;
339
+ }
340
+ if (length != ((Opaque ) o ).numBytes ()) {
341
+ return false ;
342
+ }
343
+
344
+ if (o instanceof OpaqueImpl ) {
345
+ byte [] otherBytes = ((OpaqueImpl ) o )._opaque ;
346
+ for (int i = index , n = index + length , oi = 0 ; i < n ; i ++, oi ++) {
347
+ if (buf .get (i ) != otherBytes [oi ]) {
348
+ return false ;
349
+ }
350
+ }
351
+ return true ;
352
+ } else if (o instanceof OpaqueBufferImpl ) {
353
+ OpaqueBufferImpl other = (OpaqueBufferImpl ) o ;
354
+ ByteBuffer otherBuf = other .buf ;
355
+ int otherIndex = other .index ;
356
+ for (int i = index , n = index + length , oi = otherIndex ; i < n ; i ++, oi ++) {
357
+ if (buf .get (i ) != otherBuf .get (oi )) {
358
+ return false ;
359
+ }
360
+ }
361
+ return true ;
362
+ } else {
363
+ return toImmutableOpaque ().equals (o );
364
+ }
365
+ }
366
+
367
+ @ Override
368
+ public String toString () {
369
+ return super .toString () + "[" + toBase64 () + "]" ;
370
+ }
195
371
}
196
372
}
0 commit comments