Bug 1443: Added support for misplaced augmentations.
[yangtools.git] / code-generator / binding-data-codec / src / main / java / org / opendaylight / yangtools / binding / data / codec / impl / ChoiceNodeCodecContext.java
index de785acef686231e21700a1a0607c9128c200aa3..bac768703c24cab5f00e1b13014143f9b5fcf218 100644 (file)
@@ -7,12 +7,16 @@
  */
 package org.opendaylight.yangtools.binding.data.codec.impl;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
@@ -23,9 +27,11 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 final class ChoiceNodeCodecContext extends DataContainerCodecContext<ChoiceNode> {
-
+    private static final Logger LOG = LoggerFactory.getLogger(ChoiceNodeCodecContext.class);
     private final ImmutableMap<YangInstanceIdentifier.PathArgument, DataContainerCodecPrototype<?>> byYangCaseChild;
     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byClass;
     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byCaseChildClass;
@@ -35,18 +41,50 @@ final class ChoiceNodeCodecContext extends DataContainerCodecContext<ChoiceNode>
         Map<YangInstanceIdentifier.PathArgument, DataContainerCodecPrototype<?>> byYangCaseChildBuilder = new HashMap<>();
         Map<Class<?>, DataContainerCodecPrototype<?>> byClassBuilder = new HashMap<>();
         Map<Class<?>, DataContainerCodecPrototype<?>> byCaseChildClassBuilder = new HashMap<>();
-
+        Set<Class<?>> potentialSubstitutions = new HashSet<>();
+        // Walks all cases for supplied choice in current runtime context
         for (Class<?> caze : factory().getRuntimeContext().getCases(bindingClass())) {
+            // We try to load case using exact match thus name
+            // and original schema must equals
             DataContainerCodecPrototype<ChoiceCaseNode> cazeDef = loadCase(caze);
-            byClassBuilder.put(cazeDef.getBindingClass(), cazeDef);
-            for (Class<? extends DataObject> cazeChild : BindingReflections.getChildrenClasses((Class) caze)) {
-                byCaseChildClassBuilder.put(cazeChild, cazeDef);
-            }
-            for (DataSchemaNode cazeChild : cazeDef.getSchema().getChildNodes()) {
-                byYangCaseChildBuilder.put(new NodeIdentifier(cazeChild.getQName()), cazeDef);
+            // If we have case definition, this case is instantiated
+            // at current location and thus is valid in context of parent choice
+            if (cazeDef != null) {
+                byClassBuilder.put(cazeDef.getBindingClass(), cazeDef);
+                // Updates collection of case children
+                for (Class<? extends DataObject> cazeChild : BindingReflections.getChildrenClasses((Class) caze)) {
+                    byCaseChildClassBuilder.put(cazeChild, cazeDef);
+                }
+                // Updates collection of YANG instance identifier to case
+                for (DataSchemaNode cazeChild : cazeDef.getSchema().getChildNodes()) {
+                    byYangCaseChildBuilder.put(new NodeIdentifier(cazeChild.getQName()), cazeDef);
+                }
+            } else {
+                /*
+                 * If case definition is not available, we store it for
+                 * later check if it could be used as substitution of existing one.
+                 */
+                potentialSubstitutions.add(caze);
             }
         }
 
+        Map<Class<?>, DataContainerCodecPrototype<?>> bySubstitutionBuilder = new HashMap<>();
+        /*
+         * Walks all cases which are not directly instantiated and
+         * tries to match them to instantiated cases - represent same data as instantiated case,
+         * only case name or schema path is different. This is required due property of
+         * binding specification, that if choice is in grouping schema path location is lost,
+         * and users may use incorrect case class using copy builders.
+         */
+        for(Class<?> substitution : potentialSubstitutions) {
+            search: for(Entry<Class<?>, DataContainerCodecPrototype<?>> real : byClassBuilder.entrySet()) {
+                if(BindingReflections.isSubstitutionFor(substitution, real.getKey())) {
+                    bySubstitutionBuilder.put(substitution, real.getValue());
+                    break search;
+                }
+            }
+        }
+        byClassBuilder.putAll(bySubstitutionBuilder);
         byYangCaseChild = ImmutableMap.copyOf(byYangCaseChildBuilder);
         byClass = ImmutableMap.copyOf(byClassBuilder);
         byCaseChildClass = ImmutableMap.copyOf(byCaseChildClassBuilder);
@@ -54,7 +92,18 @@ final class ChoiceNodeCodecContext extends DataContainerCodecContext<ChoiceNode>
 
     @Override
     protected DataContainerCodecContext<?> getStreamChild(final Class<?> childClass) {
-        return byClass.get(childClass).get();
+        DataContainerCodecPrototype<?> child = byClass.get(childClass);
+        Preconditions.checkArgument(child != null,"Supplied class is not valid case",childClass);
+        return child.get();
+    }
+
+    @Override
+    protected Optional<DataContainerCodecContext<?>> getPossibleStreamChild(final Class<?> childClass) {
+        DataContainerCodecPrototype<?> child = byClass.get(childClass);
+        if(child != null) {
+            return Optional.<DataContainerCodecContext<?>>of(child.get());
+        }
+        return Optional.absent();
     }
 
     Iterable<Class<?>> getCaseChildrenClasses() {
@@ -62,8 +111,13 @@ final class ChoiceNodeCodecContext extends DataContainerCodecContext<ChoiceNode>
     }
 
     protected DataContainerCodecPrototype<ChoiceCaseNode> loadCase(final Class<?> childClass) {
-        ChoiceCaseNode childSchema = factory().getRuntimeContext().getCaseSchemaDefinition(schema(), childClass);
-        return DataContainerCodecPrototype.from(childClass, childSchema, factory());
+        Optional<ChoiceCaseNode> childSchema = factory().getRuntimeContext().getCaseSchemaDefinition(schema(), childClass);
+        if (childSchema.isPresent()) {
+            return DataContainerCodecPrototype.from(childClass, childSchema.get(), factory());
+        }
+
+        LOG.debug("Supplied class %s is not valid case in schema %s", childClass, schema());
+        return null;
     }
 
     @Override