Bug 4798: Can not define a list as a subordinate
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / yangtools / binding / data / codec / impl / ChoiceNodeCodecContext.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. 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.yangtools.binding.data.codec.impl;
9
10 import com.google.common.base.Optional;
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.Set;
19 import javax.annotation.Nonnull;
20 import javax.annotation.Nullable;
21 import org.opendaylight.yangtools.yang.binding.DataObject;
22 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
23 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
26 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
27 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
28 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
29 import org.opendaylight.yangtools.yang.data.impl.schema.SchemaUtils;
30 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
31 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
32 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 final class ChoiceNodeCodecContext<D extends DataObject> extends DataContainerCodecContext<D,ChoiceSchemaNode> {
38     private static final Logger LOG = LoggerFactory.getLogger(ChoiceNodeCodecContext.class);
39     private final ImmutableMap<YangInstanceIdentifier.PathArgument, DataContainerCodecPrototype<?>> byYangCaseChild;
40     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byClass;
41     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byCaseChildClass;
42
43     public ChoiceNodeCodecContext(final DataContainerCodecPrototype<ChoiceSchemaNode> prototype) {
44         super(prototype);
45         final Map<YangInstanceIdentifier.PathArgument, DataContainerCodecPrototype<?>> byYangCaseChildBuilder = new HashMap<>();
46         final Map<Class<?>, DataContainerCodecPrototype<?>> byClassBuilder = new HashMap<>();
47         final Map<Class<?>, DataContainerCodecPrototype<?>> byCaseChildClassBuilder = new HashMap<>();
48         final Set<Class<?>> potentialSubstitutions = new HashSet<>();
49         // Walks all cases for supplied choice in current runtime context
50         for (final Class<?> caze : factory().getRuntimeContext().getCases(getBindingClass())) {
51             // We try to load case using exact match thus name
52             // and original schema must equals
53             final DataContainerCodecPrototype<ChoiceCaseNode> cazeDef = loadCase(caze);
54             // If we have case definition, this case is instantiated
55             // at current location and thus is valid in context of parent choice
56             if (cazeDef != null) {
57                 byClassBuilder.put(cazeDef.getBindingClass(), cazeDef);
58                 // Updates collection of case children
59                 @SuppressWarnings("unchecked")
60                 final Class<? extends DataObject> cazeCls = (Class<? extends DataObject>) caze;
61                 for (final Class<? extends DataObject> cazeChild : BindingReflections.getChildrenClasses(cazeCls)) {
62                     byCaseChildClassBuilder.put(cazeChild, cazeDef);
63                 }
64                 // Updates collection of YANG instance identifier to case
65                 for (final DataSchemaNode cazeChild : cazeDef.getSchema().getChildNodes()) {
66                     if (cazeChild.isAugmenting()) {
67                         final AugmentationSchema augment = SchemaUtils.findCorrespondingAugment(cazeDef.getSchema(), cazeChild);
68                         if (augment != null) {
69                             byYangCaseChildBuilder.put(SchemaUtils.getNodeIdentifierForAugmentation(augment), cazeDef);
70                             continue;
71                         }
72                     }
73                     byYangCaseChildBuilder.put(NodeIdentifier.create(cazeChild.getQName()), cazeDef);
74                 }
75             } else {
76                 /*
77                  * If case definition is not available, we store it for
78                  * later check if it could be used as substitution of existing one.
79                  */
80                 potentialSubstitutions.add(caze);
81             }
82         }
83
84         final Map<Class<?>, DataContainerCodecPrototype<?>> bySubstitutionBuilder = new HashMap<>();
85         /*
86          * Walks all cases which are not directly instantiated and
87          * tries to match them to instantiated cases - represent same data as instantiated case,
88          * only case name or schema path is different. This is required due property of
89          * binding specification, that if choice is in grouping schema path location is lost,
90          * and users may use incorrect case class using copy builders.
91          */
92         for(final Class<?> substitution : potentialSubstitutions) {
93             search: for(final Entry<Class<?>, DataContainerCodecPrototype<?>> real : byClassBuilder.entrySet()) {
94                 if(BindingReflections.isSubstitutionFor(substitution, real.getKey())) {
95                     bySubstitutionBuilder.put(substitution, real.getValue());
96                     break search;
97                 }
98             }
99         }
100         byClassBuilder.putAll(bySubstitutionBuilder);
101         byYangCaseChild = ImmutableMap.copyOf(byYangCaseChildBuilder);
102         byClass = ImmutableMap.copyOf(byClassBuilder);
103         byCaseChildClass = ImmutableMap.copyOf(byCaseChildClassBuilder);
104     }
105
106     @SuppressWarnings("unchecked")
107     @Override
108     public <DV extends DataObject> DataContainerCodecContext<DV, ?> streamChild(final Class<DV> childClass) {
109         final DataContainerCodecPrototype<?> child = byClass.get(childClass);
110         return (DataContainerCodecContext<DV, ?>) childNonNull(child, childClass, "Supplied class %s is not valid case", childClass).get();
111     }
112
113     @SuppressWarnings("unchecked")
114     @Override
115     public <DV extends DataObject> Optional<DataContainerCodecContext<DV, ?>> possibleStreamChild(
116             final Class<DV> childClass) {
117         final DataContainerCodecPrototype<?> child = byClass.get(childClass);
118         if(child != null) {
119             return Optional.<DataContainerCodecContext<DV,?>>of((DataContainerCodecContext<DV, ?>) child.get());
120         }
121         return Optional.absent();
122     }
123
124     Iterable<Class<?>> getCaseChildrenClasses() {
125         return byCaseChildClass.keySet();
126     }
127
128     protected DataContainerCodecPrototype<ChoiceCaseNode> loadCase(final Class<?> childClass) {
129         final Optional<ChoiceCaseNode> childSchema = factory().getRuntimeContext().getCaseSchemaDefinition(getSchema(), childClass);
130         if (childSchema.isPresent()) {
131             return DataContainerCodecPrototype.from(childClass, childSchema.get(), factory());
132         }
133
134         LOG.debug("Supplied class %s is not valid case in schema %s", childClass, getSchema());
135         return null;
136     }
137
138     @Override
139     public NodeCodecContext<?> yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg) {
140         final DataContainerCodecPrototype<?> cazeProto;
141         if (arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
142             cazeProto = byYangCaseChild.get(new NodeIdentifier(arg.getNodeType()));
143         } else {
144             cazeProto = byYangCaseChild.get(arg);
145         }
146
147         childNonNull(cazeProto != null, arg,"Argument %s is not valid child of %s", arg, getSchema());
148         return cazeProto.get().yangPathArgumentChild(arg);
149     }
150
151     @SuppressWarnings("unchecked")
152     @Override
153     @Nullable
154     public D deserialize(final NormalizedNode<?, ?> data) {
155         Preconditions.checkArgument(data instanceof ChoiceNode);
156         final NormalizedNodeContainer<?, ?, NormalizedNode<?,?>> casted = (NormalizedNodeContainer<?, ?, NormalizedNode<?,?>>) data;
157         final NormalizedNode<?, ?> first = Iterables.getFirst(casted.getValue(), null);
158
159         if (first == null) {
160             return null;
161         }
162         final DataContainerCodecPrototype<?> caze = byYangCaseChild.get(first.getIdentifier());
163         return (D) caze.get().deserialize(data);
164     }
165
166     DataContainerCodecContext<?, ?> getCazeByChildClass(final @Nonnull Class<? extends DataObject> type) {
167         final DataContainerCodecPrototype<?> protoCtx =
168                 childNonNull(byCaseChildClass.get(type), type, "Class %s is not child of any cases for %s", type,
169                         bindingArg());
170         return protoCtx.get();
171     }
172
173     @Override
174     protected Object deserializeObject(final NormalizedNode<?, ?> normalizedNode) {
175         return deserialize(normalizedNode);
176     }
177
178     @Override
179     public PathArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) {
180         Preconditions.checkArgument(getDomPathArgument().equals(arg));
181         return null;
182     }
183
184     @Override
185     public YangInstanceIdentifier.PathArgument serializePathArgument(final PathArgument arg) {
186         // FIXME: check for null, since binding container is null.
187         return getDomPathArgument();
188     }
189 }