Skip to content

Commit 01eea28

Browse files
authored
Merge pull request #155 from kohlschuetter/ck/Inode3
Opaque: Add support for mutable byte buffers and arrays
2 parents 490b26e + 334d339 commit 01eea28

File tree

4 files changed

+280
-40
lines changed

4 files changed

+280
-40
lines changed

core/src/main/java/org/dcache/nfs/util/Opaque.java

Lines changed: 195 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,43 @@
2222
import java.nio.ByteBuffer;
2323
import java.util.Arrays;
2424
import 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
*/
3035
public 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
}

core/src/main/java/org/dcache/nfs/v4/FileTracker.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ public OpenRecord addOpen(NFS4Client client, StateOwner owner, Inode inode, int
242242
// client explicitly requested write delegation
243243
boolean wantWriteDelegation = (shareAccess & nfs4_prot.OPEN4_SHARE_ACCESS_WANT_WRITE_DELEG) != 0;
244244

245-
Opaque fileId = inode.getFileIdKey();
245+
final Opaque fileId = inode.getFileIdKey().toImmutableOpaque();
246246
Lock lock = filesLock.get(fileId);
247247
lock.lock();
248248
try {

core/src/main/java/org/dcache/nfs/v4/nlm/SimpleLm.java

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -58,62 +58,51 @@ public SimpleLm(int concurrency) {
5858
/**
5959
* Exclusive lock on objects locks.
6060
*/
61-
private final ConcurrentHashMap<String, List<NlmLock>> locks = new ConcurrentHashMap<>();
61+
private final ConcurrentHashMap<Opaque, List<NlmLock>> locks = new ConcurrentHashMap<>();
6262

6363
@Override
6464
protected Lock getObjectLock(Opaque objId) {
65-
String key = toKey(objId);
66-
return objLock.get(key);
65+
return objLock.get(objId);
6766
}
6867

6968
@Override
7069
protected Collection<NlmLock> getActiveLocks(Opaque objId) {
71-
String key = toKey(objId);
72-
return locks.getOrDefault(key, Collections.emptyList());
70+
return locks.getOrDefault(objId, Collections.emptyList());
7371
}
7472

7573
@Override
7674
protected void add(Opaque objId, NlmLock lock) {
77-
String key = toKey(objId);
78-
Collection<NlmLock> l = locks.computeIfAbsent(key, k -> new ArrayList<>());
75+
Collection<NlmLock> l = locks.computeIfAbsent(objId.toImmutableOpaque(), k -> new ArrayList<>());
7976
l.add(lock);
8077
}
8178

8279
@Override
8380
protected boolean remove(Opaque objId, NlmLock lock) {
84-
String key = toKey(objId);
85-
Collection<NlmLock> l = locks.get(key);
81+
Collection<NlmLock> l = locks.get(objId);
8682
boolean isRemoved = false;
8783
if (l != null) {
8884
isRemoved = l.remove(lock);
8985
if (l.isEmpty()) {
90-
locks.remove(key);
86+
locks.remove(objId);
9187
}
9288
}
9389
return isRemoved;
9490
}
9591

9692
@Override
9793
protected void addAll(Opaque objId, Collection<NlmLock> locks) {
98-
String key = toKey(objId);
99-
Collection<NlmLock> l = this.locks.computeIfAbsent(key, k -> new ArrayList<>());
94+
Collection<NlmLock> l = this.locks.computeIfAbsent(objId.toImmutableOpaque(), k -> new ArrayList<>());
10095
l.addAll(locks);
10196
}
10297

10398
@Override
10499
protected void removeAll(Opaque objId, Collection<NlmLock> locks) {
105-
String key = toKey(objId);
106-
Collection<NlmLock> l = this.locks.get(key);
100+
Collection<NlmLock> l = this.locks.get(objId);
107101
if (l != null) {
108102
l.removeAll(locks);
109103
if (l.isEmpty()) {
110-
this.locks.remove(key);
104+
this.locks.remove(objId);
111105
}
112106
}
113107
}
114-
115-
private final String toKey(Opaque objId) {
116-
return objId.toBase64();
117-
}
118-
119108
}

0 commit comments

Comments
 (0)