Generate DataObject codec implementation
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / AugmentableCodecDataObject.java
1 /*
2  * Copyright (c) 2019 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.codec.impl;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.base.MoreObjects.ToStringHelper;
13 import com.google.common.collect.ImmutableClassToInstanceMap;
14 import java.util.Map;
15 import java.util.Optional;
16 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
17 import org.eclipse.jdt.annotation.Nullable;
18 import org.opendaylight.mdsal.binding.dom.codec.util.AugmentationReader;
19 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
20 import org.opendaylight.yangtools.yang.binding.Augmentable;
21 import org.opendaylight.yangtools.yang.binding.Augmentation;
22 import org.opendaylight.yangtools.yang.binding.AugmentationHolder;
23 import org.opendaylight.yangtools.yang.binding.DataObject;
24 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
25 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
26
27 /**
28  * A base class for {@link DataObject}s which are also {@link Augmentable}, backed by {@link DataObjectCodecContext}.
29  * While this class is public, it not part of API surface and is an implementation detail. The only reason for it being
30  * public is that it needs to be accessible by code generated at runtime.
31  *
32  * @param <T> DataObject type
33  */
34 public abstract class AugmentableCodecDataObject<T extends DataObject & Augmentable<T>>
35         extends CodecDataObject<T> implements Augmentable<T>, AugmentationHolder<T> {
36     @SuppressWarnings("rawtypes")
37     private static final AtomicReferenceFieldUpdater<AugmentableCodecDataObject, ImmutableClassToInstanceMap>
38             CACHED_AUGMENTATIONS_UPDATER = AtomicReferenceFieldUpdater.newUpdater(AugmentableCodecDataObject.class,
39                 ImmutableClassToInstanceMap.class, "cachedAugmentations");
40     private volatile ImmutableClassToInstanceMap<Augmentation<T>> cachedAugmentations;
41
42     public AugmentableCodecDataObject(final DataObjectCodecContext<T, ?> ctx,
43             final NormalizedNodeContainer<?, ?, ?> data) {
44         super(ctx, data);
45     }
46
47     @Override
48     public final <A extends Augmentation<T>> @Nullable A augmentation(final Class<A> augmentationType) {
49         requireNonNull(augmentationType, "Supplied augmentation must not be null.");
50
51         final ImmutableClassToInstanceMap<Augmentation<T>> aug = cachedAugmentations;
52         if (aug != null) {
53             return aug.getInstance(augmentationType);
54         }
55
56         @SuppressWarnings({"unchecked","rawtypes"})
57         final Optional<DataContainerCodecContext<?, ?>> optAugCtx = context.possibleStreamChild(
58             (Class) augmentationType);
59         if (optAugCtx.isPresent()) {
60             final DataContainerCodecContext<?, ?> augCtx = optAugCtx.get();
61             // Due to binding specification not representing grouping instantiations we can end up having the same
62             // augmentation applied to a grouping multiple times. While these augmentations have the same shape, they
63             // are still represented by distinct binding classes and therefore we need to make sure the result matches
64             // the augmentation the user is requesting -- otherwise a strict receiver would end up with a cryptic
65             // ClassCastException.
66             if (augmentationType.isAssignableFrom(augCtx.getBindingClass())) {
67                 final Optional<NormalizedNode<?, ?>> augData = codecData().getChild(augCtx.getDomPathArgument());
68                 if (augData.isPresent()) {
69                     return (A) augCtx.deserialize(augData.get());
70                 }
71             }
72         }
73         return null;
74     }
75
76     @Override
77     public final Map<Class<? extends Augmentation<T>>, Augmentation<T>> augmentations() {
78         ImmutableClassToInstanceMap<Augmentation<T>> local = cachedAugmentations;
79         if (local != null) {
80             return local;
81         }
82
83         local = ImmutableClassToInstanceMap.copyOf(context.getAllAugmentationsFrom(codecData()));
84         return CACHED_AUGMENTATIONS_UPDATER.compareAndSet(this, null, local) ? local : cachedAugmentations;
85     }
86
87     @Override
88     final int codecAugmentedHashCode() {
89         return 31 * super.codecAugmentedHashCode() + augmentations().hashCode();
90     }
91
92     @Override
93     final boolean codecAugmentedEquals(final T other) {
94         return super.codecAugmentedEquals(other) && augmentations().equals(getAllAugmentations(other));
95     }
96
97     @Override
98     final ToStringHelper codecAugmentedFillToString(final ToStringHelper helper) {
99         return super.codecAugmentedFillToString(helper).add("augmentations", augmentations());
100     }
101
102     private static Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAllAugmentations(
103             final Augmentable<?> dataObject) {
104         if (dataObject instanceof AugmentationReader) {
105             return ((AugmentationReader) dataObject).getAugmentations(dataObject);
106         }
107         return BindingReflections.getAugmentations(dataObject);
108     }
109 }