Bump yangtools to 3.0.0
[controller.git] / opendaylight / md-sal / sal-binding-broker / src / main / java / org / opendaylight / controller / md / sal / binding / impl / LazyDataObjectModification.java
1 /*
2  * Copyright (c) 2015 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.controller.md.sal.binding.impl;
9
10 import static java.util.Objects.requireNonNull;
11 import static org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType.UNMODIFIED;
12
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.Optional;
18 import java.util.stream.Collectors;
19 import java.util.stream.Stream;
20 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
21 import org.opendaylight.mdsal.binding.dom.adapter.BindingStructuralType;
22 import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTreeNode;
23 import org.opendaylight.yangtools.yang.binding.Augmentation;
24 import org.opendaylight.yangtools.yang.binding.ChildOf;
25 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
26 import org.opendaylight.yangtools.yang.binding.DataObject;
27 import org.opendaylight.yangtools.yang.binding.Identifiable;
28 import org.opendaylight.yangtools.yang.binding.Identifier;
29 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.IdentifiableItem;
30 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
31 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 /**
39  * Lazily translated {@link DataObjectModification} based on {@link DataTreeCandidateNode}.
40  *
41  * {@link LazyDataObjectModification} represents Data tree change event,
42  * but whole tree is not translated or resolved eagerly, but only child nodes
43  * which are directly accessed by user of data object modification.
44  *
45  * @param <T> Type of Binding Data Object
46  */
47 final class LazyDataObjectModification<T extends DataObject> implements DataObjectModification<T> {
48
49     private static final Logger LOG = LoggerFactory.getLogger(LazyDataObjectModification.class);
50
51     private final BindingCodecTreeNode<T> codec;
52     private final DataTreeCandidateNode domData;
53     private final PathArgument identifier;
54
55     private volatile Collection<LazyDataObjectModification<? extends DataObject>> childNodesCache;
56     private volatile ModificationType modificationType;
57
58     private LazyDataObjectModification(final BindingCodecTreeNode<T> codec, final DataTreeCandidateNode domData) {
59         this.codec = requireNonNull(codec);
60         this.domData = requireNonNull(domData);
61         this.identifier = codec.deserializePathArgument(domData.getIdentifier());
62     }
63
64     static <T extends DataObject> LazyDataObjectModification<T> create(final BindingCodecTreeNode<T> codec,
65             final DataTreeCandidateNode domData) {
66         return new LazyDataObjectModification<>(codec, domData);
67     }
68
69     private static Collection<LazyDataObjectModification<? extends DataObject>> from(
70             final BindingCodecTreeNode<?> parentCodec, final Collection<DataTreeCandidateNode> domChildNodes) {
71         final List<LazyDataObjectModification<? extends DataObject>> result = new ArrayList<>(domChildNodes.size());
72         populateList(result, parentCodec, domChildNodes);
73         return result;
74     }
75
76     private static void populateList(final List<LazyDataObjectModification<? extends DataObject>> result,
77             final BindingCodecTreeNode<?> parentCodec, final Collection<DataTreeCandidateNode> domChildNodes) {
78         for (final DataTreeCandidateNode domChildNode : domChildNodes) {
79             if (domChildNode.getModificationType() != UNMODIFIED) {
80                 final BindingStructuralType type = BindingStructuralType.from(domChildNode);
81                 if (type != BindingStructuralType.NOT_ADDRESSABLE) {
82                     /*
83                      *  Even if type is UNKNOWN, from perspective of BindingStructuralType
84                      *  we try to load codec for it. We will use that type to further specify
85                      *  debug log.
86                      */
87                     try {
88                         final BindingCodecTreeNode<?> childCodec =
89                                 parentCodec.yangPathArgumentChild(domChildNode.getIdentifier());
90                         populateList(result, type, childCodec, domChildNode);
91                     } catch (final IllegalArgumentException e) {
92                         if (type == BindingStructuralType.UNKNOWN) {
93                             LOG.debug("Unable to deserialize unknown DOM node {}", domChildNode, e);
94                         } else {
95                             LOG.debug("Binding representation for DOM node {} was not found", domChildNode, e);
96                         }
97                     }
98                 }
99             }
100         }
101     }
102
103     private static void populateList(final List<LazyDataObjectModification<? extends DataObject>> result,
104             final BindingStructuralType type, final BindingCodecTreeNode<?> childCodec,
105             final DataTreeCandidateNode domChildNode) {
106         switch (type) {
107             case INVISIBLE_LIST:
108                 // We use parent codec intentionally.
109                 populateListWithSingleCodec(result, childCodec, domChildNode.getChildNodes());
110                 break;
111             case INVISIBLE_CONTAINER:
112                 populateList(result, childCodec, domChildNode.getChildNodes());
113                 break;
114             case UNKNOWN:
115             case VISIBLE_CONTAINER:
116                 result.add(create(childCodec, domChildNode));
117                 break;
118             default:
119                 break;
120         }
121     }
122
123     private static void populateListWithSingleCodec(final List<LazyDataObjectModification<? extends DataObject>> result,
124             final BindingCodecTreeNode<?> codec, final Collection<DataTreeCandidateNode> childNodes) {
125         for (final DataTreeCandidateNode child : childNodes) {
126             if (child.getModificationType() != UNMODIFIED) {
127                 result.add(create(codec, child));
128             }
129         }
130     }
131
132     @Override
133     public T getDataBefore() {
134         return deserialize(domData.getDataBefore());
135     }
136
137     @Override
138     public T getDataAfter() {
139         return deserialize(domData.getDataAfter());
140     }
141
142     @Override
143     public Class<T> getDataType() {
144         return codec.getBindingClass();
145     }
146
147     @Override
148     public PathArgument getIdentifier() {
149         return identifier;
150     }
151
152     @Override
153     public ModificationType getModificationType() {
154         ModificationType localType = modificationType;
155         if (localType != null) {
156             return localType;
157         }
158
159         switch (domData.getModificationType()) {
160             case APPEARED:
161             case WRITE:
162                 localType = ModificationType.WRITE;
163                 break;
164             case DISAPPEARED:
165             case DELETE:
166                 localType = ModificationType.DELETE;
167                 break;
168             case SUBTREE_MODIFIED:
169                 localType = resolveSubtreeModificationType();
170                 break;
171             default:
172                 // TODO: Should we lie about modification type instead of exception?
173                 throw new IllegalStateException("Unsupported DOM Modification type " + domData.getModificationType());
174         }
175
176         modificationType = localType;
177         return localType;
178     }
179
180     private ModificationType resolveSubtreeModificationType() {
181         switch (codec.getChildAddressabilitySummary()) {
182             case ADDRESSABLE:
183                 // All children are addressable, it is safe to report SUBTREE_MODIFIED
184                 return ModificationType.SUBTREE_MODIFIED;
185             case UNADDRESSABLE:
186                 // All children are non-addressable, report WRITE
187                 return ModificationType.WRITE;
188             case MIXED:
189                 // This case is not completely trivial, as we may have NOT_ADDRESSABLE nodes underneath us. If that
190                 // is the case, we need to turn this modification into a WRITE operation, so that the user is able
191                 // to observe those nodes being introduced. This is not efficient, but unfortunately unavoidable,
192                 // as we cannot accurately represent such changes.
193                 for (DataTreeCandidateNode child : domData.getChildNodes()) {
194                     if (BindingStructuralType.recursiveFrom(child) == BindingStructuralType.NOT_ADDRESSABLE) {
195                         // We have a non-addressable child, turn this modification into a write
196                         return ModificationType.WRITE;
197                     }
198                 }
199
200                 // No unaddressable children found, proceed in addressed mode
201                 return ModificationType.SUBTREE_MODIFIED;
202             default:
203                 throw new IllegalStateException("Unsupported child addressability summary "
204                         + codec.getChildAddressabilitySummary());
205         }
206     }
207
208     @Override
209     public Collection<LazyDataObjectModification<? extends DataObject>> getModifiedChildren() {
210         Collection<LazyDataObjectModification<? extends DataObject>> local = childNodesCache;
211         if (local == null) {
212             childNodesCache = local = from(codec, domData.getChildNodes());
213         }
214         return local;
215     }
216
217     @Override
218     public <H extends ChoiceIn<? super T> & DataObject, C extends ChildOf<? super H>>
219             Collection<DataObjectModification<C>> getModifiedChildren(final Class<H> caseType,
220                     final Class<C> childType) {
221         return streamModifiedChildren(childType)
222                 .filter(child -> caseType.equals(child.identifier.getCaseType().orElse(null)))
223                 .collect(Collectors.toList());
224     }
225
226     @SuppressWarnings("unchecked")
227     private <C extends DataObject> Stream<LazyDataObjectModification<C>> streamModifiedChildren(
228             final Class<C> childType) {
229         return getModifiedChildren().stream()
230                 .filter(child -> childType.isAssignableFrom(child.getDataType()))
231                 .map(child -> (LazyDataObjectModification<C>) child);
232     }
233
234     @Override
235     public DataObjectModification<? extends DataObject> getModifiedChild(final PathArgument arg) {
236         final List<YangInstanceIdentifier.PathArgument> domArgumentList = new ArrayList<>();
237         final BindingCodecTreeNode<?> childCodec = codec.bindingPathArgumentChild(arg, domArgumentList);
238         final Iterator<YangInstanceIdentifier.PathArgument> toEnter = domArgumentList.iterator();
239         DataTreeCandidateNode current = domData;
240         while (toEnter.hasNext() && current != null) {
241             current = current.getModifiedChild(toEnter.next()).orElse(null);
242         }
243         return current != null && current.getModificationType() != UNMODIFIED ? create(childCodec, current) : null;
244     }
245
246     @Override
247     @SuppressWarnings("unchecked")
248     public <C extends Identifiable<K> & ChildOf<? super T>, K extends Identifier<C>> DataObjectModification<C>
249             getModifiedChildListItem(final Class<C> listItem, final K listKey) {
250         return (DataObjectModification<C>) getModifiedChild(IdentifiableItem.of(listItem, listKey));
251     }
252
253     @Override
254     @SuppressWarnings("unchecked")
255     public <H extends ChoiceIn<? super T> & DataObject, C extends Identifiable<K> & ChildOf<? super H>,
256             K extends Identifier<C>> DataObjectModification<C> getModifiedChildListItem(final Class<H> caseType,
257                         final Class<C> listItem, final K listKey) {
258         return (DataObjectModification<C>) getModifiedChild(IdentifiableItem.of(caseType, listItem, listKey));
259     }
260
261     @Override
262     @SuppressWarnings("unchecked")
263     public <C extends ChildOf<? super T>> DataObjectModification<C> getModifiedChildContainer(final Class<C> child) {
264         return (DataObjectModification<C>) getModifiedChild(Item.of(child));
265     }
266
267     @Override
268     @SuppressWarnings("unchecked")
269     public <H extends ChoiceIn<? super T> & DataObject, C extends ChildOf<? super H>> DataObjectModification<C>
270             getModifiedChildContainer(final Class<H> caseType, final Class<C> child) {
271         return (DataObjectModification<C>) getModifiedChild(Item.of(caseType, child));
272     }
273
274     @Override
275     @SuppressWarnings("unchecked")
276     public <C extends Augmentation<T> & DataObject> DataObjectModification<C> getModifiedAugmentation(
277             final Class<C> augmentation) {
278         return (DataObjectModification<C>) getModifiedChild(Item.of(augmentation));
279     }
280
281     private T deserialize(final Optional<NormalizedNode<?, ?>> dataAfter) {
282         return dataAfter.map(codec::deserialize).orElse(null);
283     }
284
285     @Override
286     public String toString() {
287         return getClass().getSimpleName() + "{identifier = " + identifier + ", domData = " + domData + "}";
288     }
289 }