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