Split out NotificationCodecContext.Prototype
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / DataContainerCodecPrototype.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.mdsal.binding.dom.codec.impl;
9
10 import static com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
12
13 import java.lang.invoke.MethodHandles;
14 import java.lang.invoke.VarHandle;
15 import org.eclipse.jdt.annotation.NonNull;
16 import org.opendaylight.mdsal.binding.dom.codec.api.CommonDataObjectCodecTreeNode.ChildAddressabilitySummary;
17 import org.opendaylight.mdsal.binding.dom.codec.impl.NodeCodecContext.CodecContextFactory;
18 import org.opendaylight.mdsal.binding.runtime.api.CompositeRuntimeType;
19 import org.opendaylight.mdsal.binding.runtime.api.RuntimeType;
20 import org.opendaylight.mdsal.binding.runtime.api.RuntimeTypeContainer;
21 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
22 import org.opendaylight.yangtools.yang.common.QName;
23 import org.opendaylight.yangtools.yang.common.QNameModule;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
25 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
32 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 abstract sealed class DataContainerCodecPrototype<T extends RuntimeTypeContainer> implements NodeContextSupplier
39         permits AugmentationCodecPrototype, DataObjectCodecPrototype {
40     private static final Logger LOG = LoggerFactory.getLogger(DataContainerCodecPrototype.class);
41
42     private static final VarHandle INSTANCE;
43
44     static {
45         try {
46             INSTANCE = MethodHandles.lookup().findVarHandle(DataContainerCodecPrototype.class,
47                 "instance", DataContainerCodecContext.class);
48         } catch (NoSuchFieldException | IllegalAccessException e) {
49             throw new ExceptionInInitializerError(e);
50         }
51     }
52
53     private final @NonNull T type;
54     private final @NonNull QNameModule namespace;
55     private final @NonNull CodecContextFactory factory;
56     private final @NonNull Item<?> bindingArg;
57     private final @NonNull ChildAddressabilitySummary childAddressabilitySummary;
58
59     // multiple paths represent augmentation wrapper
60     // FIXME: this means it is either this or 'childArgs'
61
62     // Accessed via INSTANCE
63     @SuppressWarnings("unused")
64     private volatile DataContainerCodecContext<?, T> instance;
65
66     DataContainerCodecPrototype(final Item<?> bindingArg, final QNameModule namespace, final T type,
67             final CodecContextFactory factory) {
68         this.bindingArg = requireNonNull(bindingArg);
69         this.namespace = requireNonNull(namespace);
70         this.type = requireNonNull(type);
71         this.factory = requireNonNull(factory);
72
73         childAddressabilitySummary = type instanceof RuntimeType runtimeType
74             ? computeChildAddressabilitySummary(runtimeType.statement())
75                 // BindingRuntimeTypes, does not matter
76                 : ChildAddressabilitySummary.MIXED;
77     }
78
79     private static @NonNull ChildAddressabilitySummary computeChildAddressabilitySummary(final Object nodeSchema) {
80         // FIXME: rework this to work on EffectiveStatements
81         if (nodeSchema instanceof DataNodeContainer contaner) {
82             boolean haveAddressable = false;
83             boolean haveUnaddressable = false;
84             for (DataSchemaNode child : contaner.getChildNodes()) {
85                 if (child instanceof ContainerSchemaNode || child instanceof AugmentationSchemaNode) {
86                     haveAddressable = true;
87                 } else if (child instanceof ListSchemaNode list) {
88                     if (list.getKeyDefinition().isEmpty()) {
89                         haveUnaddressable = true;
90                     } else {
91                         haveAddressable = true;
92                     }
93                 } else if (child instanceof AnydataSchemaNode || child instanceof AnyxmlSchemaNode
94                         || child instanceof TypedDataSchemaNode) {
95                     haveUnaddressable = true;
96                 } else if (child instanceof ChoiceSchemaNode choice) {
97                     switch (computeChildAddressabilitySummary(choice)) {
98                         case ADDRESSABLE -> haveAddressable = true;
99                         case UNADDRESSABLE -> haveUnaddressable = true;
100                         case MIXED -> {
101                             haveAddressable = true;
102                             haveUnaddressable = true;
103                         }
104                         default -> throw new IllegalStateException("Unhandled accessibility summary for " + child);
105                     }
106                 } else {
107                     LOG.warn("Unhandled child node {}", child);
108                 }
109             }
110
111             if (!haveAddressable) {
112                 // Empty or all are unaddressable
113                 return ChildAddressabilitySummary.UNADDRESSABLE;
114             }
115
116             return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
117         } else if (nodeSchema instanceof ChoiceSchemaNode choice) {
118             return computeChildAddressabilitySummary(choice);
119         }
120
121         // No child nodes possible: return unaddressable
122         return ChildAddressabilitySummary.UNADDRESSABLE;
123     }
124
125     private static @NonNull ChildAddressabilitySummary computeChildAddressabilitySummary(
126             final ChoiceSchemaNode choice) {
127         boolean haveAddressable = false;
128         boolean haveUnaddressable = false;
129         for (CaseSchemaNode child : choice.getCases()) {
130             switch (computeChildAddressabilitySummary(child)) {
131                 case ADDRESSABLE:
132                     haveAddressable = true;
133                     break;
134                 case UNADDRESSABLE:
135                     haveUnaddressable = true;
136                     break;
137                 case MIXED:
138                     // A child is mixed, which means we are mixed, too
139                     return ChildAddressabilitySummary.MIXED;
140                 default:
141                     throw new IllegalStateException("Unhandled accessibility summary for " + child);
142             }
143         }
144
145         if (!haveAddressable) {
146             // Empty or all are unaddressable
147             return ChildAddressabilitySummary.UNADDRESSABLE;
148         }
149
150         return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
151     }
152
153     static <T extends CompositeRuntimeType> DataContainerCodecPrototype<T> from(final Class<?> cls, final T type,
154             final CodecContextFactory factory) {
155         return new DataObjectCodecPrototype<>(cls, createIdentifier(type), type, factory);
156     }
157
158     static <T extends CompositeRuntimeType> DataContainerCodecPrototype<T> from(final Item<?> bindingArg, final T type,
159             final CodecContextFactory factory) {
160         return new DataObjectCodecPrototype<>(bindingArg, createIdentifier(type), type, factory);
161     }
162
163     private static @NonNull NodeIdentifier createIdentifier(final CompositeRuntimeType type) {
164         final Object arg = type.statement().argument();
165         verify(arg instanceof QName, "Unexpected type %s argument %s", type, arg);
166         return NodeIdentifier.create((QName) arg);
167     }
168
169     final @NonNull T getType() {
170         return type;
171     }
172
173     final @NonNull ChildAddressabilitySummary getChildAddressabilitySummary() {
174         return childAddressabilitySummary;
175     }
176
177     final @NonNull QNameModule getNamespace() {
178         return namespace;
179     }
180
181     final @NonNull CodecContextFactory getFactory() {
182         return factory;
183     }
184
185     final @NonNull Class<?> getBindingClass() {
186         return bindingArg.getType();
187     }
188
189     final @NonNull Item<?> getBindingArg() {
190         return bindingArg;
191     }
192
193     abstract @NonNull NodeIdentifier getYangArg();
194
195     @Override
196     public final DataContainerCodecContext<?, T> get() {
197         final var existing = (DataContainerCodecContext<?, T>) INSTANCE.getAcquire(this);
198         return existing != null ? existing : loadInstance();
199     }
200
201     @SuppressWarnings("unchecked")
202     final <R extends CompositeRuntimeType> DataObjectCodecContext<?, R> getDataObject() {
203         final var context = get();
204         verify(context instanceof DataObjectCodecContext, "Unexpected instance %s", context);
205         return (DataObjectCodecContext<?, R>) context;
206     }
207
208     private @NonNull DataContainerCodecContext<?, T> loadInstance() {
209         final var tmp = createInstance();
210         final var witness = (DataContainerCodecContext<?, T>) INSTANCE.compareAndExchangeRelease(this, null, tmp);
211         return witness == null ? tmp : witness;
212     }
213
214     // This method must allow concurrent loading, i.e. nothing in it may have effects outside of the loaded object
215     abstract @NonNull DataContainerCodecContext<?, T> createInstance();
216 }