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