Fixup LazyLeafOperations 89/84089/3
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 30 Aug 2019 08:58:33 +0000 (10:58 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Fri, 30 Aug 2019 10:12:44 +0000 (12:12 +0200)
LazyLeafOperations.isEnabled() allows code to query whether leaf nodes
are being treated as expendable, hence assumptions about them being
equal on identity can be violated. We retrofit the check into tests we
modified before.

Furthermore users of getValue() expect it to work with identities,
even when it is being specialized to a Collection. The expectation here
is that the same collection will be returned from NormalizedNode,
which we are violating. Fix this up by introducing a new implementation
class, which hides this difference and defers equality to the backing
map.

JIRA: YANGTOOLS-1019
Change-Id: Icf62619c3ea5a1c994c6547a0674b36d707248d3
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/nodes/LazyLeafOperations.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/nodes/LazyValues.java [new file with mode: 0644]
yang/yang-data-jaxen/src/test/java/org/opendaylight/yangtools/yang/data/jaxen/DerefXPathFunctionTest.java

index 868b014cb3dd6531682460ae4c0e229c930c6090..2dba8075b888c888bac06776aaeb7e030b03481c 100644 (file)
@@ -11,10 +11,8 @@ import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
-import com.google.common.collect.Collections2;
 import java.util.Collection;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Optional;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
@@ -64,6 +62,16 @@ public final class LazyLeafOperations {
 
     }
 
+    /**
+     * A boolean flag indicating whether leaf nodes are being treated as expendable.
+     *
+     * @return True if NormalizedNode implementations in this artifact are treating leaf nodes as transient, i.e. do
+     *              not retain them.
+     */
+    public static boolean isEnabled() {
+        return EXPENDABLE;
+    }
+
     public static Optional<DataContainerChild<?, ?>> findChild(final Map<PathArgument, Object> map,
             final PathArgument key) {
         final Object value = map.get(key);
@@ -83,13 +91,14 @@ public final class LazyLeafOperations {
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
     public static @NonNull Collection<DataContainerChild<?, ?>> getValue(final Map<PathArgument, Object> map) {
-        return EXPENDABLE ? Collections2.transform(map.entrySet(), LazyLeafOperations::decodeEntry)
+        return EXPENDABLE ? new LazyValues(map)
                 // This is an ugly cast, but it is accurate IFF all modifications are done through this class
-                : (Collection) map.values();
+                : (Collection)map.values();
     }
 
-    private static @NonNull Object encodeExpendableChild(final DataContainerChild<?, ?> key) {
-        return key instanceof LeafNode ? ((LeafNode<?>) key).getValue() : requireNonNull(key);
+    static @NonNull LeafNode<?> coerceLeaf(final PathArgument key, final Object value) {
+        verify(key instanceof NodeIdentifier, "Unexpected value %s for child %s", value, key);
+        return ImmutableNodes.leafNode((NodeIdentifier) key, value);
     }
 
     private static @Nullable DataContainerChild<?, ?> decodeChild(final PathArgument key, final @NonNull Object value) {
@@ -101,20 +110,12 @@ public final class LazyLeafOperations {
         return value instanceof DataContainerChild ? (DataContainerChild<?, ?>) value  : coerceLeaf(key, value);
     }
 
+    private static @NonNull Object encodeExpendableChild(final DataContainerChild<?, ?> key) {
+        return key instanceof LeafNode ? ((LeafNode<?>) key).getValue() : requireNonNull(key);
+    }
+
     private static @NonNull DataContainerChild<?, ?> verifyCast(final @NonNull Object value) {
         verify(value instanceof DataContainerChild, "Unexpected child %s", value);
         return (DataContainerChild<?, ?>)value;
     }
-
-    private static @NonNull DataContainerChild<? extends PathArgument, ?> decodeEntry(
-            final @NonNull Entry<PathArgument, Object> entry) {
-        final Object value = entry.getValue();
-        return value instanceof DataContainerChild ? (DataContainerChild<?, ?>) value
-                : coerceLeaf(entry.getKey(), value);
-    }
-
-    private static @NonNull LeafNode<?> coerceLeaf(final PathArgument key, final Object value) {
-        verify(key instanceof NodeIdentifier, "Unexpected value %s for child %s", value, key);
-        return ImmutableNodes.leafNode((NodeIdentifier) key, value);
-    }
 }
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/nodes/LazyValues.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/nodes/LazyValues.java
new file mode 100644 (file)
index 0000000..e9ad16e
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.impl.schema.nodes;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.AbstractCollection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+
+// This is *almost* the same as Guava's TransformedCollection. The main difference is delegation of hashCode()/equals()
+// towards the backing map. This is needed because we do not retain a reference to this object and thus
+// NormalizedNode.getValue() does not compare as equal. When invoked twice and lazy leaves are in effect. Note that
+// Collection.equals() is undefined, but the expectation from users is that we will return the same view object, which
+// equals on identity.
+final class LazyValues extends AbstractCollection<DataContainerChild<?, ?>> {
+    private final Map<PathArgument, Object> map;
+
+    LazyValues(final Map<PathArgument, Object> map) {
+        this.map = requireNonNull(map);
+    }
+
+    @Override
+    public int size() {
+        return map.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return map.isEmpty();
+    }
+
+    @Override
+    public Iterator<DataContainerChild<?, ?>> iterator() {
+        return new Iter(map.entrySet().iterator());
+    }
+
+    @Override
+    public int hashCode() {
+        return map.hashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        return this == obj || obj instanceof LazyValues && map.equals(((LazyValues)obj).map);
+    }
+
+    private static final class Iter implements Iterator<DataContainerChild<?, ?>> {
+        private final Iterator<Entry<PathArgument, Object>> iterator;
+
+        Iter(final Iterator<Entry<PathArgument, Object>> iterator) {
+            this.iterator = requireNonNull(iterator);
+        }
+
+        @Override
+        public boolean hasNext() {
+            return iterator.hasNext();
+        }
+
+        @Override
+        public DataContainerChild<?, ?> next() {
+            final Entry<PathArgument, Object> entry = iterator.next();
+            final Object value = entry.getValue();
+            return value instanceof DataContainerChild ? (DataContainerChild<?, ?>) value
+                    : LazyLeafOperations.coerceLeaf(entry.getKey(), value);
+        }
+    }
+}
index e383694cc7738a82b094b65a94807369e7e9194e..60d6667522dd545d88b7b195f2fad5ca5cdc11df 100644 (file)
@@ -38,6 +38,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathDocument;
 import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathSchemaContext;
 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.nodes.LazyLeafOperations;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
 
@@ -87,8 +88,8 @@ public class DerefXPathFunctionTest {
                 .getFunction(null, null, "deref");
         final Object derefResult = derefFunction.call(normalizedNodeContext, ImmutableList.of());
         assertNotNull(derefResult);
-        assertTrue(derefResult instanceof NormalizedNode<?, ?>);
-        assertEquals(referencedLeafNode, derefResult);
+        assertTrue(derefResult instanceof LeafNode<?>);
+        assertLeafEquals(referencedLeafNode, (LeafNode<?>) derefResult);
     }
 
     @Test
@@ -120,8 +121,8 @@ public class DerefXPathFunctionTest {
                 .getFunction(null, null, "deref");
         Object derefResult = derefFunction.call(normalizedNodeContext, ImmutableList.of());
         assertNotNull(derefResult);
-        assertTrue(derefResult instanceof NormalizedNode<?, ?>);
-        assertEquals(referencedLeafNode, derefResult);
+        assertTrue(derefResult instanceof LeafNode<?>);
+        assertLeafEquals(referencedLeafNode, (LeafNode<?>) derefResult);
 
         final YangInstanceIdentifier relLeafrefPath = YangInstanceIdentifier.of(MY_INNER_CONTAINER)
                 .node(REL_LEAFREF_LEAF);
@@ -129,8 +130,8 @@ public class DerefXPathFunctionTest {
 
         derefResult = derefFunction.call(normalizedNodeContext, ImmutableList.of());
         assertNotNull(derefResult);
-        assertTrue(derefResult instanceof NormalizedNode<?, ?>);
-        assertEquals(referencedLeafNode, derefResult);
+        assertTrue(derefResult instanceof LeafNode<?>);
+        assertLeafEquals(referencedLeafNode, (LeafNode<?>) derefResult);
     }
 
     @Test
@@ -272,4 +273,12 @@ public class DerefXPathFunctionTest {
                 .withChild(myInnerContainerNode).build();
         return myContainerNode;
     }
-}
\ No newline at end of file
+
+    private static void assertLeafEquals(final LeafNode<?> expected, final LeafNode<?> actual) {
+        if (LazyLeafOperations.isEnabled()) {
+            assertEquals(expected, actual);
+        } else {
+            assertSame(expected, actual);
+        }
+    }
+}