2222import java .nio .ByteBuffer ;
2323import java .util .Arrays ;
2424import java .util .Base64 ;
25+ import java .util .Objects ;
2526
2627/**
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.
2934 */
3035public interface Opaque {
3136 /**
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.
3338 *
3439 * @param bytes The bytes.
3540 * @return The {@link Opaque} instance.
3641 */
3742 static Opaque forBytes (byte [] bytes ) {
38- return new OpaqueImpl (bytes .clone ());
43+ return new OpaqueImmutableImpl (bytes .clone ());
3944 }
4045
4146 /**
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}.
4362 *
4463 * @param buf The buffer.
4564 * @param length The number of bytes.
@@ -49,7 +68,24 @@ static Opaque forBytes(ByteBuffer buf, int length) {
4968 byte [] bytes = new byte [length ];
5069 buf .get (bytes );
5170
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 );
5389 }
5490
5591 /**
@@ -102,6 +138,13 @@ static boolean defaultEquals(Opaque obj, Object other) {
102138 */
103139 String toBase64 ();
104140
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+
105148 /**
106149 * Writes the bytes of this {@link Opaque} to the given {@link ByteBuffer}.
107150 *
@@ -131,11 +174,10 @@ default void putBytes(ByteBuffer buf) {
131174 @ Override
132175 boolean equals (Object o );
133176
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 ;
137179
138- private OpaqueImpl (byte [] opaque ) {
180+ OpaqueImpl (byte [] opaque ) {
139181 _opaque = opaque ;
140182 }
141183
@@ -145,21 +187,22 @@ public byte[] toBytes() {
145187 }
146188
147189 @ 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 );
153192 }
154193
155194 @ 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 );
158201 }
159202
160203 @ Override
161- public int hashCode ( ) {
162- return Arrays . hashCode (_opaque );
204+ public void putBytes ( ByteBuffer buf ) {
205+ buf . put (_opaque );
163206 }
164207
165208 @ Override
@@ -173,6 +216,19 @@ public boolean equals(Object o) {
173216
174217 if (o instanceof OpaqueImpl ) {
175218 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 ;
176232 } else {
177233 return Arrays .equals (_opaque , ((Opaque ) o ).toBytes ());
178234 }
@@ -192,5 +248,125 @@ public String toString() {
192248 public int numBytes () {
193249 return _opaque .length ;
194250 }
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+ }
195371 }
196372}
0 commit comments