Improve ListEntryNodeDataWithSchema 66/78066/16
authorRobert Varga <robert.varga@pantheon.tech>
Thu, 22 Nov 2018 22:32:00 +0000 (23:32 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Fri, 23 Nov 2018 10:14:08 +0000 (11:14 +0100)
ImmutableMapTemplate allows us to more efficiently instantiate
NodeIdentifierPredicates, as it takes care of ordering of keys
while maintaining an efficient internal representations.

It furthermore provides a keySet() method, which is useful for
quickly checking if a leaf is part of key definition -- unlike
ListSchemaNode.getKeyDefinition(), which is a List and therefore
must be linearly searched.

It also allows us to skip HashMap allocation for unkeyed lists,
clearly showing that the class should be split into two.

JIRA: YANGTOOLS-917
Change-Id: If2d7bbbc5c4fe1ca96097f374f4313aabb411908
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/ListEntryNodeDataWithSchema.java

index 28e8ecd0c5d4ace1e3ba6375bf4cbe1e70db4dd7..ebb7362222281a2cc1be2a768cec72fb22df553b 100644 (file)
@@ -13,8 +13,8 @@ import static com.google.common.base.Verify.verify;
 import java.io.IOException;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.LinkedHashMap;
 import java.util.Map;
+import org.opendaylight.yangtools.util.ImmutableMapTemplate;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamAttributeWriter;
@@ -31,64 +31,71 @@ import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
  * Represents a YANG list entry node.
  */
 public class ListEntryNodeDataWithSchema extends CompositeNodeDataWithSchema {
+    // This template results in Maps in schema definition order
+    private final ImmutableMapTemplate<QName> predicateTemplate;
+    private final Map<QName, SimpleNodeDataWithSchema> keyValues;
 
-    private final Map<QName, SimpleNodeDataWithSchema> qnameToKeys = new HashMap<>();
-
+    // FIXME: 3.0.0: require ListSchemaNode
+    // FIXME: 3.0.0: hide this constructor and provide specialized keyed/unkeyed classes
     public ListEntryNodeDataWithSchema(final DataSchemaNode schema) {
         super(schema);
+
+        final Collection<QName> keyDef = ((ListSchemaNode) getSchema()).getKeyDefinition();
+        if (keyDef.isEmpty()) {
+            predicateTemplate = null;
+            keyValues = null;
+        } else {
+            predicateTemplate = ImmutableMapTemplate.ordered(keyDef);
+            keyValues = new HashMap<>();
+        }
     }
 
     @Override
     public void addChild(final AbstractNodeDataWithSchema newChild) {
-        final DataSchemaNode childSchema = newChild.getSchema();
-        if (childSchema instanceof LeafSchemaNode && isPartOfKey((LeafSchemaNode) childSchema)) {
-            verify(newChild instanceof SimpleNodeDataWithSchema);
-            qnameToKeys.put(childSchema.getQName(), (SimpleNodeDataWithSchema)newChild);
+        if (predicateTemplate != null) {
+            final DataSchemaNode childSchema = newChild.getSchema();
+            if (childSchema instanceof LeafSchemaNode) {
+                populateKeyValue(childSchema.getQName(), newChild);
+            }
         }
         super.addChild(newChild);
     }
 
-    private boolean isPartOfKey(final LeafSchemaNode potentialKey) {
-        for (QName qname : ((ListSchemaNode) getSchema()).getKeyDefinition()) {
-            if (qname.equals(potentialKey.getQName())) {
-                return true;
-            }
+    private void populateKeyValue(final QName childName, final AbstractNodeDataWithSchema child) {
+        if (predicateTemplate.keySet().contains(childName)) {
+            verify(child instanceof SimpleNodeDataWithSchema);
+            keyValues.put(childName, (SimpleNodeDataWithSchema)child);
         }
-        return false;
     }
 
     @Override
     public void write(final NormalizedNodeStreamWriter writer) throws IOException {
-        final Collection<QName> keyDef = ((ListSchemaNode) getSchema()).getKeyDefinition();
-        if (keyDef.isEmpty()) {
-            writer.nextDataSchemaNode(getSchema());
+        writer.nextDataSchemaNode(getSchema());
+        if (predicateTemplate != null) {
+            writeKeyedListItem(writer);
+        } else {
             writer.startUnkeyedListItem(provideNodeIdentifier(), childSizeHint());
-            super.write(writer);
-            writer.endNode();
-            return;
         }
 
-        checkState(keyDef.size() == qnameToKeys.size(),
-                "Map entry corresponding to %s is missing some of required keys %s", getSchema().getQName(), keyDef);
+        super.write(writer);
+        writer.endNode();
+    }
 
-        // Need to restore schema order...
-        final Map<QName, Object> predicates = new LinkedHashMap<>();
-        for (QName qname : keyDef) {
-            predicates.put(qname, qnameToKeys.get(qname).getValue());
-        }
+    private void writeKeyedListItem(final NormalizedNodeStreamWriter writer) throws IOException {
+        // FIXME: 3.0.0: remove this check? predicateTemplate will throw an IllegalArgumentException if anything
+        //               goes wrong -- which is a change of behavior, as now we're throwing an ISE. Do we want that?
+        final Collection<QName> keySet = predicateTemplate.keySet();
+        checkState(keySet.size() == keyValues.size(),
+                "Map entry corresponding to %s is missing some of required keys %s", getSchema().getQName(), keySet);
 
-        writer.nextDataSchemaNode(getSchema());
+        final NodeIdentifierWithPredicates identifier = new NodeIdentifierWithPredicates(getSchema().getQName(),
+            predicateTemplate.instantiateTransformed(keyValues, (key, node) -> node.getValue()));
 
         if (writer instanceof NormalizedNodeStreamAttributeWriter && getAttributes() != null) {
-            ((NormalizedNodeStreamAttributeWriter) writer).startMapEntryNode(
-                    new NodeIdentifierWithPredicates(getSchema().getQName(), predicates), childSizeHint(),
-                    getAttributes());
+            ((NormalizedNodeStreamAttributeWriter) writer).startMapEntryNode(identifier, childSizeHint(),
+                getAttributes());
         } else {
-            writer.startMapEntryNode(new NodeIdentifierWithPredicates(getSchema().getQName(), predicates),
-                    childSizeHint());
+            writer.startMapEntryNode(identifier, childSizeHint());
         }
-
-        super.write(writer);
-        writer.endNode();
     }
 }