SLING-9312 - LazyBindings.entrySet: hashCode of binding values is calculated
* switched to collecting the entries into a HashMap
* added a test to track the number of hashCode calls; with the previous
implementation the test fails, with one extra hashCode call
diff --git a/src/main/java/org/apache/sling/api/scripting/LazyBindings.java b/src/main/java/org/apache/sling/api/scripting/LazyBindings.java
index 3dff1a6..4acb4e5 100644
--- a/src/main/java/org/apache/sling/api/scripting/LazyBindings.java
+++ b/src/main/java/org/apache/sling/api/scripting/LazyBindings.java
@@ -24,6 +24,7 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Stream;
import javax.script.Bindings;
@@ -114,11 +115,10 @@
@NotNull
@Override
public Set<Entry<String, Object>> entrySet() {
- HashSet<Entry<String, Object>> entrySet = new HashSet<>(super.entrySet());
- for (Map.Entry supplierEntry : suppliers.entrySet()) {
- entrySet.add(supplierEntry);
- }
- return Collections.unmodifiableSet(entrySet);
+ Map<String, Object> holder = new HashMap<>();
+ Stream.concat(super.entrySet().stream(), suppliers.entrySet().stream())
+ .forEach(entry -> holder.put(entry.getKey(), entry.getValue()));
+ return holder.entrySet();
}
@Override
diff --git a/src/test/java/org/apache/sling/api/scripting/LazyBindingsTest.java b/src/test/java/org/apache/sling/api/scripting/LazyBindingsTest.java
index 71c2894..f4bcf3d 100644
--- a/src/test/java/org/apache/sling/api/scripting/LazyBindingsTest.java
+++ b/src/test/java/org/apache/sling/api/scripting/LazyBindingsTest.java
@@ -25,6 +25,7 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import org.junit.After;
@@ -41,19 +42,16 @@
private static final int THE_ANSWER = 42;
private Set<String> usedSuppliers;
+ private Map<String, Integer> hashCodeCalls;
private LazyBindings lazyBindings;
- private final LazyBindings.Supplier supplier = new LazyBindings.Supplier() {
- @Override
- public Object get() {
- usedSuppliers.add(THE_QUESTION);
- return THE_ANSWER;
- }
- };
+ private TestSupplier supplier;
@Before
public void setUp() {
+ hashCodeCalls = new HashMap<>();
usedSuppliers = new HashSet<>();
+ supplier = new TestSupplier(THE_QUESTION, () -> THE_ANSWER);
final Map<String, LazyBindings.Supplier> supplierMap = new HashMap<>();
supplierMap.put(THE_QUESTION, supplier);
lazyBindings = new LazyBindings(supplierMap);
@@ -158,7 +156,13 @@
expectedEntrySet.add(new AbstractMap.SimpleEntry<>("a", 0));
expectedEntrySet.add(new AbstractMap.SimpleEntry<>(THE_QUESTION, supplier));
assertFalse(usedSuppliers.contains(THE_QUESTION));
+ supplier.trackHashCodeCalls = true;
+ assertEquals(2, lazyBindings.entrySet().size());
+ assertEquals("Unexpected hashCode call for the test supplier.", 0, (int) hashCodeCalls.get(THE_QUESTION));
assertEquals(expectedEntrySet, lazyBindings.entrySet());
+ // the equals implementation will have to call hashCode on the supplier, but the entrySet method should not, so we expect only
+ // one hashCode call
+ assertEquals(1, (int) hashCodeCalls.get(THE_QUESTION));
assertFalse(usedSuppliers.contains(THE_QUESTION));
}
@@ -203,4 +207,31 @@
assertFalse(usedSuppliers.contains(supplierName));
}
+ private class TestSupplier implements LazyBindings.Supplier {
+
+ private final String name;
+ private final LazyBindings.Supplier wrapped;
+ private boolean trackHashCodeCalls;
+
+ TestSupplier(String name, LazyBindings.Supplier wrapped) {
+ this.name = name;
+ this.wrapped = wrapped;
+ hashCodeCalls.putIfAbsent(name, 0);
+ }
+
+ @Override
+ public Object get() {
+ usedSuppliers.add(name);
+ return wrapped.get();
+ }
+
+ @Override
+ public int hashCode() {
+ if (trackHashCodeCalls) {
+ hashCodeCalls.compute(name, (s, integer) -> integer == null ? 0 : integer + 1);
+ }
+ return super.hashCode();
+ }
+ }
+
}