You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by bd...@apache.org on 2021/06/21 16:45:31 UTC

[sling-org-apache-sling-graphql-core] branch SLING-10502/lazy-loading created (now 7aea4ef)

This is an automated email from the ASF dual-hosted git repository.

bdelacretaz pushed a change to branch SLING-10502/lazy-loading
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-graphql-core.git.


      at 7aea4ef  SLING-10502 - lazy loading helpers

This branch includes the following new commits:

     new 7aea4ef  SLING-10502 - lazy loading helpers

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[sling-org-apache-sling-graphql-core] 01/01: SLING-10502 - lazy loading helpers

Posted by bd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

bdelacretaz pushed a commit to branch SLING-10502/lazy-loading
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-graphql-core.git

commit 7aea4ef95f4e1e79df341f04399f9c0123ab9519
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Mon Jun 21 18:42:53 2021 +0200

    SLING-10502 - lazy loading helpers
---
 .../helpers/layzloading/LazyLoadingField.java      |  44 +++
 .../helpers/layzloading/LazyLoadingMap.java        | 171 ++++++++++++
 .../graphql/helpers/layzloading/package-info.java  |  26 ++
 .../helpers/lazyloading/LazyLoadingFieldTest.java  |  65 +++++
 .../helpers/lazyloading/LazyLoadingMapTest.java    | 300 +++++++++++++++++++++
 5 files changed, 606 insertions(+)

