Skip to content

Commit 7fb6c4e

Browse files
committed
Add putIfAbsent to MultiKeyMap
1 parent 821ecab commit 7fb6c4e

4 files changed

Lines changed: 85 additions & 0 deletions

File tree

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
> * **Impact**: Minimal - fixes inconsistent behavior and provides migration path through feature options
3333
> * **Rationale**: Eliminates confusion from mixed precision behavior and provides simple, memorable conversion rules
3434
> * Added `computeIfAbsent` support to `MultiKeyMap` for lazy value population
35+
> * Added `putIfAbsent` support to `MultiKeyMap` for atomic insert when key is missing or mapped to null
3536
3637
#### 3.6.0
3738
> * **Feature Enhancement**: Added comprehensive `java.awt.Color` conversion support to `Converter`:

src/main/java/com/cedarsoftware/util/MultiKeyMap.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1445,6 +1445,32 @@ public void putAll(Map<? extends Object, ? extends V> m) {
14451445
}
14461446
}
14471447

1448+
/**
1449+
* {@inheritDoc}
1450+
* <p>
1451+
* Uses a double-check locking pattern to avoid unnecessary synchronization
1452+
* when a value is already present. If the key is absent or currently mapped
1453+
* to {@code null}, the provided value is stored.
1454+
*
1455+
* @see Map#putIfAbsent(Object, Object)
1456+
*/
1457+
@Override
1458+
public V putIfAbsent(Object key, V value) {
1459+
V existing = get(key);
1460+
if (existing != null) {
1461+
return existing;
1462+
}
1463+
1464+
synchronized (writeLock) {
1465+
existing = get(key);
1466+
if (existing == null) {
1467+
put(key, value);
1468+
return null;
1469+
}
1470+
return existing;
1471+
}
1472+
}
1473+
14481474
/**
14491475
* {@inheritDoc}
14501476
* <p>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.cedarsoftware.util;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.util.Arrays;
6+
7+
import static org.junit.jupiter.api.Assertions.*;
8+
9+
/**
10+
* Tests for the putIfAbsent API on MultiKeyMap.
11+
*/
12+
class MultiKeyMapPutIfAbsentTest {
13+
14+
@Test
15+
void testPutOnAbsentKey() {
16+
MultiKeyMap<String> map = new MultiKeyMap<>(16);
17+
18+
assertNull(map.putIfAbsent("a", "value"));
19+
assertEquals("value", map.get("a"));
20+
}
21+
22+
@Test
23+
void testNoOverwriteWhenPresent() {
24+
MultiKeyMap<String> map = new MultiKeyMap<>(16);
25+
map.put("existing", "value");
26+
27+
assertEquals("value", map.putIfAbsent("existing", "new"));
28+
assertEquals("value", map.get("existing"));
29+
}
30+
31+
@Test
32+
void testReplaceNullValue() {
33+
MultiKeyMap<String> map = new MultiKeyMap<>(16);
34+
map.put("nullKey", (String) null);
35+
36+
assertNull(map.putIfAbsent("nullKey", "filled"));
37+
assertEquals("filled", map.get("nullKey"));
38+
}
39+
40+
@Test
41+
void testMultiKeyArrayAndCollection() {
42+
MultiKeyMap<String> map = new MultiKeyMap<>(16);
43+
44+
Object[] arrayKey = {"x", "y"};
45+
assertNull(map.putIfAbsent(arrayKey, "array"));
46+
assertEquals("array", map.get("x", "y"));
47+
48+
assertNull(map.putIfAbsent(Arrays.asList("a", "b"), "list"));
49+
assertEquals("list", map.get("a", "b"));
50+
}
51+
}

userguide.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2135,6 +2135,7 @@ A high-performance, thread-safe Map implementation that supports multi-dimension
21352135
- **Map compatible**: Works with Map interface for existing code
21362136
- **Escape hatch**: Force arrays to be single keys when needed
21372137
- **computeIfAbsent support**: Lazily populate values when keys are missing
2138+
- **putIfAbsent support**: Atomically add entries only when absent
21382139

21392140
### API Design Philosophy
21402141

@@ -2360,6 +2361,12 @@ String value = cache.computeIfAbsent(
23602361
k -> loadFromStore((Object[]) k));
23612362
```
23622363

2364+
**Atomic Insert with putIfAbsent:**
2365+
```java
2366+
MultiKeyMap<String> config = new MultiKeyMap<>();
2367+
config.putIfAbsent(new Object[]{"env", "prod"}, "prodConfig");
2368+
```
2369+
23632370
This implementation provides a clean, efficient alternative to composite key objects and nested Maps, with excellent performance characteristics and a developer-friendly API.
23642371

23652372
---

0 commit comments

Comments
 (0)