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