You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2019/11/06 14:16:38 UTC
[sling-org-apache-sling-api] 01/01: SLING-8737 - Add support for
lazily-evaluated bindings
This is an automated email from the ASF dual-hosted git repository.
radu pushed a commit to branch issue/SLING-8737
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-api.git
commit f2508de96b7e718862cf8bcc9b374145069a16f8
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Tue Nov 5 18:24:29 2019 +0100
SLING-8737 - Add support for lazily-evaluated bindings
* added a lazy bindings implementation in LazyBindings and made SlingBindings
extend it
---
.../apache/sling/api/scripting/LazyBindings.java | 174 +++++++++++++++++++
.../apache/sling/api/scripting/SlingBindings.java | 6 +-
.../apache/sling/api/scripting/package-info.java | 2 +-
.../sling/api/scripting/LazyBindingsTest.java | 193 +++++++++++++++++++++
4 files changed, 370 insertions(+), 5 deletions(-)
diff --git a/src/main/java/org/apache/sling/api/scripting/LazyBindings.java b/src/main/java/org/apache/sling/api/scripting/LazyBindings.java
new file mode 100644
index 0000000..c5a8030
--- /dev/null
+++ b/src/main/java/org/apache/sling/api/scripting/LazyBindings.java
@@ -0,0 +1,174 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.api.scripting;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import javax.script.Bindings;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * <p>
+ * The {@code LazyBindings} wraps another map and dynamically provides entries for the wrapped map through a map of {@link Supplier}s.
+ * </p>
+ * <p>
+ * When {@link #get(Object)} is called with a {@code key} that's not present in the wrapped map, then the {@link Supplier}s map will be
+ * queried and, if an entry exists for that key, the {@link Supplier}-generated value will be used to populate the wrapped map.
+ * </p>
+ * <p>
+ * While the {@link #keySet()} and {@link #containsKey(Object)} will also check the keys present in the {@link Supplier}s map, all other
+ * methods (e.g. {@link #values()}, {@link #containsValue(Object)}) will only deal with the contents of the wrapped map.
+ * <p>{@link #entrySet()} will however return a merged view of both the {@link Supplier}s and the wrapped map, so that copies to other
+ * {@code LazyBindings} maps work
+ * </p>
+ * <p>
+ * This class <b>does not provide any thread-safety guarantees</b>. If {@code this} {@code Bindings} map needs to be used in a concurrent
+ * setup it's the responsibility of the caller to synchronize access. The simplest way would be to wrap it through {@link
+ * Collections#synchronizedMap(Map)}.
+ * </p>
+ */
+public class LazyBindings extends HashMap<String, Object> implements Bindings {
+
+ private final Map<String, Supplier<?>> suppliers;
+
+ public LazyBindings() {
+ this(new HashMap<>(), Collections.emptyMap());
+ }
+
+ public LazyBindings(Map<String, Supplier<?>> suppliers) {
+ this(suppliers, Collections.emptyMap());
+ }
+
+ public LazyBindings(Map<String, Supplier<?>> suppliers, Map<String, Object> wrapped) {
+ super(wrapped);
+ this.suppliers = suppliers;
+ }
+
+
+ @Override
+ public Object put(String key, Object value) {
+ Object previous = super.get(key);
+ if (value instanceof Supplier<?>) {
+ suppliers.put(key, (Supplier<?>) value);
+ } else {
+ super.put(key, value);
+ }
+ return previous;
+ }
+
+ @Override
+ public void putAll(Map<? extends String, ?> toMerge) {
+ for (Entry<? extends String, ?> entry : toMerge.entrySet()) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ suppliers.clear();
+ }
+
+ @NotNull
+ @Override
+ public Set<String> keySet() {
+ Set<String> keySet = new HashSet<>(super.keySet());
+ if (!suppliers.isEmpty()) {
+ keySet.addAll(suppliers.keySet());
+ }
+ return Collections.unmodifiableSet(keySet);
+ }
+
+ @NotNull
+ @Override
+ public Collection<Object> values() {
+ return super.values();
+ }
+
+ @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);
+ }
+
+ @Override
+ public int size() {
+ Set<String> keys = new HashSet<>(super.keySet());
+ keys.addAll(suppliers.keySet());
+ return keys.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return size() == 0;
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return super.containsKey(key) || suppliers.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return super.containsValue(value);
+ }
+
+ @Override
+ public Object get(Object key) {
+ String k = key.toString();
+ if (!super.containsKey(k) && suppliers.containsKey(k)) {
+ Object value = suppliers.get(k).get();
+ super.put(k, value);
+ suppliers.remove(k);
+ }
+ return super.get(key);
+ }
+
+ @Override
+ public Object remove(Object key) {
+ Object previous = super.remove(key);
+ if (previous == null) {
+ Supplier<?> supplier = suppliers.remove(key);
+ if (supplier != null) {
+ return supplier.get();
+ }
+ }
+ return previous;
+ }
+
+ @Override
+ public Object getOrDefault(Object key, Object defaultValue) {
+ Object result = get(key);
+ if (result == null) {
+ return defaultValue;
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/org/apache/sling/api/scripting/SlingBindings.java b/src/main/java/org/apache/sling/api/scripting/SlingBindings.java
index 4212390..bdc32f7 100644
--- a/src/main/java/org/apache/sling/api/scripting/SlingBindings.java
+++ b/src/main/java/org/apache/sling/api/scripting/SlingBindings.java
@@ -20,14 +20,12 @@ package org.apache.sling.api.scripting;
import java.io.PrintWriter;
import java.io.Reader;
-import java.util.HashMap;
-
-import org.jetbrains.annotations.Nullable;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
+import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
/**
@@ -36,7 +34,7 @@ import org.slf4j.Logger;
* which <em>MUST</em> or <em>MAY</em> be provided for the script execution.
* Other variables may be define as callers see fit.
*/
-public class SlingBindings extends HashMap<String, Object> {
+public class SlingBindings extends LazyBindings {
private static final long serialVersionUID = 209505693646323450L;
diff --git a/src/main/java/org/apache/sling/api/scripting/package-info.java b/src/main/java/org/apache/sling/api/scripting/package-info.java
index 8cd75db..9e31fd1 100644
--- a/src/main/java/org/apache/sling/api/scripting/package-info.java
+++ b/src/main/java/org/apache/sling/api/scripting/package-info.java
@@ -17,7 +17,7 @@
* under the License.
*/
-@Version("2.3.3")
+@Version("2.4.0")
package org.apache.sling.api.scripting;
import org.osgi.annotation.versioning.Version;
diff --git a/src/test/java/org/apache/sling/api/scripting/LazyBindingsTest.java b/src/test/java/org/apache/sling/api/scripting/LazyBindingsTest.java
new file mode 100644
index 0000000..7ddb7ab
--- /dev/null
+++ b/src/test/java/org/apache/sling/api/scripting/LazyBindingsTest.java
@@ -0,0 +1,193 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.api.scripting;
+
+import java.util.AbstractMap;
+import java.util.Arrays;
+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.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class LazyBindingsTest {
+ private static final String THE_QUESTION = "What is The Answer to the Ultimate Question of Life, The Universe, and Everything?";
+ private static final int THE_ANSWER = 42;
+
+ private Set<String> usedSuppliers;
+ private LazyBindings lazyBindings;
+
+ private final Supplier<?> supplier = new Supplier() {
+ @Override
+ public Object get() {
+ usedSuppliers.add(THE_QUESTION);
+ return THE_ANSWER;
+ }
+ };
+
+ @Before
+ public void setUp() {
+ usedSuppliers = new HashSet<>();
+ final Map<String, Supplier<?>> supplierMap = new HashMap<>();
+ supplierMap.put(THE_QUESTION, supplier);
+ lazyBindings = new LazyBindings(supplierMap);
+ }
+
+ @After
+ public void tearDown() {
+ usedSuppliers = null;
+ lazyBindings = null;
+ }
+
+ @Test
+ public void testGet() {
+ assertFalse(usedSuppliers.contains(THE_QUESTION));
+ assertEquals(THE_ANSWER, lazyBindings.get(THE_QUESTION));
+ assertTrue(usedSuppliers.contains(THE_QUESTION));
+ assertNull(lazyBindings.get("none"));
+ }
+
+ @Test
+ public void testRemove() {
+ lazyBindings.put("a", 0);
+ assertNull(lazyBindings.remove("null"));
+ assertEquals(0, lazyBindings.remove("a"));
+ assertFalse(usedSuppliers.contains(THE_QUESTION));
+ assertEquals(THE_ANSWER, lazyBindings.remove(THE_QUESTION));
+ assertTrue(usedSuppliers.contains(THE_QUESTION));
+ }
+
+ @Test
+ public void testPut() {
+ assertFalse(usedSuppliers.contains(THE_QUESTION));
+ Object supplierProvidedValueReplacement = lazyBindings.put(THE_QUESTION, 43);
+ assertNull(supplierProvidedValueReplacement);
+ assertEquals(43, lazyBindings.get(THE_QUESTION));
+
+ lazyBindings.put("putSupplier", (Supplier) () -> {
+ usedSuppliers.add("putSupplier");
+ return "putSupplierValue";
+ });
+ assertFalse(usedSuppliers.contains("putSupplier"));
+ assertTrue(lazyBindings.containsKey("putSupplier"));
+ assertEquals("putSupplierValue", lazyBindings.get("putSupplier"));
+ assertTrue(usedSuppliers.contains("putSupplier"));
+ }
+
+ @Test
+ public void testPutAll() {
+ Map<String, Object> toMerge = new HashMap<>();
+ toMerge.put(THE_QUESTION, (Supplier) () -> {
+ usedSuppliers.add(THE_QUESTION);
+ return THE_ANSWER;
+ });
+ toMerge.put("b", 1);
+ toMerge.put("c", 2);
+ lazyBindings.put("a", 0);
+ lazyBindings.put("putSupplier", (Supplier) () -> {
+ usedSuppliers.add("putSupplier");
+ return "putSupplierValue";
+ });
+ lazyBindings.putAll(toMerge);
+ Set<String> keys = new HashSet<>(Arrays.asList(THE_QUESTION, "a", "b", "c", "putSupplier"));
+ assertEquals(keys, lazyBindings.keySet());
+ assertEquals(THE_ANSWER, lazyBindings.get(THE_QUESTION));
+ assertTrue(usedSuppliers.contains(THE_QUESTION));
+ assertEquals("putSupplierValue", lazyBindings.get("putSupplier"));
+ assertTrue(usedSuppliers.contains("putSupplier"));
+ assertEquals(2, lazyBindings.get("c"));
+ }
+
+ @Test
+ public void testClearSizeEmpty() {
+ lazyBindings.put("a", 0);
+ assertEquals(2, lazyBindings.size());
+ assertFalse(lazyBindings.isEmpty());
+ lazyBindings.clear();
+ assertEquals(0, lazyBindings.size());
+ assertTrue(lazyBindings.isEmpty());
+ }
+
+ @Test
+ public void testLazyContainsKey() {
+ lazyBindings.put("a", 0);
+ assertTrue(lazyBindings.containsKey(THE_QUESTION));
+ assertTrue(lazyBindings.containsKey("a"));
+ assertFalse(usedSuppliers.contains(THE_QUESTION));
+ }
+
+ @Test
+ public void testContainsValue() {
+ assertFalse(usedSuppliers.contains(THE_QUESTION));
+ assertFalse(lazyBindings.containsValue(THE_ANSWER));
+ assertFalse(usedSuppliers.contains(THE_QUESTION));
+ lazyBindings.put("a", 0);
+ assertTrue(lazyBindings.containsValue(0));
+ }
+
+ @Test
+ public void testEntrySet() {
+ lazyBindings.put("a", 0);
+ Set<Map.Entry<String, Object>> expectedEntrySet = new HashSet<>();
+ expectedEntrySet.add(new AbstractMap.SimpleEntry<>("a", 0));
+ expectedEntrySet.add(new AbstractMap.SimpleEntry<>(THE_QUESTION, supplier));
+ assertFalse(usedSuppliers.contains(THE_QUESTION));
+ assertEquals(expectedEntrySet, lazyBindings.entrySet());
+ assertFalse(usedSuppliers.contains(THE_QUESTION));
+ }
+
+ @Test
+ public void testKeySet() {
+ lazyBindings.put("a", 0);
+ assertEquals(new HashSet<>(Arrays.asList(THE_QUESTION, "a")), lazyBindings.keySet());
+ assertFalse(usedSuppliers.contains(THE_QUESTION));
+ }
+
+ @Test
+ public void testValues() {
+ assertFalse(usedSuppliers.contains(THE_QUESTION));
+ Collection<Object> values = lazyBindings.values();
+ assertFalse(usedSuppliers.contains(THE_QUESTION));
+ assertEquals(0, values.size());
+ lazyBindings.put("a", 0);
+ Set<Object> expectedValues = new HashSet<>();
+ expectedValues.add(0);
+ assertEquals(expectedValues, new HashSet<>(lazyBindings.values()));
+ }
+
+ @Test
+ public void testGetOrDefault() {
+ lazyBindings.put("a", 0);
+ assertEquals(0, lazyBindings.getOrDefault("a", 1));
+ assertFalse(usedSuppliers.contains(THE_QUESTION));
+ assertEquals(THE_ANSWER, lazyBindings.getOrDefault(THE_QUESTION, THE_ANSWER + 1));
+ assertTrue(usedSuppliers.contains(THE_QUESTION));
+ assertEquals(1, lazyBindings.getOrDefault("b", 1));
+ }
+}