This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 8599116a82 Fix Json5 collection freeze-bypass and standardize
Unmodifiable inner-class names
8599116a82 is described below
commit 8599116a82313e7b2ef8d2b6aa4e533d4c873f6e
Author: James Bognar <[email protected]>
AuthorDate: Sat Jul 25 08:20:45 2026 -0400
Fix Json5 collection freeze-bypass and standardize Unmodifiable inner-class
names
Json5Map/Json5List's Unmodifiable variants overrode only a subset of
mutators,
so a frozen instance could still be mutated (clear, putAll, putIfAbsent,
compute*, merge, replace*, the keySet()/entrySet()/values() views,
add(Object),
addAll, remove(Object), iterator-remove, sort, etc.). Extended both override
sets to the full MarshalledMap/MarshalledList "D4" surface so the entire
mutation surface throws.
Also standardized the marshall-family frozen-variant inner classes onto the
project's nested Unmodifiable convention (X.Unmodifiable extends X),
renaming
the legacy UnmodifiableXxx names in Json5Map, Json5List, JsonMap, JsonList,
MarshalledMap, and MarshalledList. Private-static, internal-only renames
with
no behavior or API change.
---
.../juneau/marshall/collections/JsonList.java | 8 +-
.../juneau/marshall/collections/JsonMap.java | 8 +-
.../marshall/collections/MarshalledList.java | 8 +-
.../juneau/marshall/collections/MarshalledMap.java | 8 +-
.../apache/juneau/marshall/json5/Json5List.java | 177 ++++++++++++++++++++-
.../org/apache/juneau/marshall/json5/Json5Map.java | 78 ++++++++-
.../juneau/collections/MarshalledMap_Test.java | 2 +-
.../marshall/collections/MarshalledMap_Test.java | 2 +-
.../juneau/marshall/json5/Json5List_Test.java | 47 ++++++
.../juneau/marshall/json5/Json5Map_Test.java | 33 ++++
10 files changed, 345 insertions(+), 26 deletions(-)
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/collections/JsonList.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/collections/JsonList.java
index 7653e35190..32810b86ec 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/collections/JsonList.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/collections/JsonList.java
@@ -104,13 +104,13 @@ public class JsonList extends MarshalledList {
@SuppressWarnings({
"java:S110" // Inner class has many fields, acceptable for
collection implementation
})
- private static class UnmodifiableJsonList extends JsonList {
+ private static class Unmodifiable extends JsonList {
private static final long serialVersionUID = 1L;
@SuppressWarnings({
"synthetic-access" // Access to outer class members is
intentional
})
- UnmodifiableJsonList(JsonList contents) {
+ Unmodifiable(JsonList contents) {
if (nn(contents))
contents.forEach(super::add);
}
@@ -712,9 +712,9 @@ public class JsonList extends MarshalledList {
@Override /* Overridden from MarshalledList */
public JsonList unmodifiable() {
- if (this instanceof UnmodifiableJsonList this2)
+ if (this instanceof Unmodifiable this2)
return this2;
- return new UnmodifiableJsonList(this);
+ return new Unmodifiable(this);
}
/**
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/collections/JsonMap.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/collections/JsonMap.java
index f8b1fe2dd0..9ed4940e2e 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/collections/JsonMap.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/collections/JsonMap.java
@@ -101,13 +101,13 @@ public class JsonMap extends MarshalledMap {
"java:S2160", // equals() / hashCode() inherited from JsonMap;
map equality is element-based
"java:S110" // Inheritance depth is intentional in the
marshalled-map hierarchy.
})
- private static class UnmodifiableJsonMap extends JsonMap {
+ private static class Unmodifiable extends JsonMap {
private static final long serialVersionUID = 1L;
@SuppressWarnings({
"synthetic-access" // Access to outer class members is
intentional
})
- UnmodifiableJsonMap(JsonMap contents) {
+ Unmodifiable(JsonMap contents) {
if (nn(contents))
contents.forEach(super::put);
}
@@ -714,9 +714,9 @@ public class JsonMap extends MarshalledMap {
@Override /* Overridden from MarshalledMap */
public JsonMap unmodifiable() {
- if (this instanceof UnmodifiableJsonMap this2)
+ if (this instanceof Unmodifiable this2)
return this2;
- return new UnmodifiableJsonMap(this);
+ return new Unmodifiable(this);
}
/**
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/collections/MarshalledList.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/collections/MarshalledList.java
index d14346e83a..64d239d967 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/collections/MarshalledList.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/collections/MarshalledList.java
@@ -58,13 +58,13 @@ public class MarshalledList extends LinkedList<Object> {
@SuppressWarnings({
"java:S110" // Inner class has many fields, acceptable for
collection implementation
})
- private static class UnmodifiableMarshalledList extends MarshalledList {
+ private static class Unmodifiable extends MarshalledList {
private static final long serialVersionUID = 1L;
@SuppressWarnings({
"synthetic-access" // Access to outer class members is
intentional
})
- UnmodifiableMarshalledList(MarshalledList contents) {
+ Unmodifiable(MarshalledList contents) {
if (nn(contents))
contents.forEach(super::add);
}
@@ -876,9 +876,9 @@ public class MarshalledList extends LinkedList<Object> {
* @return An unmodifiable copy of this list if it's modifiable, or
this list if it is already unmodifiable.
*/
public MarshalledList unmodifiable() {
- if (this instanceof UnmodifiableMarshalledList this2)
+ if (this instanceof Unmodifiable this2)
return this2;
- return new UnmodifiableMarshalledList(this);
+ return new Unmodifiable(this);
}
private PathTraversal getPathTraversal() {
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/collections/MarshalledMap.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/collections/MarshalledMap.java
index d83138321e..a15876e611 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/collections/MarshalledMap.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/collections/MarshalledMap.java
@@ -63,13 +63,13 @@ public class MarshalledMap extends
LinkedHashMap<String,Object> {
@SuppressWarnings({
"java:S2160" // equals() / hashCode() inherited from
MarshalledMap; map equality is element-based
})
- private static class UnmodifiableMarshalledMap extends MarshalledMap {
+ private static class Unmodifiable extends MarshalledMap {
private static final long serialVersionUID = 1L;
@SuppressWarnings({
"synthetic-access" // Access to outer class members is
intentional
})
- UnmodifiableMarshalledMap(MarshalledMap contents) {
+ Unmodifiable(MarshalledMap contents) {
if (nn(contents))
contents.forEach(super::put);
}
@@ -1519,9 +1519,9 @@ public class MarshalledMap extends
LinkedHashMap<String,Object> {
* @return An unmodifiable copy of this map if it's modifiable, or this
map if it is already unmodifiable.
*/
public MarshalledMap unmodifiable() {
- if (this instanceof UnmodifiableMarshalledMap this2)
+ if (this instanceof Unmodifiable this2)
return this2;
- return new UnmodifiableMarshalledMap(this);
+ return new Unmodifiable(this);
}
/**
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/json5/Json5List.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/json5/Json5List.java
index 5f5ab6d2bf..5ad958f423 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/json5/Json5List.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/json5/Json5List.java
@@ -70,34 +70,203 @@ public class Json5List extends MarshalledList {
@SuppressWarnings({
"java:S110" // Inner class has many fields, acceptable for
collection implementation
})
- private static class UnmodifiableJson5List extends Json5List {
+ private static class Unmodifiable extends Json5List {
private static final long serialVersionUID = 1L;
@SuppressWarnings({
"synthetic-access" // Access to outer class members is
intentional
})
- UnmodifiableJson5List(Json5List contents) {
+ Unmodifiable(Json5List contents) {
if (nn(contents))
contents.forEach(super::add);
}
+ @Override /* Overridden from List */
+ public boolean add(Object object) {
+ throw uoroex();
+ }
+
@Override /* Overridden from List */
public void add(int location, Object object) {
throw uoroex();
}
+ @Override /* Overridden from List */
+ public boolean addAll(Collection<?> c) {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from List */
+ public boolean addAll(int location, Collection<?> c) {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from Deque */
+ public void addFirst(Object object) {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from Deque */
+ public void addLast(Object object) {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from List */
+ public void clear() {
+ throw uoroex();
+ }
+
@Override
public boolean isUnmodifiable() { return true; }
+ @Override /* Overridden from List */
+ public Iterator<Object> iterator() {
+ return readOnlyIterator(super.iterator());
+ }
+
+ @Override /* Overridden from List */
+ public ListIterator<Object> listIterator() {
+ return readOnlyListIterator(super.listIterator());
+ }
+
+ @Override /* Overridden from List */
+ public ListIterator<Object> listIterator(int location) {
+ return
readOnlyListIterator(super.listIterator(location));
+ }
+
+ @Override /* Overridden from Queue */
+ public boolean offer(Object object) {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from Deque */
+ public boolean offerFirst(Object object) {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from Deque */
+ public boolean offerLast(Object object) {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from Queue */
+ public Object poll() {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from Deque */
+ public Object pollFirst() {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from Deque */
+ public Object pollLast() {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from Deque */
+ public Object pop() {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from Deque */
+ public void push(Object object) {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from Queue */
+ public Object remove() {
+ throw uoroex();
+ }
+
@Override /* Overridden from List */
public Object remove(int location) {
throw uoroex();
}
+ @Override /* Overridden from List */
+ public boolean remove(Object object) {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from List */
+ public boolean removeAll(Collection<?> c) {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from Deque */
+ public Object removeFirst() {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from Deque */
+ public boolean removeFirstOccurrence(Object object) {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from Collection */
+ public boolean removeIf(Predicate<? super Object> filter) {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from Deque */
+ public Object removeLast() {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from Deque */
+ public boolean removeLastOccurrence(Object object) {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from List */
+ public void replaceAll(UnaryOperator<Object> operator) {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from List */
+ public boolean retainAll(Collection<?> c) {
+ throw uoroex();
+ }
+
@Override /* Overridden from List */
public Object set(int location, Object object) {
throw uoroex();
}
+
+ @Override /* Overridden from List */
+ public void sort(Comparator<? super Object> c) {
+ throw uoroex();
+ }
+
+ @Override /* Overridden from List */
+ public List<Object> subList(int start, int end) {
+ return u(new ArrayList<>(this).subList(start, end));
+ }
+
+ private static Iterator<Object>
readOnlyIterator(Iterator<Object> it) {
+ return new Iterator<>() {
+ @Override public boolean hasNext() { return
it.hasNext(); }
+ @Override public Object next() { return
it.next(); }
+ @Override public void remove() { throw
uoroex(); }
+ @Override public void
forEachRemaining(Consumer<? super Object> action) {
it.forEachRemaining(action); }
+ };
+ }
+
+ private static ListIterator<Object>
readOnlyListIterator(ListIterator<Object> it) {
+ return new ListIterator<>() {
+ @Override public boolean hasNext() { return
it.hasNext(); }
+ @Override public Object next() { return
it.next(); }
+ @Override public boolean hasPrevious() { return
it.hasPrevious(); }
+ @Override public Object previous() { return
it.previous(); }
+ @Override public int nextIndex() { return
it.nextIndex(); }
+ @Override public int previousIndex() { return
it.previousIndex(); }
+ @Override public void remove() { throw
uoroex(); }
+ @Override public void set(Object e) { throw
uoroex(); }
+ @Override public void add(Object e) { throw
uoroex(); }
+ @Override public void
forEachRemaining(Consumer<? super Object> action) {
it.forEachRemaining(action); }
+ };
+ }
}
private static final long serialVersionUID = 1L;
@@ -443,9 +612,9 @@ public class Json5List extends MarshalledList {
@Override /* Overridden from MarshalledList */
public Json5List unmodifiable() {
- if (this instanceof UnmodifiableJson5List this2)
+ if (this instanceof Unmodifiable this2)
return this2;
- return new UnmodifiableJson5List(this);
+ return new Unmodifiable(this);
}
/**
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/json5/Json5Map.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/json5/Json5Map.java
index 939021d246..1a63d2939d 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/json5/Json5Map.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/json5/Json5Map.java
@@ -67,29 +67,99 @@ public class Json5Map extends MarshalledMap {
"java:S2160", // equals() / hashCode() inherited from Json5Map;
map equality is element-based
"java:S110" // Inheritance depth inherited from MarshalledMap
-> LinkedHashMap chain; intentional
})
- private static class UnmodifiableJson5Map extends Json5Map {
+ private static class Unmodifiable extends Json5Map {
private static final long serialVersionUID = 1L;
@SuppressWarnings({
"synthetic-access" // Access to outer class members is
intentional
})
- UnmodifiableJson5Map(Json5Map contents) {
+ Unmodifiable(Json5Map contents) {
if (nn(contents))
contents.forEach(super::put);
}
+ @Override
+ public void clear() {
+ throw uoroex();
+ }
+
+ @Override
+ public Object compute(String key, BiFunction<? super String,?
super Object,?> remappingFunction) {
+ throw uoroex();
+ }
+
+ @Override
+ public Object computeIfAbsent(String key, Function<? super
String,?> mappingFunction) {
+ throw uoroex();
+ }
+
+ @Override
+ public Object computeIfPresent(String key, BiFunction<? super
String,? super Object,?> remappingFunction) {
+ throw uoroex();
+ }
+
+ @Override
+ public Set<Map.Entry<String,Object>> entrySet() {
+ return u(super.entrySet());
+ }
+
@Override
public boolean isUnmodifiable() { return true; }
+ @Override
+ public Set<String> keySet() {
+ return u(super.keySet());
+ }
+
+ @Override
+ public Object merge(String key, Object value, BiFunction<?
super Object,? super Object,?> remappingFunction) {
+ throw uoroex();
+ }
+
@Override
public Object put(String key, Object val) {
throw uoroex();
}
+ @Override
+ public void putAll(Map<? extends String,?> m) {
+ throw uoroex();
+ }
+
+ @Override
+ public Object putIfAbsent(String key, Object value) {
+ throw uoroex();
+ }
+
@Override
public Object remove(Object key) {
throw uoroex();
}
+
+ @Override
+ public boolean remove(Object key, Object value) {
+ throw uoroex();
+ }
+
+ @Override
+ public Object replace(String key, Object value) {
+ throw uoroex();
+ }
+
+ @Override
+ public boolean replace(String key, Object oldValue, Object
newValue) {
+ throw uoroex();
+ }
+
+ @Override
+ public void replaceAll(BiFunction<? super String,? super
Object,?> function) {
+ throw uoroex();
+ }
+
+ @Override
+ public Collection<Object> values() {
+ return
Collections.unmodifiableCollection(super.values());
+ }
}
private static final long serialVersionUID = 1L;
@@ -549,9 +619,9 @@ public class Json5Map extends MarshalledMap {
@Override /* Overridden from MarshalledMap */
public Json5Map unmodifiable() {
- if (this instanceof UnmodifiableJson5Map this2)
+ if (this instanceof Unmodifiable this2)
return this2;
- return new UnmodifiableJson5Map(this);
+ return new Unmodifiable(this);
}
/**
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/collections/MarshalledMap_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/collections/MarshalledMap_Test.java
index 4878ecbb7b..f30c9e1347 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/collections/MarshalledMap_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/collections/MarshalledMap_Test.java
@@ -774,7 +774,7 @@ class MarshalledMap_Test extends TestBase {
}
@Test void b81_unmodifiableConstructorWithNull() {
- // UnmodifiableMarshalledMap is private but reachable via
.unmodifiable().
+ // Unmodifiable is private but reachable via .unmodifiable().
// Construct from a null source by going through the public API.
var m = MarshalledMap.create().unmodifiable();
assertTrue(m.isUnmodifiable());
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/collections/MarshalledMap_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/collections/MarshalledMap_Test.java
index 3d696311d1..a9a93186f4 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/collections/MarshalledMap_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/collections/MarshalledMap_Test.java
@@ -768,7 +768,7 @@ class MarshalledMap_Test extends TestBase {
}
@Test void b81_unmodifiableConstructorWithNull() {
- // UnmodifiableMarshalledMap is private but reachable via
.unmodifiable().
+ // Unmodifiable is private but reachable via .unmodifiable().
// Construct from a null source by going through the public API.
var m = MarshalledMap.create().unmodifiable();
assertTrue(m.isUnmodifiable());
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/json5/Json5List_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/json5/Json5List_Test.java
index 71d03170a2..df4cdda520 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/json5/Json5List_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/json5/Json5List_Test.java
@@ -20,6 +20,7 @@ import static org.apache.juneau.commons.utils.Shorts.*;
import static org.junit.jupiter.api.Assertions.*;
import java.io.*;
+import java.util.*;
import org.apache.juneau.*;
import org.apache.juneau.marshall.collections.*;
@@ -231,4 +232,50 @@ class Json5List_Test extends TestBase {
assertEquals(3, l.size());
assertEquals("a", l.getString(0));
}
+
+ @SuppressWarnings({
+ "java:S5778", // Lambda intentionally calls multiple throwing
methods to test compound failure scenarios.
+ "java:S5961" // High assertion count is acceptable in a
comprehensive data-driven mutator-surface test.
+ })
+ @Test void a20_unmodifiableFullMutatorSurfaceThrows() {
+ // Regression: Json5List.Unmodifiable originally overrode only
add(int,·)/remove(int)/set(int,·), leaving
+ // every other List/Deque mutator (and iterator/listIterator
remove) able to bypass the freeze.
+ var l = Json5List.of("a", "b", "c").unmodifiable();
+ assertThrows(UnsupportedOperationException.class, () ->
l.add(0, "x"));
+ assertThrows(UnsupportedOperationException.class, () ->
l.add("x"));
+ assertThrows(UnsupportedOperationException.class, () ->
l.remove(0));
+ assertThrows(UnsupportedOperationException.class, () ->
l.remove("a"));
+ assertThrows(UnsupportedOperationException.class, () ->
l.set(0, "x"));
+ assertThrows(UnsupportedOperationException.class, () ->
l.addAll(List.of("x")));
+ assertThrows(UnsupportedOperationException.class, () ->
l.addAll(0, List.of("x")));
+ assertThrows(UnsupportedOperationException.class, () ->
l.removeAll(List.of("a")));
+ assertThrows(UnsupportedOperationException.class, () ->
l.retainAll(List.of("a")));
+ assertThrows(UnsupportedOperationException.class, l::clear);
+ assertThrows(UnsupportedOperationException.class, () ->
l.addFirst("x"));
+ assertThrows(UnsupportedOperationException.class, () ->
l.addLast("x"));
+ assertThrows(UnsupportedOperationException.class,
l::removeFirst);
+ assertThrows(UnsupportedOperationException.class,
l::removeLast);
+ assertThrows(UnsupportedOperationException.class, () ->
l.removeFirstOccurrence("a"));
+ assertThrows(UnsupportedOperationException.class, () ->
l.removeLastOccurrence("a"));
+ assertThrows(UnsupportedOperationException.class, () ->
l.offer("x"));
+ assertThrows(UnsupportedOperationException.class, () ->
l.offerFirst("x"));
+ assertThrows(UnsupportedOperationException.class, () ->
l.offerLast("x"));
+ assertThrows(UnsupportedOperationException.class, l::poll);
+ assertThrows(UnsupportedOperationException.class, l::pollFirst);
+ assertThrows(UnsupportedOperationException.class, l::pollLast);
+ assertThrows(UnsupportedOperationException.class, l::pop);
+ assertThrows(UnsupportedOperationException.class, () ->
l.push("x"));
+ assertThrows(UnsupportedOperationException.class, () ->
l.removeIf(o -> true));
+ assertThrows(UnsupportedOperationException.class, () ->
l.replaceAll(o -> "x"));
+ assertThrows(UnsupportedOperationException.class, () ->
l.sort((a, b) -> 0));
+ var it = l.iterator();
+ it.next();
+ assertThrows(UnsupportedOperationException.class, it::remove);
+ var lit = l.listIterator();
+ lit.next();
+ assertThrows(UnsupportedOperationException.class, lit::remove);
+ assertThrows(UnsupportedOperationException.class, () ->
lit.add("x"));
+ assertThrows(UnsupportedOperationException.class, () ->
lit.set("x"));
+ assertEquals(3, l.size());
+ }
}
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/json5/Json5Map_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/json5/Json5Map_Test.java
index 0b01b3709b..27d26fcfb1 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/json5/Json5Map_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/json5/Json5Map_Test.java
@@ -20,6 +20,7 @@ import static org.apache.juneau.commons.utils.Shorts.*;
import static org.junit.jupiter.api.Assertions.*;
import java.io.*;
+import java.util.*;
import org.apache.juneau.*;
import org.apache.juneau.marshall.collections.*;
@@ -235,4 +236,36 @@ class Json5Map_Test extends TestBase {
Object inner = ((Json5Map)o).get("b");
assertTrue(inner instanceof Json5List, "Expected Json5List for
nested array, got " + cn(inner));
}
+
+ @SuppressWarnings({
+ "java:S5778" // Lambda intentionally calls multiple throwing
methods to test compound failure scenarios.
+ })
+ @Test void a23_unmodifiableFullMutatorSurfaceThrows() {
+ // Regression: Json5Map.Unmodifiable originally overrode only
put()/remove(Object), leaving every
+ // other Map mutator (and the keySet()/entrySet()/values()
collection views) able to bypass the freeze.
+ var m = Json5Map.of("a", 1).unmodifiable();
+ assertThrows(UnsupportedOperationException.class, () ->
m.put("b", 2));
+ assertThrows(UnsupportedOperationException.class, () ->
m.remove("a"));
+ assertThrows(UnsupportedOperationException.class, () ->
m.putAll(Map.of("b", 2)));
+ assertThrows(UnsupportedOperationException.class, m::clear);
+ assertThrows(UnsupportedOperationException.class, () ->
m.putIfAbsent("b", 2));
+ assertThrows(UnsupportedOperationException.class, () ->
m.remove("a", 1));
+ assertThrows(UnsupportedOperationException.class, () ->
m.replace("a", 2));
+ assertThrows(UnsupportedOperationException.class, () ->
m.replace("a", 1, 2));
+ assertThrows(UnsupportedOperationException.class, () ->
m.replaceAll((k, v) -> 99));
+ assertThrows(UnsupportedOperationException.class, () ->
m.compute("a", (k, v) -> 99));
+ assertThrows(UnsupportedOperationException.class, () ->
m.computeIfAbsent("b", k -> 99));
+ assertThrows(UnsupportedOperationException.class, () ->
m.computeIfPresent("a", (k, v) -> 99));
+ assertThrows(UnsupportedOperationException.class, () ->
m.merge("a", 99, (v1, v2) -> 99));
+ var entryIt = m.entrySet().iterator();
+ entryIt.next();
+ assertThrows(UnsupportedOperationException.class,
entryIt::remove);
+ var keyIt = m.keySet().iterator();
+ keyIt.next();
+ assertThrows(UnsupportedOperationException.class,
keyIt::remove);
+ var valIt = m.values().iterator();
+ valIt.next();
+ assertThrows(UnsupportedOperationException.class,
valIt::remove);
+ assertEquals(1, m.size());
+ }
}