diff --git a/src/main/java/org/apache/sling/graphql/helpers/layzloading/LazyLoadingField.java b/src/main/java/org/apache/sling/graphql/helpers/layzloading/LazyLoadingField.java
new file mode 100644
index 0000000..83ce1f6
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/helpers/layzloading/LazyLoadingField.java
@@ -0,0 +1,44 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+package org.apache.sling.graphql.helpers.layzloading;
+
+import java.util.function.Supplier;
+
+/** Helper for a single lazy-loading value */
+public class LazyLoadingField<T> {
+    private Supplier<T> supplier;
+    private T value;
+
+    public LazyLoadingField(Supplier<T> supplier) {
+        this.supplier = supplier;
+    }
+
+    public T get() {
+        if(value == null) {
+            synchronized(this) {
+                if(value == null && supplier != null) {
+                    value = supplier.get();
+                }
+                supplier = null;
+            }
+        }
+        return value;
+    }
+}
diff --git a/src/main/java/org/apache/sling/graphql/helpers/layzloading/LazyLoadingMap.java b/src/main/java/org/apache/sling/graphql/helpers/layzloading/LazyLoadingMap.java
new file mode 100644
index 0000000..1a70e62
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/helpers/layzloading/LazyLoadingMap.java
@@ -0,0 +1,171 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+package org.apache.sling.graphql.helpers.layzloading;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LazyLoadingMap<K, T> extends HashMap<K, T> {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final Map<K, Supplier<T>> suppliers = new HashMap<>();
+    private int suppliersCallCount;
+
+    /** Calls computeAll - should be avoided if possible */
+    @Override
+    public boolean equals(Object o) {
+        if(!(o instanceof LazyLoadingMap)) {
+            return false;
+        }
+        final LazyLoadingMap<?,?> other = (LazyLoadingMap<?,?>)o;
+
+        // Equality seems complicated to compute without this
+        computeAll();
+        return super.equals(other);
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() + suppliers.hashCode();
+    }
+
+    /** Adds a Supplier that provides a lazy loaded value 
+     *  @return this object, to be able to chain calls
+    */
+    public LazyLoadingMap<K, T> withSupplier(K key, Supplier<T> supplier) {
+        suppliers.put(key, supplier);
+        return this;
+    }
+
+    @Override
+    public T get(Object key) {
+        return lazyCompute(key);
+    }
+
+    @SuppressWarnings("unchecked")
+    private T lazyCompute(Object key) {
+        if(key == null) {
+            return null;
+        }
+        T value = super.get(key);
+        if(value == null) {
+            synchronized(this) {
+                if(value == null) {
+                    final Supplier<T> s = suppliers.remove(key);
+                    if(s != null) {
+                        suppliersCallCount++;
+                        value = s.get();
+                        super.put((K)key, value);
+                    }
+                }
+            }
+        }
+        return value;
+    }
+
+
+    /** This indicates how many Supplier calls have been made.
+     *  Can be useful in case of doubt, as several methods need
+     *  to call computeAll().
+     */
+    public int getSuppliersCallCount() {
+        return suppliersCallCount;
+    }
+
+    @Override
+    public T remove(Object key) {
+        synchronized(this) {
+            lazyCompute(key);
+            return super.remove(key);
+        }        
+    }
+
+    @Override
+    public void clear() {
+        synchronized(this) {
+            suppliers.clear();
+            super.clear();
+        }
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+        return super.containsKey(key) || suppliers.containsKey(key);
+    }
+
+    @Override
+    public int size() {
+        return keySet().size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return super.isEmpty() && suppliers.isEmpty();
+    }
+
+    @Override
+    public Set<K> keySet() {
+        final Set<K> result = new HashSet<>();
+        result.addAll(super.keySet());
+        result.addAll(suppliers.keySet());
+        return result;
+    }
+
+    /** Required for some methods that need all our values
+     *  Calling those methods should be avoided if possible
+     */
+    private void computeAll() {
+        log.info("computeAll called, all remaining lazy values will be evaluated now");
+        suppliers.entrySet().forEach(e -> {
+            if(!super.containsKey(e.getKey())) {
+                suppliersCallCount++;
+                put(e.getKey(), e.getValue().get());
+            }
+        });
+    }
+
+    /** Calls computeAll - should be avoided if possible */
+    @Override
+    public Collection<T> values() {
+        computeAll();
+        return super.values();
+    }
+
+    /** Calls computeAll - should be avoided if possible */
+    @Override
+    public Set<Entry<K, T>> entrySet() {
+        computeAll();
+        return super.entrySet();
+    }
+
+    /** Calls computeAll - should be avoided if possible */
+    @Override
+    public boolean containsValue(Object value) {
+        computeAll();
+        return super.containsValue(value);
+    }
+}
diff --git a/src/main/java/org/apache/sling/graphql/helpers/layzloading/package-info.java b/src/main/java/org/apache/sling/graphql/helpers/layzloading/package-info.java
new file mode 100644
index 0000000..b43f77a
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/helpers/layzloading/package-info.java
@@ -0,0 +1,26 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+ /**
+  * This package contains helpers which are independent of
+  * a specific implementation of the underlying graphQL engine.
+  */
+@Version("0.0.1")
+package org.apache.sling.graphql.helpers.layzloading;
+import org.osgi.annotation.versioning.Version;
diff --git a/src/test/java/org/apache/sling/graphql/helpers/lazyloading/LazyLoadingFieldTest.java b/src/test/java/org/apache/sling/graphql/helpers/lazyloading/LazyLoadingFieldTest.java
new file mode 100644
index 0000000..52608fe
--- /dev/null
+++ b/src/test/java/org/apache/sling/graphql/helpers/lazyloading/LazyLoadingFieldTest.java
@@ -0,0 +1,65 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.graphql.helpers.lazyloading;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+
+import org.apache.sling.graphql.helpers.layzloading.LazyLoadingField;
+import org.junit.Test;
+
+public class LazyLoadingFieldTest {
+    @Test
+    public void basicTest() {
+        final AtomicInteger nCalls = new AtomicInteger();
+
+        final Supplier<String> sup = () -> {
+            nCalls.incrementAndGet();
+            return UUID.randomUUID().toString();
+        };
+
+        final LazyLoadingField<String> f = new LazyLoadingField<>(sup);
+        assertEquals(0, nCalls.get());
+        final String firstValue = f.get();
+        assertEquals(1, nCalls.get());
+        for(int i=0; i < 42; i++) {
+            assertEquals(firstValue, f.get());
+        }
+        assertEquals(1, nCalls.get());
+    }
+
+    @Test
+    public void nullSupplier() {
+        final AtomicInteger nCalls = new AtomicInteger();
+        final Supplier<Double> nullSup = () -> {
+            nCalls.incrementAndGet();
+            return null;
+        };
+        final LazyLoadingField<Double> f = new LazyLoadingField<>(nullSup);
+        assertEquals(0, nCalls.get());
+        assertNull(f.get());
+        assertEquals(1, nCalls.get());
+        assertNull(f.get());
+        assertEquals(1, nCalls.get());
+    }
+}
diff --git a/src/test/java/org/apache/sling/graphql/helpers/lazyloading/LazyLoadingMapTest.java b/src/test/java/org/apache/sling/graphql/helpers/lazyloading/LazyLoadingMapTest.java
new file mode 100644
index 0000000..c886303
--- /dev/null
+++ b/src/test/java/org/apache/sling/graphql/helpers/lazyloading/LazyLoadingMapTest.java
@@ -0,0 +1,300 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.graphql.helpers.lazyloading;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Supplier;
+
+import org.apache.sling.graphql.helpers.layzloading.LazyLoadingMap;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LazyLoadingMapTest {
+    private static final String TEST_STRING = "Fritz Frisst etc. etc.";
+    private int counter;
+    private Supplier<String> lazyCounter = () -> "X" + String.valueOf(++counter);
+
+    @Before
+    public void setup() {
+        counter = 0;
+    }
+
+    @Test
+    public void basicTest() {
+        final LazyLoadingMap<Integer, String> map = new LazyLoadingMap<>();
+        assertNull(map.get(42));
+
+        map
+            .withSupplier(21, () -> UUID.randomUUID().toString())
+            .withSupplier(42, () -> TEST_STRING)
+        ;
+        assertEquals(0, map.getSuppliersCallCount());
+        assertEquals(TEST_STRING, map.get(42));
+        assertEquals(1, map.getSuppliersCallCount());
+        final String random = map.get(21);
+        assertNotNull(random);
+        assertEquals(random, map.get(21));
+        assertEquals(2, map.getSuppliersCallCount());
+    }
+
+    @Test
+    public void addAlsoWorks() {
+        final LazyLoadingMap<Integer, String> map = new LazyLoadingMap<>();
+        map.put(42, TEST_STRING);
+        assertEquals(TEST_STRING, map.get(42));
+        map.withSupplier(42, () -> "should not change");
+        assertEquals(TEST_STRING, map.get(42));
+        assertEquals(0, map.getSuppliersCallCount());
+    }
+
+    @Test
+    public void remove() {
+        final LazyLoadingMap<Integer, String> map = new LazyLoadingMap<>();
+        map.withSupplier(21, lazyCounter);
+        map.put(42, TEST_STRING);
+        assertEquals(2, map.size());
+        assertEquals(TEST_STRING, map.get(42));
+        assertEquals(TEST_STRING, map.remove(42));
+        assertNull(map.get(42));
+        assertEquals(1, map.size());
+        assertEquals(0, map.getSuppliersCallCount());
+
+        // Remove before and after computing
+        assertEquals(0, map.getSuppliersCallCount());
+        map.withSupplier(112, lazyCounter);
+        map.withSupplier(113, lazyCounter);
+        assertEquals("X1", map.get(113));
+        assertEquals("X1", map.remove(113));
+        assertEquals(1, map.getSuppliersCallCount());
+        assertEquals("X2", map.remove(112));
+        assertEquals(2, map.getSuppliersCallCount());
+    }
+
+    @Test
+    public void containsValueComputesEverything() {
+        final LazyLoadingMap<Integer, String> map = new LazyLoadingMap<>();
+        assertFalse(map.containsKey(42));
+        assertEquals(0, map.getSuppliersCallCount());
+
+        assertFalse(map.containsValue("X1"));
+        map.withSupplier(42, lazyCounter);
+        assertTrue(map.containsValue("X1"));
+
+        assertFalse(map.containsValue("X2"));
+        map.withSupplier(21, lazyCounter);
+        assertEquals(1, map.getSuppliersCallCount());
+        assertTrue(map.containsValue("X1"));
+        assertTrue(map.containsValue("X2"));
+        assertEquals(2, map.getSuppliersCallCount());
+
+        assertFalse(map.containsValue(TEST_STRING));
+        map.put(71, TEST_STRING);
+        map
+            .withSupplier(92, lazyCounter)
+            .withSupplier(93, lazyCounter)
+        ;
+        assertTrue(map.containsValue(TEST_STRING));
+        assertTrue(map.containsValue("X1"));
+        assertTrue(map.containsValue("X2"));
+        assertTrue(map.containsValue("X3"));
+        assertTrue(map.containsValue("X4"));
+        assertFalse(map.containsValue("X5"));
+
+        assertEquals(4, map.getSuppliersCallCount());
+    }
+
+    @Test
+    public void containsKey() {
+        final LazyLoadingMap<Integer, String> map = new LazyLoadingMap<>();
+        assertFalse(map.containsKey(42));
+        assertEquals(0, map.getSuppliersCallCount());
+
+        map.withSupplier(42, lazyCounter);
+        map.put(21, "nothing");
+
+        assertTrue(map.containsKey(42));
+        assertTrue(map.containsKey(21));
+        assertFalse(map.containsKey(22));
+
+        assertEquals(0, map.getSuppliersCallCount());
+    }
+
+    @Test
+    public void keySet() {
+        final LazyLoadingMap<Integer, String> map = new LazyLoadingMap<>();
+        assertEquals(0, map.keySet().size());
+        map.put(112, "nothing");
+        assertEquals(1, map.keySet().size());
+        map.withSupplier(21, lazyCounter);
+        map.withSupplier(42, lazyCounter);
+        map.withSupplier(110, lazyCounter);
+
+        assertEquals(0, map.getSuppliersCallCount());
+        map.get(42);
+        assertEquals(1, map.getSuppliersCallCount());
+
+        final Set<Integer> ks = map.keySet();
+        assertEquals(4, ks.size());
+        assertTrue(ks.contains(21));
+        assertTrue(ks.contains(42));
+        assertTrue(ks.contains(112));
+        assertTrue(ks.contains(110));
+        assertEquals(1, map.getSuppliersCallCount());
+    }
+
+    @Test
+    public void entrySet() {
+        final LazyLoadingMap<Integer, String> map = new LazyLoadingMap<>();
+        map.put(112, TEST_STRING);
+        map.withSupplier(21, lazyCounter);
+        map.withSupplier(42, lazyCounter);
+
+        final Set<String> toFind = new HashSet<>();
+        toFind.add(TEST_STRING);
+        toFind.add("X1");
+        toFind.add("X2");
+
+        assertEquals(3, toFind.size());
+        map.entrySet().forEach(e -> toFind.remove(e.getValue()));
+        assertEquals(0, toFind.size());
+        assertEquals(2, map.getSuppliersCallCount());
+    }
+
+    @Test
+    public void values() {
+        final LazyLoadingMap<Integer, String> map = new LazyLoadingMap<>();
+        map.put(112, TEST_STRING);
+        map.withSupplier(21, lazyCounter);
+        map.withSupplier(42, lazyCounter);
+
+        final Set<String> toFind = new HashSet<>();
+        toFind.add(TEST_STRING);
+        toFind.add("X1");
+        toFind.add("X2");
+
+        assertEquals(3, toFind.size());
+        assertEquals(0, map.getSuppliersCallCount());
+        map.values().forEach(v -> toFind.remove(v));
+        assertEquals(0, toFind.size());
+        assertEquals(2, map.getSuppliersCallCount());
+    }
+
+    @Test
+    public void isEmpty() {
+        final LazyLoadingMap<Integer, String> map = new LazyLoadingMap<>();
+        assertTrue(map.isEmpty());
+
+        map.put(112, TEST_STRING);
+        assertFalse(map.isEmpty());
+        map.withSupplier(42, lazyCounter);
+        assertFalse(map.isEmpty());
+
+        map.remove(112);
+        assertFalse(map.isEmpty());
+        map.remove(42);
+        assertTrue(map.isEmpty());
+
+        assertEquals(1, map.getSuppliersCallCount());
+    }
+
+    @Test
+    public void nullSupplier() {
+        final LazyLoadingMap<Integer, String> map = new LazyLoadingMap<>();
+        final Supplier<String> nullSup = () -> null;
+
+        map.withSupplier(42, nullSup);
+        assertEquals(0, map.getSuppliersCallCount());
+        assertEquals(1, map.size());
+
+        assertNull(map.get(42));
+        assertEquals(1, map.getSuppliersCallCount());
+        assertEquals(1, map.size());
+
+        assertNull(map.get(42));
+        assertEquals(1, map.getSuppliersCallCount());
+        assertEquals(1, map.size());
+    }
+
+    @Test
+    public void clear() {
+        final LazyLoadingMap<Integer, String> map = new LazyLoadingMap<>();
+        map.withSupplier(21, lazyCounter);
+        map.withSupplier(42, lazyCounter);
+        assertEquals("X1", map.get(42));
+        assertEquals(1, map.getSuppliersCallCount());
+        assertEquals(2, map.size());
+        map.clear();
+        assertEquals(0, map.size());
+        assertNull(map.get(42));
+        assertEquals(1, map.getSuppliersCallCount());
+    }
+
+    @Test
+    public void testHashCode() {
+        final LazyLoadingMap<Integer, String> map = new LazyLoadingMap<>();
+        int hc = map.hashCode();
+        map.put(21, TEST_STRING);
+        assertNotEquals(hc, hashCode());
+        hc = map.hashCode();
+        map.withSupplier(42, lazyCounter);
+        assertNotEquals(hc, hashCode());
+    }
+
+    @Test
+    public void testEquals() {
+        final Supplier<String> constant = () -> "Some String";
+        final LazyLoadingMap<Integer, String> A = new LazyLoadingMap<>();
+        final LazyLoadingMap<Integer, String> B = new LazyLoadingMap<>();
+        assertEquals(A, B);
+
+        A.withSupplier(42, constant);
+        A.put(21, TEST_STRING);
+        assertNotEquals(A, B);
+        assertEquals(1, A.getSuppliersCallCount());
+        assertEquals(0, B.getSuppliersCallCount());
+
+        B.withSupplier(42, constant);
+        assertNotEquals(A, B);
+        B.put(21, TEST_STRING);
+        assertEquals(B, A);
+        assertEquals(1, A.getSuppliersCallCount());
+        assertEquals(1, B.getSuppliersCallCount());
+    }
+
+    @Test
+    public void nullKey() {
+        final LazyLoadingMap<Integer, String> map = new LazyLoadingMap<>();
+        assertNull(map.get(null));
+    }
+
+    @Test
+    public void applesAndOranges() {
+        final LazyLoadingMap<Integer, String> map = new LazyLoadingMap<>();
+        assertNotEquals(map, "A string");
+    }
+}