Reduce use of getSchemaContext
[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 com.google.common.collect.Iterables;
11 import java.lang.invoke.MethodHandles;
12 import java.lang.invoke.VarHandle;
13 import org.checkerframework.checker.lock.qual.Holding;
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.yangtools.yang.binding.DataObject;
18 import org.opendaylight.yangtools.yang.binding.DataRoot;
19 import org.opendaylight.yangtools.yang.binding.Identifiable;
20 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
21 import org.opendaylight.yangtools.yang.common.QNameModule;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
25 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
32 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
34 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
36 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
37 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 final class DataContainerCodecPrototype<T extends WithStatus> implements NodeContextSupplier {
42     private static final Logger LOG = LoggerFactory.getLogger(DataContainerCodecPrototype.class);
43
44     private static final VarHandle INSTANCE;
45
46     static {
47         try {
48             INSTANCE = MethodHandles.lookup().findVarHandle(DataContainerCodecPrototype.class,
49                 "instance", DataContainerCodecContext.class);
50         } catch (NoSuchFieldException | IllegalAccessException e) {
51             throw new ExceptionInInitializerError(e);
52         }
53     }
54
55     private final T schema;
56     private final QNameModule namespace;
57     private final CodecContextFactory factory;
58     private final Item<?> bindingArg;
59     private final PathArgument yangArg;
60     private final ChildAddressabilitySummary childAddressabilitySummary;
61
62     /*
63      * This field is now utterly in the hot path of CodecDataObject's instantiation of data. It is a volatile support
64      * double-checked locking, as detailed in https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
65      *
66      * We are opting for the document Java 9 equivalent, forsaking our usual CAS-based approach, where we'd re-assert
67      * concurrent loads. This improves safety a bit (by forcing a synchronized() block), as we expect prototypes to be
68      * long-lived.
69      *
70      * In terms of safe publication, we have two volatile fields in DataObjectCodecContext to worry about, so given
71      * above access trade-off, we opt for the additional safety.
72      *
73      * TODO: all we need is safe publish semantics here, we can most probably tolerate concurrent value loads -- and
74      *       everything except the the above volatiles seems to be ready. Those volatile should disappear once we have
75      *       constant class loader visibility (and thus can discover all valid augmentations and aliases). That would
76      *       mean dropping @Holding from createInstance().
77      */
78     @SuppressWarnings("unused")
79     private volatile DataContainerCodecContext<?, T> instance;
80
81     @SuppressWarnings("unchecked")
82     private DataContainerCodecPrototype(final Class<?> cls, final PathArgument arg, final T nodeSchema,
83             final CodecContextFactory factory) {
84         this(Item.of((Class<? extends DataObject>) cls), arg, nodeSchema, factory);
85     }
86
87     private DataContainerCodecPrototype(final Item<?> bindingArg, final PathArgument arg, final T nodeSchema,
88             final CodecContextFactory factory) {
89         this.bindingArg = bindingArg;
90         this.yangArg = arg;
91         this.schema = nodeSchema;
92         this.factory = factory;
93
94         if (arg instanceof AugmentationIdentifier) {
95             this.namespace = Iterables.getFirst(((AugmentationIdentifier) arg).getPossibleChildNames(), null)
96                     .getModule();
97         } else {
98             this.namespace = arg.getNodeType().getModule();
99         }
100
101         this.childAddressabilitySummary = computeChildAddressabilitySummary(nodeSchema);
102     }
103
104     private static ChildAddressabilitySummary computeChildAddressabilitySummary(final WithStatus nodeSchema) {
105         if (nodeSchema instanceof DataNodeContainer) {
106             boolean haveAddressable = false;
107             boolean haveUnaddressable = false;
108             for (DataSchemaNode child : ((DataNodeContainer) nodeSchema).getChildNodes()) {
109                 if (child instanceof ContainerSchemaNode || child instanceof AugmentationSchemaNode) {
110                     haveAddressable = true;
111                 } else if (child instanceof ListSchemaNode) {
112                     if (((ListSchemaNode) child).getKeyDefinition().isEmpty()) {
113                         haveUnaddressable = true;
114                     } else {
115                         haveAddressable = true;
116                     }
117                 } else if (child instanceof AnydataSchemaNode || child instanceof AnyxmlSchemaNode
118                         || child instanceof TypedDataSchemaNode) {
119                     haveUnaddressable = true;
120                 } else if (child instanceof ChoiceSchemaNode) {
121                     switch (computeChildAddressabilitySummary(child)) {
122                         case ADDRESSABLE:
123                             haveAddressable = true;
124                             break;
125                         case MIXED:
126                             haveAddressable = true;
127                             haveUnaddressable = true;
128                             break;
129                         case UNADDRESSABLE:
130                             haveUnaddressable = true;
131                             break;
132                         default:
133                             throw new IllegalStateException("Unhandled accessibility summary for " + child);
134                     }
135                 } else {
136                     LOG.warn("Unhandled child node {}", child);
137                 }
138             }
139
140             if (!haveAddressable) {
141                 // Empty or all are unaddressable
142                 return ChildAddressabilitySummary.UNADDRESSABLE;
143             }
144
145             return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
146         } else if (nodeSchema instanceof ChoiceSchemaNode) {
147             boolean haveAddressable = false;
148             boolean haveUnaddressable = false;
149             for (CaseSchemaNode child : ((ChoiceSchemaNode) nodeSchema).getCases()) {
150                 switch (computeChildAddressabilitySummary(child)) {
151                     case ADDRESSABLE:
152                         haveAddressable = true;
153                         break;
154                     case UNADDRESSABLE:
155                         haveUnaddressable = true;
156                         break;
157                     case MIXED:
158                         // A child is mixed, which means we are mixed, too
159                         return ChildAddressabilitySummary.MIXED;
160                     default:
161                         throw new IllegalStateException("Unhandled accessibility summary for " + child);
162                 }
163             }
164
165             if (!haveAddressable) {
166                 // Empty or all are unaddressable
167                 return ChildAddressabilitySummary.UNADDRESSABLE;
168             }
169
170             return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
171         }
172
173         // No child nodes possible: return unaddressable
174         return ChildAddressabilitySummary.UNADDRESSABLE;
175     }
176
177     static DataContainerCodecPrototype<SchemaContext> rootPrototype(final CodecContextFactory factory) {
178         final SchemaContext schema = factory.getRuntimeContext().getEffectiveModelContext();
179         final NodeIdentifier arg = NodeIdentifier.create(schema.getQName());
180         return new DataContainerCodecPrototype<>(DataRoot.class, arg, schema, factory);
181     }
182
183     @SuppressWarnings({ "unchecked", "rawtypes" })
184     static <T extends DataSchemaNode> DataContainerCodecPrototype<T> from(final Class<?> cls, final T schema,
185             final CodecContextFactory factory) {
186         return new DataContainerCodecPrototype(cls, NodeIdentifier.create(schema.getQName()), schema, factory);
187     }
188
189     @SuppressWarnings({ "unchecked", "rawtypes" })
190     static <T extends DataSchemaNode> DataContainerCodecPrototype<T> from(final Item<?> bindingArg, final T schema,
191             final CodecContextFactory factory) {
192         return new DataContainerCodecPrototype(bindingArg, NodeIdentifier.create(schema.getQName()), schema, factory);
193     }
194
195     @SuppressWarnings({ "rawtypes", "unchecked" })
196     static DataContainerCodecPrototype<?> from(final Class<?> augClass, final AugmentationIdentifier arg,
197             final AugmentationSchemaNode schema, final CodecContextFactory factory) {
198         return new DataContainerCodecPrototype(augClass, arg, schema, factory);
199     }
200
201     static DataContainerCodecPrototype<NotificationDefinition> from(final Class<?> augClass,
202             final NotificationDefinition schema, final CodecContextFactory factory) {
203         final PathArgument arg = NodeIdentifier.create(schema.getQName());
204         return new DataContainerCodecPrototype<>(augClass,arg, schema, factory);
205     }
206
207     protected T getSchema() {
208         return schema;
209     }
210
211     ChildAddressabilitySummary getChildAddressabilitySummary() {
212         return childAddressabilitySummary;
213     }
214
215     protected QNameModule getNamespace() {
216         return namespace;
217     }
218
219     protected CodecContextFactory getFactory() {
220         return factory;
221     }
222
223     protected Class<?> getBindingClass() {
224         return bindingArg.getType();
225     }
226
227     protected Item<?> getBindingArg() {
228         return bindingArg;
229     }
230
231     protected PathArgument getYangArg() {
232         return yangArg;
233     }
234
235     @Override
236     public DataContainerCodecContext<?, T> get() {
237         final DataContainerCodecContext<?, T> existing = getInstance();
238         return existing != null ? existing : loadInstance();
239     }
240
241     private synchronized @NonNull DataContainerCodecContext<?, T> loadInstance() {
242         // re-acquire under lock
243         DataContainerCodecContext<?, T> tmp = getInstance();
244         if (tmp == null) {
245             tmp = createInstance();
246             INSTANCE.setRelease(this, tmp);
247         }
248         return tmp;
249     }
250
251     // Helper for getAcquire access to instance
252     private DataContainerCodecContext<?, T> getInstance() {
253         return (DataContainerCodecContext<?, T>) INSTANCE.getAcquire(this);
254     }
255
256     @Holding("this")
257     @SuppressWarnings({ "rawtypes", "unchecked" })
258     private @NonNull DataContainerCodecContext<?, T> createInstance() {
259         // FIXME: make protected abstract
260         if (schema instanceof ContainerSchemaNode) {
261             return new ContainerNodeCodecContext(this);
262         } else if (schema instanceof ListSchemaNode) {
263             return Identifiable.class.isAssignableFrom(getBindingClass())
264                     ? KeyedListNodeCodecContext.create((DataContainerCodecPrototype<ListSchemaNode>) this)
265                             : new ListNodeCodecContext(this);
266         } else if (schema instanceof ChoiceSchemaNode) {
267             return new ChoiceNodeCodecContext(this);
268         } else if (schema instanceof AugmentationSchemaNode) {
269             return new AugmentationNodeContext(this);
270         } else if (schema instanceof CaseSchemaNode) {
271             return new CaseNodeCodecContext(this);
272         }
273         throw new IllegalArgumentException("Unsupported type " + getBindingClass() + " " + schema);
274     }
275
276     boolean isChoice() {
277         return schema instanceof ChoiceSchemaNode;
278     }
279 }