Bug 3874: Support of yang modeled AnyXML - JSON deserialization
[yangtools.git] / yang / yang-data-codec-gson / src / main / java / org / opendaylight / yangtools / yang / data / codec / gson / CompositeNodeDataWithSchema.java
index 83d715cefe20f973d7a466d2d2d741529bb110e4..4d40db27f78c2b48cf7611659e46610619156b9d 100644 (file)
  */
 package org.opendaylight.yangtools.yang.data.codec.gson;
 
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Deque;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Map.Entry;
-import java.util.Set;
-
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.SchemaAwareNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.SchemaUtils;
 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
-import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
-import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.YangModeledAnyXmlSchemaNode;
 
+/**
+ * A node which is composed of multiple simpler nodes.
+ */
 class CompositeNodeDataWithSchema extends AbstractNodeDataWithSchema {
 
     /**
      * nodes which were added to schema via augmentation and are present in data input
      */
-    protected Map<AugmentationSchema, List<AbstractNodeDataWithSchema>> augmentationsToChild = new HashMap<>();
+    private final Multimap<AugmentationSchema, AbstractNodeDataWithSchema> augmentationsToChild = ArrayListMultimap.create();
 
     /**
-     * remaining data nodes (which aren't added via augment). Every of them should have the same QName
+     * remaining data nodes (which aren't added via augment). Every of one them should have the same QName.
      */
-    protected List<AbstractNodeDataWithSchema> childs = new ArrayList<>();
+    private final List<AbstractNodeDataWithSchema> children = new ArrayList<>();
 
     public CompositeNodeDataWithSchema(final DataSchemaNode schema) {
         super(schema);
     }
 
-    public AbstractNodeDataWithSchema addSimpleChild(final DataSchemaNode schema) {
-        SimpleNodeDataWithSchema newChild = null;
-        if (schema instanceof LeafSchemaNode) {
-            newChild = new LeafNodeDataWithSchema(schema);
-        } else if (schema instanceof AnyXmlSchemaNode) {
-            newChild = new AnyXmlNodeDataWithSchema(schema);
+    public AbstractNodeDataWithSchema addChild(final Deque<DataSchemaNode> schemas) {
+        Preconditions.checkArgument(!schemas.isEmpty(), "Expecting at least one schema");
+
+        // Pop the first node...
+        final DataSchemaNode schema = schemas.pop();
+        if (schemas.isEmpty()) {
+            // Simple, direct node
+            return addChild(schema);
         }
 
-        if (newChild != null) {
+        // The choice/case mess, reuse what we already popped
+        final DataSchemaNode choiceCandidate = schema;
+        Preconditions.checkArgument(choiceCandidate instanceof ChoiceSchemaNode,
+            "Expected node of type ChoiceNode but was %s", choiceCandidate.getClass().getSimpleName());
+        final ChoiceSchemaNode choiceNode = (ChoiceSchemaNode) choiceCandidate;
 
-            AugmentationSchema augSchema = null;
-            if (schema.isAugmenting()) {
-                augSchema = findCorrespondingAugment(getSchema(), schema);
-            }
-            if (augSchema != null) {
-                addChildToAugmentation(augSchema, newChild);
-            } else {
-                addChild(newChild);
-            }
-            return newChild;
-        }
-        return null;
-    }
+        final DataSchemaNode caseCandidate = schemas.pop();
+        Preconditions.checkArgument(caseCandidate instanceof ChoiceCaseNode,
+            "Expected node of type ChoiceCaseNode but was %s", caseCandidate.getClass().getSimpleName());
+        final ChoiceCaseNode caseNode = (ChoiceCaseNode) caseCandidate;
 
-    private void addChildToAugmentation(final AugmentationSchema augSchema, final AbstractNodeDataWithSchema newChild) {
-        List<AbstractNodeDataWithSchema> childsInAugment = augmentationsToChild.get(augSchema);
-        if (childsInAugment == null) {
-            childsInAugment = new ArrayList<>();
-            augmentationsToChild.put(augSchema, childsInAugment);
+        AugmentationSchema augSchema = null;
+        if (choiceCandidate.isAugmenting()) {
+            augSchema = SchemaUtils.findCorrespondingAugment(getSchema(), choiceCandidate);
         }
-        childsInAugment.add(newChild);
-    }
 
-    public AbstractNodeDataWithSchema addChild(final Deque<DataSchemaNode> schemas) {
-        if (schemas.size() == 1) {
-            final DataSchemaNode childDataSchemaNode = schemas.pop();
-            return addChild(childDataSchemaNode);
+        // looking for existing choice
+        final Collection<AbstractNodeDataWithSchema> childNodes;
+        if (augSchema != null) {
+            childNodes = augmentationsToChild.get(augSchema);
         } else {
-            DataSchemaNode choiceCandidate = schemas.pop();
-            DataSchemaNode caseCandidate = schemas.pop();
-            ChoiceNode choiceNode = null;
-            ChoiceCaseNode caseNode = null;
-            if (choiceCandidate instanceof ChoiceNode) {
-                choiceNode = (ChoiceNode) choiceCandidate;
-            } else {
-                throw new IllegalArgumentException("Awaited node of type ChoiceNode but was "
-                        + choiceCandidate.getClass().getSimpleName());
-            }
-
-            if (caseCandidate instanceof ChoiceCaseNode) {
-                caseNode = (ChoiceCaseNode) caseCandidate;
-            } else {
-                throw new IllegalArgumentException("Awaited node of type ChoiceCaseNode but was "
-                        + caseCandidate.getClass().getSimpleName());
-            }
+            childNodes = children;
+        }
 
-            AugmentationSchema augSchema = null;
-            if (choiceCandidate.isAugmenting()) {
-                augSchema = findCorrespondingAugment(getSchema(), choiceCandidate);
-            }
+        CompositeNodeDataWithSchema caseNodeDataWithSchema = findChoice(childNodes, choiceCandidate, caseCandidate);
+        if (caseNodeDataWithSchema == null) {
+            ChoiceNodeDataWithSchema choiceNodeDataWithSchema = new ChoiceNodeDataWithSchema(choiceNode);
+            childNodes.add(choiceNodeDataWithSchema);
+            caseNodeDataWithSchema = choiceNodeDataWithSchema.addCompositeChild(caseNode);
+        }
 
-            // looking for existing choice
-            List<AbstractNodeDataWithSchema> childNodes = Collections.emptyList();
-            if (augSchema != null) {
-                childNodes = augmentationsToChild.get(augSchema);
-            } else {
-                childNodes = childs;
-            }
+        return caseNodeDataWithSchema.addChild(schemas);
+    }
 
-            CompositeNodeDataWithSchema caseNodeDataWithSchema = findChoice(childNodes, choiceCandidate, caseCandidate);
-            if (caseNodeDataWithSchema == null) {
-                ChoiceNodeDataWithSchema choiceNodeDataWithSchema = new ChoiceNodeDataWithSchema(choiceNode);
-                addChild(choiceNodeDataWithSchema);
-                caseNodeDataWithSchema = choiceNodeDataWithSchema.addCompositeChild(caseNode);
+    private AbstractNodeDataWithSchema addSimpleChild(final DataSchemaNode schema) {
+        SimpleNodeDataWithSchema newChild = null;
+        if (schema instanceof LeafSchemaNode) {
+            newChild = new LeafNodeDataWithSchema(schema);
+        } else if (schema instanceof AnyXmlSchemaNode) {
+            // YangModeledAnyXmlSchemaNode is handled by addCompositeChild method.
+            if (schema instanceof YangModeledAnyXmlSchemaNode) {
+                return null;
             }
-
-            return caseNodeDataWithSchema.addChild(schemas);
+            newChild = new AnyXmlNodeDataWithSchema(schema);
+        } else {
+            return null;
         }
 
+        AugmentationSchema augSchema = null;
+        if (schema.isAugmenting()) {
+            augSchema = SchemaUtils.findCorrespondingAugment(getSchema(), schema);
+        }
+        if (augSchema != null) {
+            augmentationsToChild.put(augSchema, newChild);
+        } else {
+            addChild(newChild);
+        }
+        return newChild;
     }
 
-    private CaseNodeDataWithSchema findChoice(final List<AbstractNodeDataWithSchema> childNodes, final DataSchemaNode choiceCandidate,
-            final DataSchemaNode caseCandidate) {
-        if (childNodes == null) {
-            return null;
-        }
-        for (AbstractNodeDataWithSchema nodeDataWithSchema : childNodes) {
-            if (nodeDataWithSchema instanceof ChoiceNodeDataWithSchema
-                    && nodeDataWithSchema.getSchema().getQName().equals(choiceCandidate.getQName())) {
-                CaseNodeDataWithSchema casePrevious = ((ChoiceNodeDataWithSchema) nodeDataWithSchema).getCase();
-                if (casePrevious.getSchema().getQName() != caseCandidate.getQName()) {
-                    throw new IllegalArgumentException("Data from case " + caseCandidate.getQName()
-                            + " are specified but other data from case " + casePrevious.getSchema().getQName()
-                            + " were specified erlier. Data aren't from the same case.");
+    private static CaseNodeDataWithSchema findChoice(final Collection<AbstractNodeDataWithSchema> childNodes,
+            final DataSchemaNode choiceCandidate, final DataSchemaNode caseCandidate) {
+        if (childNodes != null) {
+            for (AbstractNodeDataWithSchema nodeDataWithSchema : childNodes) {
+                if (nodeDataWithSchema instanceof ChoiceNodeDataWithSchema
+                        && nodeDataWithSchema.getSchema().getQName().equals(choiceCandidate.getQName())) {
+                    CaseNodeDataWithSchema casePrevious = ((ChoiceNodeDataWithSchema) nodeDataWithSchema).getCase();
+
+                    Preconditions.checkArgument(casePrevious.getSchema().getQName().equals(caseCandidate.getQName()),
+                        "Data from case %s are specified but other data from case %s were specified erlier. Data aren't from the same case.",
+                        caseCandidate.getQName(), casePrevious.getSchema().getQName());
+
+                    return casePrevious;
                 }
-                return casePrevious;
             }
         }
         return null;
     }
 
-    public AbstractNodeDataWithSchema addCompositeChild(final DataSchemaNode schema) {
-        CompositeNodeDataWithSchema newChild;
+    AbstractNodeDataWithSchema addCompositeChild(final DataSchemaNode schema) {
+        final CompositeNodeDataWithSchema newChild;
+
         if (schema instanceof ListSchemaNode) {
             newChild = new ListNodeDataWithSchema(schema);
         } else if (schema instanceof LeafListSchemaNode) {
             newChild = new LeafListNodeDataWithSchema(schema);
         } else if (schema instanceof ContainerSchemaNode) {
             newChild = new ContainerNodeDataWithSchema(schema);
+        } else if (schema instanceof YangModeledAnyXmlSchemaNode) {
+            newChild = new YangModeledAnyXmlNodeDataWithSchema((YangModeledAnyXmlSchemaNode)schema);
         } else {
             newChild = new CompositeNodeDataWithSchema(schema);
         }
+
         addCompositeChild(newChild);
         return newChild;
     }
 
-    public void addCompositeChild(final CompositeNodeDataWithSchema newChild) {
-        AugmentationSchema augSchema = findCorrespondingAugment(getSchema(), newChild.getSchema());
+    void addCompositeChild(final CompositeNodeDataWithSchema newChild) {
+        AugmentationSchema augSchema = SchemaUtils.findCorrespondingAugment(getSchema(), newChild.getSchema());
         if (augSchema != null) {
-            addChildToAugmentation(augSchema, newChild);
+            augmentationsToChild.put(augSchema, newChild);
         } else {
             addChild(newChild);
         }
@@ -180,55 +171,34 @@ class CompositeNodeDataWithSchema extends AbstractNodeDataWithSchema {
     }
 
     public void addChild(final AbstractNodeDataWithSchema newChild) {
-        childs.add(newChild);
+        children.add(newChild);
     }
 
     /**
-     * Tries to find in {@code parent} which is dealed as augmentation target node with QName as {@code child}. If such
-     * node is found then it is returned, else null.
+     * Return a hint about how may children we are going to generate.
+     * @return Size of currently-present node list.
      */
-    protected AugmentationSchema findCorrespondingAugment(final DataSchemaNode parent, final DataSchemaNode child) {
-        if (parent instanceof AugmentationTarget) {
-            for (AugmentationSchema augmentation : ((AugmentationTarget) parent).getAvailableAugmentations()) {
-                DataSchemaNode childInAugmentation = augmentation.getDataChildByName(child.getQName());
-                if (childInAugmentation != null) {
-                    return augmentation;
-                }
-            }
-        }
-        return null;
+    protected final int childSizeHint() {
+        return children.size();
     }
 
     @Override
-    protected void writeToStream(final NormalizedNodeStreamWriter nnStreamWriter) throws IOException {
-        for (AbstractNodeDataWithSchema child : childs) {
-            child.writeToStream(nnStreamWriter);
+    public void write(final SchemaAwareNormalizedNodeStreamWriter writer) throws IOException {
+        for (AbstractNodeDataWithSchema child : children) {
+            child.write(writer);
         }
-        for (Entry<AugmentationSchema, List<AbstractNodeDataWithSchema>> augmentationToChild : augmentationsToChild.entrySet()) {
-
-            final List<AbstractNodeDataWithSchema> childsFromAgumentation = augmentationToChild.getValue();
-
+        for (Entry<AugmentationSchema, Collection<AbstractNodeDataWithSchema>> augmentationToChild : augmentationsToChild.asMap().entrySet()) {
+            final Collection<AbstractNodeDataWithSchema> childsFromAgumentation = augmentationToChild.getValue();
             if (!childsFromAgumentation.isEmpty()) {
-                nnStreamWriter.startAugmentationNode(toAugmentationIdentifier(augmentationToChild));
+                // FIXME: can we get the augmentation schema?
+                writer.startAugmentationNode(SchemaUtils.getNodeIdentifierForAugmentation(augmentationToChild.getKey()));
 
                 for (AbstractNodeDataWithSchema nodeDataWithSchema : childsFromAgumentation) {
-                    nodeDataWithSchema.writeToStream(nnStreamWriter);
+                    nodeDataWithSchema.write(writer);
                 }
 
-                nnStreamWriter.endNode();
+                writer.endNode();
             }
         }
     }
-
-    private static AugmentationIdentifier toAugmentationIdentifier(
-            final Entry<AugmentationSchema, List<AbstractNodeDataWithSchema>> augmentationToChild) {
-        Collection<DataSchemaNode> nodes = augmentationToChild.getKey().getChildNodes();
-        Set<QName> nodesQNames = new HashSet<>();
-        for (DataSchemaNode node : nodes) {
-            nodesQNames.add(node.getQName());
-        }
-
-        return new AugmentationIdentifier(nodesQNames);
-    }
-
 }