Update binding-dom adaptation to remove AugmentationNode
[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 com.google.common.collect.ImmutableSet;
14 import java.lang.invoke.MethodHandles;
15 import java.lang.invoke.VarHandle;
16 import org.eclipse.jdt.annotation.NonNull;
17 import org.opendaylight.mdsal.binding.dom.codec.api.CommonDataObjectCodecTreeNode.ChildAddressabilitySummary;
18 import org.opendaylight.mdsal.binding.dom.codec.impl.NodeCodecContext.CodecContextFactory;
19 import org.opendaylight.mdsal.binding.runtime.api.AugmentRuntimeType;
20 import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeTypes;
21 import org.opendaylight.mdsal.binding.runtime.api.CaseRuntimeType;
22 import org.opendaylight.mdsal.binding.runtime.api.ChoiceRuntimeType;
23 import org.opendaylight.mdsal.binding.runtime.api.CompositeRuntimeType;
24 import org.opendaylight.mdsal.binding.runtime.api.ContainerLikeRuntimeType;
25 import org.opendaylight.mdsal.binding.runtime.api.ContainerRuntimeType;
26 import org.opendaylight.mdsal.binding.runtime.api.ListRuntimeType;
27 import org.opendaylight.mdsal.binding.runtime.api.NotificationRuntimeType;
28 import org.opendaylight.mdsal.binding.runtime.api.RuntimeType;
29 import org.opendaylight.mdsal.binding.runtime.api.RuntimeTypeContainer;
30 import org.opendaylight.yangtools.yang.binding.DataObject;
31 import org.opendaylight.yangtools.yang.binding.DataRoot;
32 import org.opendaylight.yangtools.yang.binding.Identifiable;
33 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
34 import org.opendaylight.yangtools.yang.common.QName;
35 import org.opendaylight.yangtools.yang.common.QNameModule;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
38 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
45 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
48 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.stmt.PresenceEffectiveStatement;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 abstract sealed class DataContainerCodecPrototype<T extends RuntimeTypeContainer> implements NodeContextSupplier {
54     static final class Augmentation extends DataContainerCodecPrototype<AugmentRuntimeType> {
55         private final @NonNull ImmutableSet<NodeIdentifier> childArgs;
56
57         @SuppressWarnings("unchecked")
58         Augmentation(final Class<?> cls, final QNameModule namespace, final AugmentRuntimeType type,
59                 final CodecContextFactory factory, final ImmutableSet<NodeIdentifier> childArgs) {
60             super(Item.of((Class<? extends DataObject>) cls), namespace, type, factory);
61             this.childArgs = requireNonNull(childArgs);
62         }
63
64         @Override
65         PathArgument getYangArg() {
66             throw new UnsupportedOperationException("Augmentation does not have PathArgument address");
67         }
68
69         @Override
70         AugmentationNodeContext<?> createInstance() {
71             return new AugmentationNodeContext<>(this);
72         }
73
74         // Guaranteed to be non-empty
75         @NonNull ImmutableSet<NodeIdentifier> getChildArgs() {
76             return childArgs;
77         }
78     }
79
80     static final class Regular<T extends RuntimeTypeContainer> extends DataContainerCodecPrototype<T> {
81         private final @NonNull PathArgument yangArg;
82
83         @SuppressWarnings("unchecked")
84         private Regular(final Class<?> cls, final PathArgument yangArg, final T type,
85                 final CodecContextFactory factory) {
86             this(Item.of((Class<? extends DataObject>) cls), yangArg, type, factory);
87         }
88
89         private Regular(final Item<?> bindingArg, final PathArgument yangArg, final T type,
90                 final CodecContextFactory factory) {
91             super(bindingArg, yangArg.getNodeType().getModule(), type, factory);
92             this.yangArg = requireNonNull(yangArg);
93         }
94
95         @Override
96         PathArgument getYangArg() {
97             return yangArg;
98         }
99
100         @Override
101         @SuppressWarnings({ "rawtypes", "unchecked" })
102         DataContainerCodecContext<?, T> createInstance() {
103             final var type = getType();
104             if (type instanceof ContainerLikeRuntimeType containerLike) {
105                 if (containerLike instanceof ContainerRuntimeType container
106                     && container.statement().findFirstEffectiveSubstatement(PresenceEffectiveStatement.class)
107                         .isEmpty()) {
108                     return new NonPresenceContainerNodeCodecContext(this);
109                 }
110                 return new ContainerNodeCodecContext(this);
111             } else if (type instanceof ListRuntimeType) {
112                 return Identifiable.class.isAssignableFrom(getBindingClass())
113                         ? KeyedListNodeCodecContext.create((DataContainerCodecPrototype<ListRuntimeType>) this)
114                                 : new ListNodeCodecContext(this);
115             } else if (type instanceof ChoiceRuntimeType) {
116                 return new ChoiceNodeCodecContext(this);
117             } else if (type instanceof CaseRuntimeType) {
118                 return new CaseNodeCodecContext(this);
119             }
120             throw new IllegalArgumentException("Unsupported type " + getBindingClass() + " " + type);
121         }
122     }
123
124     private static final Logger LOG = LoggerFactory.getLogger(DataContainerCodecPrototype.class);
125
126     private static final VarHandle INSTANCE;
127
128     static {
129         try {
130             INSTANCE = MethodHandles.lookup().findVarHandle(DataContainerCodecPrototype.class,
131                 "instance", DataContainerCodecContext.class);
132         } catch (NoSuchFieldException | IllegalAccessException e) {
133             throw new ExceptionInInitializerError(e);
134         }
135     }
136
137     private final @NonNull T type;
138     private final @NonNull QNameModule namespace;
139     private final @NonNull CodecContextFactory factory;
140     private final @NonNull Item<?> bindingArg;
141     private final @NonNull ChildAddressabilitySummary childAddressabilitySummary;
142
143     // multiple paths represent augmentation wrapper
144     // FIXME: this means it is either this or 'childArgs'
145
146     // Accessed via INSTANCE
147     @SuppressWarnings("unused")
148     private volatile DataContainerCodecContext<?, T> instance;
149
150     private DataContainerCodecPrototype(final Item<?> bindingArg, final QNameModule namespace, final T type,
151             final CodecContextFactory factory) {
152         this.bindingArg = requireNonNull(bindingArg);
153         this.namespace = requireNonNull(namespace);
154         this.type = requireNonNull(type);
155         this.factory = requireNonNull(factory);
156
157         childAddressabilitySummary = type instanceof RuntimeType runtimeType
158             ? computeChildAddressabilitySummary(runtimeType.statement())
159                 // BindingRuntimeTypes, does not matter
160                 : ChildAddressabilitySummary.MIXED;
161     }
162
163     private static @NonNull ChildAddressabilitySummary computeChildAddressabilitySummary(final Object nodeSchema) {
164         // FIXME: rework this to work on EffectiveStatements
165         if (nodeSchema instanceof DataNodeContainer contaner) {
166             boolean haveAddressable = false;
167             boolean haveUnaddressable = false;
168             for (DataSchemaNode child : contaner.getChildNodes()) {
169                 if (child instanceof ContainerSchemaNode || child instanceof AugmentationSchemaNode) {
170                     haveAddressable = true;
171                 } else if (child instanceof ListSchemaNode list) {
172                     if (list.getKeyDefinition().isEmpty()) {
173                         haveUnaddressable = true;
174                     } else {
175                         haveAddressable = true;
176                     }
177                 } else if (child instanceof AnydataSchemaNode || child instanceof AnyxmlSchemaNode
178                         || child instanceof TypedDataSchemaNode) {
179                     haveUnaddressable = true;
180                 } else if (child instanceof ChoiceSchemaNode choice) {
181                     switch (computeChildAddressabilitySummary(choice)) {
182                         case ADDRESSABLE -> haveAddressable = true;
183                         case UNADDRESSABLE -> haveUnaddressable = true;
184                         case MIXED -> {
185                             haveAddressable = true;
186                             haveUnaddressable = true;
187                         }
188                         default -> throw new IllegalStateException("Unhandled accessibility summary for " + child);
189                     }
190                 } else {
191                     LOG.warn("Unhandled child node {}", child);
192                 }
193             }
194
195             if (!haveAddressable) {
196                 // Empty or all are unaddressable
197                 return ChildAddressabilitySummary.UNADDRESSABLE;
198             }
199
200             return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
201         } else if (nodeSchema instanceof ChoiceSchemaNode choice) {
202             return computeChildAddressabilitySummary(choice);
203         }
204
205         // No child nodes possible: return unaddressable
206         return ChildAddressabilitySummary.UNADDRESSABLE;
207     }
208
209     private static @NonNull ChildAddressabilitySummary computeChildAddressabilitySummary(
210             final ChoiceSchemaNode choice) {
211         boolean haveAddressable = false;
212         boolean haveUnaddressable = false;
213         for (CaseSchemaNode child : choice.getCases()) {
214             switch (computeChildAddressabilitySummary(child)) {
215                 case ADDRESSABLE:
216                     haveAddressable = true;
217                     break;
218                 case UNADDRESSABLE:
219                     haveUnaddressable = true;
220                     break;
221                 case MIXED:
222                     // A child is mixed, which means we are mixed, too
223                     return ChildAddressabilitySummary.MIXED;
224                 default:
225                     throw new IllegalStateException("Unhandled accessibility summary for " + child);
226             }
227         }
228
229         if (!haveAddressable) {
230             // Empty or all are unaddressable
231             return ChildAddressabilitySummary.UNADDRESSABLE;
232         }
233
234         return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
235     }
236
237     static DataContainerCodecPrototype<BindingRuntimeTypes> rootPrototype(final CodecContextFactory factory) {
238         return new Regular<>(DataRoot.class, NodeIdentifier.create(SchemaContext.NAME),
239             factory.getRuntimeContext().getTypes(), factory);
240     }
241
242     static <T extends CompositeRuntimeType> DataContainerCodecPrototype<T> from(final Class<?> cls, final T type,
243             final CodecContextFactory factory) {
244         return new Regular<>(cls, createIdentifier(type), type, factory);
245     }
246
247     static <T extends CompositeRuntimeType> DataContainerCodecPrototype<T> from(final Item<?> bindingArg, final T type,
248             final CodecContextFactory factory) {
249         return new Regular<>(bindingArg, createIdentifier(type), type, factory);
250     }
251
252     static DataContainerCodecPrototype<NotificationRuntimeType> from(final Class<?> augClass,
253             final NotificationRuntimeType schema, final CodecContextFactory factory) {
254         return new Regular<>(augClass, NodeIdentifier.create(schema.statement().argument()), schema, factory);
255     }
256
257     private static @NonNull NodeIdentifier createIdentifier(final CompositeRuntimeType type) {
258         final Object arg = type.statement().argument();
259         verify(arg instanceof QName, "Unexpected type %s argument %s", type, arg);
260         return NodeIdentifier.create((QName) arg);
261     }
262
263     final @NonNull T getType() {
264         return type;
265     }
266
267     final @NonNull ChildAddressabilitySummary getChildAddressabilitySummary() {
268         return childAddressabilitySummary;
269     }
270
271     final @NonNull QNameModule getNamespace() {
272         return namespace;
273     }
274
275     final @NonNull CodecContextFactory getFactory() {
276         return factory;
277     }
278
279     final @NonNull Class<?> getBindingClass() {
280         return bindingArg.getType();
281     }
282
283     final @NonNull Item<?> getBindingArg() {
284         return bindingArg;
285     }
286
287     abstract @NonNull PathArgument getYangArg();
288
289     @Override
290     public final DataContainerCodecContext<?, T> get() {
291         final var existing = (DataContainerCodecContext<?, T>) INSTANCE.getAcquire(this);
292         return existing != null ? existing : loadInstance();
293     }
294
295     @SuppressWarnings("unchecked")
296     final <R extends CompositeRuntimeType> DataObjectCodecContext<?, R> getDataObject() {
297         final var context = get();
298         verify(context instanceof DataObjectCodecContext, "Unexpected instance %s", context);
299         return (DataObjectCodecContext<?, R>) context;
300     }
301
302     private @NonNull DataContainerCodecContext<?, T> loadInstance() {
303         final var tmp = createInstance();
304         final var witness = (DataContainerCodecContext<?, T>) INSTANCE.compareAndExchangeRelease(this, null, tmp);
305         return witness == null ? tmp : witness;
306     }
307
308     @SuppressWarnings({ "rawtypes", "unchecked" })
309     // This method must allow concurrent loading, i.e. nothing in it may have effects outside of the loaded object
310     abstract @NonNull DataContainerCodecContext<?, T> createInstance();
311 }