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