Update binding-dom adaptation to remove AugmentationNode
[mdsal.git] / binding / mdsal-binding-dom-adapter / src / main / java / org / opendaylight / mdsal / binding / dom / adapter / AbstractDataObjectModification.java
1 /*
2  * Copyright (c) 2023 PANTHEON.tech, 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.dom.adapter;
9
10 import static java.util.Objects.requireNonNull;
11 import static org.opendaylight.yangtools.yang.data.tree.api.ModificationType.UNMODIFIED;
12
13 import com.google.common.base.MoreObjects;
14 import com.google.common.base.MoreObjects.ToStringHelper;
15 import com.google.common.base.VerifyException;
16 import com.google.common.collect.ArrayListMultimap;
17 import com.google.common.collect.ImmutableList;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.List;
21 import java.util.Optional;
22 import java.util.stream.Collectors;
23 import java.util.stream.Stream;
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.opendaylight.mdsal.binding.api.DataObjectModification;
27 import org.opendaylight.mdsal.binding.dom.codec.api.BindingAugmentationCodecTreeNode;
28 import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode;
29 import org.opendaylight.mdsal.binding.dom.codec.api.CommonDataObjectCodecTreeNode;
30 import org.opendaylight.yangtools.yang.binding.Augmentation;
31 import org.opendaylight.yangtools.yang.binding.ChildOf;
32 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
33 import org.opendaylight.yangtools.yang.binding.DataObject;
34 import org.opendaylight.yangtools.yang.binding.Identifiable;
35 import org.opendaylight.yangtools.yang.binding.Identifier;
36 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.IdentifiableItem;
37 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
38 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
40 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
41 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * Lazily translated {@link DataObjectModification} based on {@link DataTreeCandidateNode}.
47  * {@link AbstractDataObjectModification} represents Data tree change event, but whole tree is not translated or
48  * resolved eagerly, but only child nodes which are directly accessed by user of data object modification.
49  *
50  * <p>
51  * This class is further specialized as {@link LazyAugmentationModification} and {@link LazyDataObjectModification}, as
52  * both use different serialization methods.
53  *
54  * @param <T> Type of Binding {@link DataObject}
55  * @param <N> Type of underlying {@link CommonDataObjectCodecTreeNode}
56  */
57 abstract sealed class AbstractDataObjectModification<T extends DataObject, N extends CommonDataObjectCodecTreeNode<T>>
58         implements DataObjectModification<T>
59         permits LazyAugmentationModification, LazyDataObjectModification {
60     private static final Logger LOG = LoggerFactory.getLogger(AbstractDataObjectModification.class);
61
62     final @NonNull DataTreeCandidateNode domData;
63     final @NonNull PathArgument identifier;
64     final @NonNull N codec;
65
66     private volatile ImmutableList<AbstractDataObjectModification<?, ?>> childNodesCache;
67     private volatile ModificationType modificationType;
68     private volatile @Nullable T dataBefore;
69     private volatile @Nullable T dataAfter;
70
71     AbstractDataObjectModification(final DataTreeCandidateNode domData, final N codec, final PathArgument identifier) {
72         this.domData = requireNonNull(domData);
73         this.identifier = requireNonNull(identifier);
74         this.codec = requireNonNull(codec);
75     }
76
77     static @Nullable AbstractDataObjectModification<?, ?> from(final CommonDataObjectCodecTreeNode<?> codec,
78             final @NonNull DataTreeCandidateNode current) {
79         if (codec instanceof BindingDataObjectCodecTreeNode<?> childDataObjectCodec) {
80             return new LazyDataObjectModification<>(childDataObjectCodec, current);
81         } else if (codec instanceof BindingAugmentationCodecTreeNode<?> childAugmentationCodec) {
82             return LazyAugmentationModification.forParent(childAugmentationCodec, current);
83         } else {
84             throw new VerifyException("Unhandled codec " + codec);
85         }
86     }
87
88     @Override
89     public final Class<T> getDataType() {
90         return codec.getBindingClass();
91     }
92
93     @Override
94     public final PathArgument getIdentifier() {
95         return identifier;
96     }
97
98     @Override
99     public final ModificationType getModificationType() {
100         var localType = modificationType;
101         if (localType != null) {
102             return localType;
103         }
104
105         final var domModificationType = domModificationType();
106         modificationType = localType = switch (domModificationType) {
107             case APPEARED, WRITE -> ModificationType.WRITE;
108             case DISAPPEARED, DELETE -> ModificationType.DELETE;
109             case SUBTREE_MODIFIED -> resolveSubtreeModificationType();
110             default ->
111                 // TODO: Should we lie about modification type instead of exception?
112                 throw new IllegalStateException("Unsupported DOM Modification type " + domModificationType);
113         };
114         return localType;
115     }
116
117     @Override
118     public final T getDataBefore() {
119         var local = dataBefore;
120         if (local == null) {
121             dataBefore = local = deserialize(domData.getDataBefore());
122         }
123         return local;
124     }
125
126     @Override
127     public final T getDataAfter() {
128         var local = dataAfter;
129         if (local == null) {
130             dataAfter = local = deserialize(domData.getDataAfter());
131         }
132         return local;
133     }
134
135     private @Nullable T deserialize(final Optional<NormalizedNode> normalized) {
136         return normalized.isEmpty() ? null : deserialize(normalized.orElseThrow());
137     }
138
139     abstract @Nullable T deserialize(@NonNull NormalizedNode normalized);
140
141     @Override
142     public final DataObjectModification<?> getModifiedChild(final PathArgument arg) {
143         final var domArgumentList = new ArrayList<YangInstanceIdentifier.PathArgument>();
144         final var childCodec = codec.bindingPathArgumentChild(arg, domArgumentList);
145         final var toEnter = domArgumentList.iterator();
146
147         // Careful now: we need to validated the first item against subclass
148         var current = toEnter.hasNext() ? firstModifiedChild(toEnter.next()) : domData;
149         // ... and for everything else we can just go wild
150         while (toEnter.hasNext() && current != null) {
151             current = current.getModifiedChild(toEnter.next()).orElse(null);
152         }
153
154         if (current == null || current.getModificationType() == UNMODIFIED) {
155             return null;
156         }
157         return from(childCodec, current);
158     }
159
160     abstract @Nullable DataTreeCandidateNode firstModifiedChild(YangInstanceIdentifier.PathArgument arg);
161
162     @Override
163     public final ImmutableList<AbstractDataObjectModification<?, ?>> getModifiedChildren() {
164         var local = childNodesCache;
165         if (local == null) {
166             childNodesCache = local = createModifiedChilden(codec, domData, domChildNodes());
167         }
168         return local;
169     }
170
171     @Override
172     public final <C extends ChildOf<? super T>> List<DataObjectModification<C>> getModifiedChildren(
173             final Class<C> childType) {
174         return streamModifiedChildren(childType).collect(Collectors.toList());
175     }
176
177     @Override
178     public final <H extends ChoiceIn<? super T> & DataObject, C extends ChildOf<? super H>>
179             List<DataObjectModification<C>> getModifiedChildren(final Class<H> caseType, final Class<C> childType) {
180         return streamModifiedChildren(childType)
181             .filter(child -> caseType.equals(child.identifier.getCaseType().orElse(null)))
182             .collect(Collectors.toList());
183     }
184
185     @SuppressWarnings("unchecked")
186     private <C extends DataObject> Stream<LazyDataObjectModification<C>> streamModifiedChildren(
187             final Class<C> childType) {
188         return getModifiedChildren().stream()
189             .filter(child -> childType.isAssignableFrom(child.getDataType()))
190             .map(child -> (LazyDataObjectModification<C>) child);
191     }
192
193     @Override
194     @SuppressWarnings("unchecked")
195     public final <C extends Identifiable<K> & ChildOf<? super T>, K extends Identifier<C>> DataObjectModification<C>
196             getModifiedChildListItem(final Class<C> listItem, final K listKey) {
197         return (DataObjectModification<C>) getModifiedChild(IdentifiableItem.of(listItem, listKey));
198     }
199
200     @Override
201     @SuppressWarnings("unchecked")
202     public final <H extends ChoiceIn<? super T> & DataObject, C extends Identifiable<K> & ChildOf<? super H>,
203             K extends Identifier<C>> DataObjectModification<C> getModifiedChildListItem(final Class<H> caseType,
204                     final Class<C> listItem, final K listKey) {
205         return (DataObjectModification<C>) getModifiedChild(IdentifiableItem.of(caseType, listItem, listKey));
206     }
207
208     @Override
209     @SuppressWarnings("unchecked")
210     public final <C extends ChildOf<? super T>> DataObjectModification<C> getModifiedChildContainer(
211             final Class<C> child) {
212         return (DataObjectModification<C>) getModifiedChild(Item.of(child));
213     }
214
215     @Override
216     @SuppressWarnings("unchecked")
217     public final <H extends ChoiceIn<? super T> & DataObject, C extends ChildOf<? super H>> DataObjectModification<C>
218             getModifiedChildContainer(final Class<H> caseType, final Class<C> child) {
219         return (DataObjectModification<C>) getModifiedChild(Item.of(caseType, child));
220     }
221
222     @Override
223     @SuppressWarnings("unchecked")
224     public final <C extends Augmentation<T> & DataObject> DataObjectModification<C> getModifiedAugmentation(
225             final Class<C> augmentation) {
226         return (DataObjectModification<C>) getModifiedChild(Item.of(augmentation));
227     }
228
229     @Override
230     public final String toString() {
231         return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
232     }
233
234     ToStringHelper addToStringAttributes(final ToStringHelper helper) {
235         return helper.add("identifier", identifier).add("domData", domData);
236     }
237
238     abstract @NonNull Collection<DataTreeCandidateNode> domChildNodes();
239
240     abstract org.opendaylight.yangtools.yang.data.tree.api.@NonNull ModificationType domModificationType();
241
242     private @NonNull ModificationType resolveSubtreeModificationType() {
243         return switch (codec.getChildAddressabilitySummary()) {
244             case ADDRESSABLE ->
245                 // All children are addressable, it is safe to report SUBTREE_MODIFIED
246                 ModificationType.SUBTREE_MODIFIED;
247             case UNADDRESSABLE ->
248                 // All children are non-addressable, report WRITE
249                 ModificationType.WRITE;
250             case MIXED -> {
251                 // This case is not completely trivial, as we may have NOT_ADDRESSABLE nodes underneath us. If that
252                 // is the case, we need to turn this modification into a WRITE operation, so that the user is able
253                 // to observe those nodes being introduced. This is not efficient, but unfortunately unavoidable,
254                 // as we cannot accurately represent such changes.
255                 for (DataTreeCandidateNode child : domChildNodes()) {
256                     if (BindingStructuralType.recursiveFrom(child) == BindingStructuralType.NOT_ADDRESSABLE) {
257                         // We have a non-addressable child, turn this modification into a write
258                         yield ModificationType.WRITE;
259                     }
260                 }
261
262                 // No unaddressable children found, proceed in addressed mode
263                 yield ModificationType.SUBTREE_MODIFIED;
264             }
265         };
266     }
267
268     private static @NonNull ImmutableList<AbstractDataObjectModification<?, ?>> createModifiedChilden(
269             final CommonDataObjectCodecTreeNode<?> parentCodec, final DataTreeCandidateNode parent,
270             final Collection<DataTreeCandidateNode> children) {
271         final var result = ImmutableList.<AbstractDataObjectModification<?, ?>>builder();
272         populateList(result, parentCodec, parent, children);
273         return result.build();
274     }
275
276     private static void populateList(final ImmutableList.Builder<AbstractDataObjectModification<?, ?>> result,
277             final CommonDataObjectCodecTreeNode<?> parentCodec, final DataTreeCandidateNode parent,
278             final Collection<DataTreeCandidateNode> children) {
279         final var augmentChildren =
280             ArrayListMultimap.<BindingAugmentationCodecTreeNode<?>, DataTreeCandidateNode>create();
281
282         for (var domChildNode : parent.getChildNodes()) {
283             if (domChildNode.getModificationType() != UNMODIFIED) {
284                 final var type = BindingStructuralType.from(domChildNode);
285                 if (type != BindingStructuralType.NOT_ADDRESSABLE) {
286                     /*
287                      * Even if type is UNKNOWN, from perspective of BindingStructuralType we try to load codec for it.
288                      * We will use that type to further specify debug log.
289                      */
290                     try {
291                         final var childCodec = parentCodec.yangPathArgumentChild(domChildNode.getIdentifier());
292                         if (childCodec instanceof BindingDataObjectCodecTreeNode<?> childDataObjectCodec) {
293                             populateList(result, type, childDataObjectCodec, domChildNode);
294                         } else if (childCodec instanceof BindingAugmentationCodecTreeNode<?> childAugmentationCodec) {
295                             // Defer creation once we have collected all modified children
296                             augmentChildren.put(childAugmentationCodec, domChildNode);
297                         } else {
298                             throw new VerifyException("Unhandled codec %s for type %s".formatted(childCodec, type));
299                         }
300                     } catch (final IllegalArgumentException e) {
301                         if (type == BindingStructuralType.UNKNOWN) {
302                             LOG.debug("Unable to deserialize unknown DOM node {}", domChildNode, e);
303                         } else {
304                             LOG.debug("Binding representation for DOM node {} was not found", domChildNode, e);
305                         }
306                     }
307                 }
308             }
309         }
310
311         for (var entry : augmentChildren.asMap().entrySet()) {
312             final var modification = LazyAugmentationModification.forModifications(entry.getKey(), parent,
313                 entry.getValue());
314             if (modification != null) {
315                 result.add(modification);
316             }
317         }
318     }
319
320     private static void populateList(final ImmutableList.Builder<AbstractDataObjectModification<?, ?>> result,
321             final BindingStructuralType type, final BindingDataObjectCodecTreeNode<?> childCodec,
322             final DataTreeCandidateNode domChildNode) {
323         switch (type) {
324             case INVISIBLE_LIST:
325                 // We use parent codec intentionally.
326                 populateListWithSingleCodec(result, childCodec, domChildNode.getChildNodes());
327                 break;
328             case INVISIBLE_CONTAINER:
329                 populateList(result, childCodec, domChildNode, domChildNode.getChildNodes());
330                 break;
331             case UNKNOWN:
332             case VISIBLE_CONTAINER:
333                 result.add(new LazyDataObjectModification<>(childCodec, domChildNode));
334                 break;
335             default:
336         }
337     }
338
339     private static void populateListWithSingleCodec(
340             final ImmutableList.Builder<AbstractDataObjectModification<?, ?>> result,
341             final BindingDataObjectCodecTreeNode<?> codec, final Collection<DataTreeCandidateNode> childNodes) {
342         for (var child : childNodes) {
343             if (child.getModificationType() != UNMODIFIED) {
344                 result.add(new LazyDataObjectModification<>(codec, child));
345             }
346         }
347     }
348 }