BUG 1975 - yang unkeyed list is transformed to map node
[yangtools.git] / yang / yang-data-operations / src / main / java / org / opendaylight / yangtools / yang / data / operations / AbstractContainerNodeModification.java
1 /*
2  * Copyright (c) 2013 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.yang.data.operations;
9
10 import com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import com.google.common.collect.Lists;
13 import com.google.common.collect.Sets;
14 import java.util.List;
15 import java.util.Set;
16 import org.opendaylight.yangtools.yang.common.QName;
17 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
18 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
19 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
20 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
21 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
22 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
23 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
24 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
25 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
26 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
27 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
28 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
29 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
33
34 abstract class AbstractContainerNodeModification<S, N extends DataContainerNode<?>> implements Modification<S, N> {
35
36     @Override
37     public final Optional<N> modify(S schema, Optional<N> actual, Optional<N> modification,
38             OperationStack operationStack) throws DataModificationException {
39
40         operationStack.enteringNode(modification);
41
42         Optional<N> result;
43         QName nodeQName = getQName(schema);
44
45         switch (operationStack.getCurrentOperation()) {
46         case DELETE: {
47             DataModificationException.DataMissingException.check(nodeQName, actual);
48         }
49         case REMOVE: {
50             result = Optional.absent();
51             break;
52         }
53         case CREATE: {
54             DataModificationException.DataExistsException.check(nodeQName, actual, null);
55         }
56         case REPLACE: {
57             result = modification;
58             break;
59         }
60         case NONE: {
61             DataModificationException.DataMissingException.check(nodeQName, actual);
62         }
63         case MERGE: {
64             // Recursively modify all child nodes
65             result = modifyContainer(schema, actual, modification, operationStack);
66             break;
67         }
68         default:
69             throw new UnsupportedOperationException(String.format("Unable to perform operation: %s on: %s, unknown",
70                     operationStack.getCurrentOperation(), schema));
71         }
72
73         operationStack.exitingNode(modification);
74         return result;
75     }
76
77     protected abstract QName getQName(S schema);
78
79     private Optional<N> modifyContainer(S schema, Optional<N> actual, Optional<N> modification,
80             OperationStack operationStack) throws DataModificationException {
81
82         if (!actual.isPresent()) {
83             return modification;
84         }
85
86         if (!modification.isPresent()) {
87             return actual;
88         }
89
90         Set<YangInstanceIdentifier.PathArgument> toProcess = getChildrenToProcess(schema, actual, modification);
91
92         List<? extends DataContainerChild<?, ?>> merged = modifyContainerChildNodes(schema, operationStack,
93                 actual.get(), modification.get(), toProcess);
94         return build(schema, merged);
95     }
96
97     private List<? extends DataContainerChild<?, ?>> modifyContainerChildNodes(S schema, OperationStack operationStack,
98             N actual, N modification, Set<YangInstanceIdentifier.PathArgument> toProcess) throws DataModificationException {
99         List<DataContainerChild<?, ?>> result = Lists.newArrayList();
100
101         for (YangInstanceIdentifier.PathArgument childToProcessId : toProcess) {
102             Object schemaOfChildToProcess = findSchema(schema, childToProcessId);
103
104             Optional<? extends DataContainerChild<?, ?>> modifiedValues = modifyContainerNode(operationStack, actual,
105                     modification, childToProcessId, schemaOfChildToProcess);
106
107             if (modifiedValues.isPresent()) {
108                 result.add(modifiedValues.get());
109             }
110         }
111
112         return result;
113     }
114
115     private Optional<? extends DataContainerChild<?, ?>> modifyContainerNode(OperationStack operationStack, N actual,
116             N modification, YangInstanceIdentifier.PathArgument childToProcess, Object schemaChild)
117             throws DataModificationException {
118
119         Optional<DataContainerChild<?, ?>> storedChildren = actual.getChild(childToProcess);
120         Optional<DataContainerChild<?, ?>> modifiedChildren = modification.getChild(childToProcess);
121
122         return NodeDispatcher.dispatchChildModification(schemaChild, storedChildren, modifiedChildren, operationStack);
123     }
124
125     private Object findSchema(S schema, YangInstanceIdentifier.PathArgument childToProcessId) {
126         if (childToProcessId instanceof YangInstanceIdentifier.AugmentationIdentifier) {
127             return findSchemaForAugment(schema, (YangInstanceIdentifier.AugmentationIdentifier) childToProcessId);
128         } else {
129             return findSchemaForChild(schema, childToProcessId.getNodeType());
130         }
131     }
132
133     protected abstract Object findSchemaForChild(S schema, QName nodeType);
134
135     protected abstract Object findSchemaForAugment(S schema, YangInstanceIdentifier.AugmentationIdentifier childToProcessId);
136
137     private Optional<N> build(S schema, List<? extends DataContainerChild<?, ?>> result) {
138         DataContainerNodeBuilder<?, N> b = getBuilder(schema);
139
140         // TODO skip empty container nodes ? e.g. if container looses all its child nodes
141 //        if(result.isEmpty()) {
142 //            return Optional.absent();
143 //        }
144
145         for (DataContainerChild<?, ?> dataContainerChild : result) {
146             b.withChild(dataContainerChild);
147         }
148         return Optional.of(b.build());
149     }
150
151     protected abstract DataContainerNodeBuilder<?, N> getBuilder(S schema);
152
153     protected Set<YangInstanceIdentifier.PathArgument> getChildrenToProcess(S schema, Optional<N> actual,
154             Optional<N> modification) throws DataModificationException {
155         Set<YangInstanceIdentifier.PathArgument> qNames = Sets.newLinkedHashSet();
156
157         qNames.addAll(getChildQNames(actual));
158         qNames.addAll(getChildQNames(modification));
159         return qNames;
160     }
161
162     private Set<? extends YangInstanceIdentifier.PathArgument> getChildQNames(Optional<N> actual) {
163         Set<YangInstanceIdentifier.PathArgument> qNames = Sets.newLinkedHashSet();
164
165         for (DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> child : actual.get().getValue()) {
166             qNames.add(child.getIdentifier());
167         }
168
169         return qNames;
170     }
171
172     private static final class NodeDispatcher {
173
174         private static final LeafNodeModification LEAF_NODE_MODIFICATION = new LeafNodeModification();
175         private static final LeafSetNodeModification LEAF_SET_NODE_MODIFICATION = new LeafSetNodeModification();
176         private static final AugmentationNodeModification AUGMENTATION_NODE_MODIFICATION = new AugmentationNodeModification();
177         private static final MapNodeModification MAP_NODE_MODIFICATION = new MapNodeModification();
178         private static final UnkeyedListNodeModification UNKEYED_LIST_NODE_MODIFICATION = new UnkeyedListNodeModification();
179         private static final ContainerNodeModification CONTAINER_NODE_MODIFICATION = new ContainerNodeModification();
180         private static final ChoiceNodeModification CHOICE_NODE_MODIFICATION = new ChoiceNodeModification();
181
182         static Optional<? extends DataContainerChild<?, ?>> dispatchChildModification(Object schemaChild,
183                 Optional<DataContainerChild<?, ?>> actual, Optional<DataContainerChild<?, ?>> modification,
184                 OperationStack operations) throws DataModificationException {
185
186             if (schemaChild instanceof LeafSchemaNode) {
187                 return onLeafNode((LeafSchemaNode) schemaChild, actual, modification, operations);
188             } else if (schemaChild instanceof ContainerSchemaNode) {
189                 return onContainerNode((ContainerSchemaNode) schemaChild, actual, modification, operations);
190             } else if (schemaChild instanceof LeafListSchemaNode) {
191                 return onLeafSetNode((LeafListSchemaNode) schemaChild, actual, modification, operations);
192             } else if (schemaChild instanceof AugmentationSchema) {
193                 return onAugmentationNode((AugmentationSchema) schemaChild, actual, modification, operations);
194             } else if (schemaChild instanceof ListSchemaNode) {
195                 if (((ListSchemaNode)schemaChild).getKeyDefinition().isEmpty()) {
196                     return onUnkeyedNode((ListSchemaNode) schemaChild, actual, modification, operations);
197                 } else {
198                     return onMapNode((ListSchemaNode) schemaChild, actual, modification, operations);
199                 }
200             } else if (schemaChild instanceof org.opendaylight.yangtools.yang.model.api.ChoiceNode) {
201                 return onChoiceNode((org.opendaylight.yangtools.yang.model.api.ChoiceNode) schemaChild, actual,
202                         modification, operations);
203             }
204
205             throw new IllegalArgumentException("Unknown schema node type " + schemaChild);
206         }
207
208         private static Optional<? extends DataContainerChild<?, ?>> onChoiceNode(
209                 org.opendaylight.yangtools.yang.model.api.ChoiceNode schemaChild,
210                 Optional<? extends DataContainerChild<?, ?>> actual,
211                 Optional<? extends DataContainerChild<?, ?>> modification, OperationStack operations)
212                 throws DataModificationException {
213             checkType(actual, ChoiceNode.class);
214             checkType(modification, ChoiceNode.class);
215             return CHOICE_NODE_MODIFICATION.modify(schemaChild, (Optional<ChoiceNode>) actual,
216                     (Optional<ChoiceNode>) modification, operations);
217         }
218
219         private static Optional<? extends DataContainerChild<?, ?>> onMapNode(ListSchemaNode schemaChild,
220                 Optional<? extends DataContainerChild<?, ?>> actual,
221                 Optional<? extends DataContainerChild<?, ?>> modification, OperationStack operations)
222                 throws DataModificationException {
223             checkType(actual, MapNode.class);
224             checkType(modification, MapNode.class);
225             return MAP_NODE_MODIFICATION.modify(schemaChild, (Optional<MapNode>) actual,
226                     (Optional<MapNode>) modification, operations);
227         }
228
229         private static Optional<? extends DataContainerChild<?, ?>> onUnkeyedNode(ListSchemaNode schemaChild,
230                 Optional<? extends DataContainerChild<?, ?>> actual,
231                         Optional<? extends DataContainerChild<?, ?>> modification, OperationStack operations)
232                                 throws DataModificationException {
233             checkType(actual, UnkeyedListNode.class);
234             checkType(modification, UnkeyedListNode.class);
235             return UNKEYED_LIST_NODE_MODIFICATION.modify(schemaChild, (Optional<UnkeyedListNode>) actual,
236                     (Optional<UnkeyedListNode>) modification, operations);
237         }
238
239         private static Optional<? extends DataContainerChild<?, ?>> onAugmentationNode(AugmentationSchema schemaChild,
240                 Optional<? extends DataContainerChild<?, ?>> actual,
241                 Optional<? extends DataContainerChild<?, ?>> modification, OperationStack operations)
242                 throws DataModificationException {
243             checkType(actual, AugmentationNode.class);
244             checkType(modification, AugmentationNode.class);
245             return AUGMENTATION_NODE_MODIFICATION.modify(schemaChild, (Optional<AugmentationNode>) actual,
246                     (Optional<AugmentationNode>) modification, operations);
247         }
248
249         private static Optional<? extends DataContainerChild<?, ?>> onLeafSetNode(LeafListSchemaNode schemaChild,
250                 Optional<? extends DataContainerChild<?, ?>> actual,
251                 Optional<? extends DataContainerChild<?, ?>> modification, OperationStack operations)
252                 throws DataModificationException {
253             checkType(actual, LeafSetNode.class);
254             checkType(modification, LeafSetNode.class);
255             return LEAF_SET_NODE_MODIFICATION.modify(schemaChild, (Optional<LeafSetNode<?>>) actual,
256                     (Optional<LeafSetNode<?>>) modification, operations);
257         }
258
259         private static Optional<? extends DataContainerChild<?, ?>> onContainerNode(ContainerSchemaNode schemaChild,
260                 Optional<? extends DataContainerChild<?, ?>> actual,
261                 Optional<? extends DataContainerChild<?, ?>> modification, OperationStack operations)
262                 throws DataModificationException {
263             checkType(actual, ContainerNode.class);
264             checkType(modification, ContainerNode.class);
265             return CONTAINER_NODE_MODIFICATION.modify(schemaChild, (Optional<ContainerNode>) actual,
266                     (Optional<ContainerNode>) modification, operations);
267         }
268
269         private static Optional<? extends DataContainerChild<?, ?>> onLeafNode(LeafSchemaNode schemaChild,
270                 Optional<? extends DataContainerChild<?, ?>> actual,
271                 Optional<? extends DataContainerChild<?, ?>> modification, OperationStack operations)
272                 throws DataModificationException {
273             checkType(actual, LeafNode.class);
274             checkType(modification, LeafNode.class);
275             return LEAF_NODE_MODIFICATION.modify(schemaChild, (Optional<LeafNode<?>>) actual,
276                     (Optional<LeafNode<?>>) modification, operations);
277         }
278
279         private static void checkType(Optional<? extends DataContainerChild<?, ?>> actual, Class<?> leafNodeClass) {
280             if (actual.isPresent()) {
281                 Preconditions.checkArgument(leafNodeClass.isAssignableFrom(actual.get().getClass()),
282                         "Unexpected node type, should be: %s, but was: %s, for: %s", leafNodeClass, actual.getClass(),
283                         actual);
284             }
285         }
286     }
287 }