MDSAL-310 Binding v2 codec - Instance identifier should not reference choice/case.
[mdsal.git] / binding2 / mdsal-binding2-dom-codec / src / main / java / org / opendaylight / mdsal / binding / javav2 / dom / codec / impl / context / ChoiceNodeCodecContext.java
1 /*
2  * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context;
9
10 import com.google.common.annotations.Beta;
11 import com.google.common.base.Preconditions;
12 import com.google.common.collect.ImmutableMap;
13 import com.google.common.collect.Iterables;
14 import java.util.HashMap;
15 import java.util.HashSet;
16 import java.util.Map;
17 import java.util.Map.Entry;
18 import java.util.Optional;
19 import java.util.Set;
20 import javax.annotation.Nonnull;
21 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base.DataContainerCodecContext;
22 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base.DataContainerCodecPrototype;
23 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base.NodeCodecContext;
24 import org.opendaylight.mdsal.binding.javav2.runtime.reflection.BindingReflections;
25 import org.opendaylight.mdsal.binding.javav2.spec.base.Instantiable;
26 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeArgument;
27 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
33 import org.opendaylight.yangtools.yang.data.impl.schema.SchemaUtils;
34 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * Context for prototype of choice node codec.
43  *
44  * @param <D>
45  *            - type of tree node
46  */
47 @Beta
48 public class ChoiceNodeCodecContext<D extends TreeNode> extends DataContainerCodecContext<D, ChoiceSchemaNode> {
49
50     private static final Logger LOG = LoggerFactory.getLogger(ChoiceNodeCodecContext.class);
51
52     private final ImmutableMap<YangInstanceIdentifier.PathArgument, DataContainerCodecPrototype<?>> byYangCaseChild;
53     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byClass;
54     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byCaseChildClass;
55
56     /**
57      * Prepare context for choice node from prototype and all case children of choice class.
58      *
59      * @param prototype
60      *            - codec prototype of choice node
61      */
62     public ChoiceNodeCodecContext(final DataContainerCodecPrototype<ChoiceSchemaNode> prototype) {
63         super(prototype);
64         final Map<YangInstanceIdentifier.PathArgument, DataContainerCodecPrototype<?>> byYangCaseChildBuilder =
65                 new HashMap<>();
66         final Map<Class<?>, DataContainerCodecPrototype<?>> byClassBuilder = new HashMap<>();
67         final Map<Class<?>, DataContainerCodecPrototype<?>> byCaseChildClassBuilder = new HashMap<>();
68         final Set<Class<?>> potentialSubstitutions = new HashSet<>();
69
70         //TODO: Collect all choice/cases' descendant data children including augmented data nodes.
71         // Walks all cases for supplied choice in current runtime context
72         for (final Class<?> caze : factory().getRuntimeContext().getCases(getBindingClass())) {
73             // We try to load case using exact match thus name
74             // and original schema must equals
75             final DataContainerCodecPrototype<CaseSchemaNode> cazeDef = loadCase(caze);
76             // If we have case definition, this case is instantiated
77             // at current location and thus is valid in context of parent choice
78             if (cazeDef != null) {
79                 byClassBuilder.put(cazeDef.getBindingClass(), cazeDef);
80                 // Updates collection of case children
81                 @SuppressWarnings("unchecked")
82                 final Class<? extends Instantiable<?>> cazeCls = (Class<? extends Instantiable<?>>) caze;
83                 for (final Class<? extends TreeNode> cazeChild : BindingReflections.getChildrenClasses(cazeCls)) {
84                     byCaseChildClassBuilder.put(cazeChild, cazeDef);
85                 }
86                 // Updates collection of YANG instance identifier to case
87                 for (final DataSchemaNode cazeChild : cazeDef.getSchema().getChildNodes()) {
88                     if (cazeChild.isAugmenting()) {
89                         final AugmentationSchemaNode augment =
90                                 SchemaUtils.findCorrespondingAugment(cazeDef.getSchema(), cazeChild);
91                         if (augment != null) {
92                             byYangCaseChildBuilder.put(SchemaUtils.getNodeIdentifierForAugmentation(augment), cazeDef);
93                             continue;
94                         }
95                     }
96                     byYangCaseChildBuilder.put(NodeIdentifier.create(cazeChild.getQName()), cazeDef);
97                 }
98             } else {
99                 /*
100                  * If case definition is not available, we store it for later check if it could be used as
101                  * substitution of existing one.
102                  */
103                 potentialSubstitutions.add(caze);
104             }
105         }
106
107         final Map<Class<?>, DataContainerCodecPrototype<?>> bySubstitutionBuilder = new HashMap<>();
108         /*
109          * Walks all cases which are not directly instantiated and tries to match them to instantiated cases -
110          * represent same data as instantiated case, only case name or schema path is different. This is
111          * required due property of binding specification, that if choice is in grouping schema path location
112          * is lost, and users may use incorrect case class using copy builders.
113          */
114         for (final Class<?> substitution : potentialSubstitutions) {
115             for (final Entry<Class<?>, DataContainerCodecPrototype<?>> real : byClassBuilder.entrySet()) {
116                 if (BindingReflections.isSubstitutionFor(substitution, real.getKey())) {
117                     bySubstitutionBuilder.put(substitution, real.getValue());
118                     break;
119                 }
120             }
121         }
122         byClassBuilder.putAll(bySubstitutionBuilder);
123         byYangCaseChild = ImmutableMap.copyOf(byYangCaseChildBuilder);
124         byClass = ImmutableMap.copyOf(byClassBuilder);
125         byCaseChildClass = ImmutableMap.copyOf(byCaseChildClassBuilder);
126     }
127
128     @SuppressWarnings("unchecked")
129     @Nonnull
130     @Override
131     public <C extends TreeNode> DataContainerCodecContext<C, ?> streamChild(@Nonnull final Class<C> childClass) {
132         final DataContainerCodecPrototype<?> child = byClass.get(childClass);
133         return (DataContainerCodecContext<C,
134                 ?>) childNonNull(child, childClass, "Supplied class %s is not valid case", childClass).get();
135     }
136
137     @SuppressWarnings("unchecked")
138     @Override
139     public <C extends TreeNode> Optional<DataContainerCodecContext<C, ?>>
140             possibleStreamChild(@Nonnull final Class<C> childClass) {
141         final DataContainerCodecPrototype<?> child = byClass.get(childClass);
142         if (child != null) {
143             return Optional.of((DataContainerCodecContext<C, ?>) child.get());
144         }
145         return Optional.empty();
146     }
147
148
149     /**
150      * Gets the map of case class and prototype for {@link
151      * org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base.TreeNodeCodecContext}
152      * to catch choice/cases' data child by class.
153      *
154      * @return the map of case class and prototype
155      */
156     public Map<Class<?>, DataContainerCodecPrototype<?>> getClassCaseChildren() {
157         return byCaseChildClass;
158     }
159
160
161     /**
162      * Gets the map of case path argument and prototype for {@link
163      * org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base.TreeNodeCodecContext}
164      * to catch choice/cases' data child by class.
165      *
166      * @return the the map of case path and prototype
167      */
168     public Map<YangInstanceIdentifier.PathArgument, DataContainerCodecPrototype<?>> getYangCaseChildren() {
169         return byYangCaseChild;
170     }
171
172     public DataContainerCodecContext<?, ?> getCaseByChildClass(final @Nonnull Class<? extends TreeNode> type) {
173         final DataContainerCodecPrototype<?> protoCtx =
174             childNonNull(byCaseChildClass.get(type), type, "Class %s is not child of any cases for %s", type,
175                 bindingArg());
176         return protoCtx.get();
177     }
178
179     private DataContainerCodecPrototype<CaseSchemaNode> loadCase(final Class<?> childClass) {
180         final Optional<CaseSchemaNode> childSchema =
181                 factory().getRuntimeContext().getCaseSchemaDefinition(getSchema(), childClass);
182         if (childSchema.isPresent()) {
183             return DataContainerCodecPrototype.from(childClass, childSchema.get(), factory());
184         }
185
186         LOG.debug("Supplied class %s is not valid case in schema %s", childClass, getSchema());
187         return null;
188     }
189
190     @Nonnull
191     @Override
192     public NodeCodecContext<?> yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg) {
193         final DataContainerCodecPrototype<?> cazeProto;
194         if (arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
195             cazeProto = byYangCaseChild.get(new NodeIdentifier(arg.getNodeType()));
196         } else {
197             cazeProto = byYangCaseChild.get(arg);
198         }
199
200         return childNonNull(cazeProto, arg, "Argument %s is not valid child of %s", arg, getSchema()).get()
201                 .yangPathArgumentChild(arg);
202     }
203
204     @SuppressWarnings("unchecked")
205     @Nonnull
206     @Override
207     public D deserialize(@Nonnull final NormalizedNode<?, ?> data) {
208         Preconditions.checkArgument(data instanceof ChoiceNode);
209         final NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> casted =
210                 (NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>>) data;
211         final NormalizedNode<?, ?> first = Iterables.getFirst(casted.getValue(), null);
212
213         if (first == null) {
214             return null;
215         }
216         final DataContainerCodecPrototype<?> caze = byYangCaseChild.get(first.getIdentifier());
217         return (D) caze.get().deserialize(data);
218     }
219
220     @Override
221     protected Object deserializeObject(final NormalizedNode<?, ?> normalizedNode) {
222         return deserialize(normalizedNode);
223     }
224
225     @Nonnull
226     @Override
227     public TreeArgument<?> deserializePathArgument(@Nonnull final YangInstanceIdentifier.PathArgument arg) {
228         Preconditions.checkArgument(getDomPathArgument().equals(arg));
229         return null;
230     }
231
232     @Nonnull
233     @Override
234     public YangInstanceIdentifier.PathArgument serializePathArgument(@Nonnull final TreeArgument<?> arg) {
235         return getDomPathArgument();
236     }
237 }