73b3c577b982bc6135e13ae8303a6cbebcf643ca
[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 org.checkerframework.checker.lock.qual.Holding;
12 import org.eclipse.jdt.annotation.NonNull;
13 import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode.ChildAddressabilitySummary;
14 import org.opendaylight.mdsal.binding.dom.codec.impl.NodeCodecContext.CodecContextFactory;
15 import org.opendaylight.yangtools.yang.binding.DataObject;
16 import org.opendaylight.yangtools.yang.binding.DataRoot;
17 import org.opendaylight.yangtools.yang.binding.Identifiable;
18 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
19 import org.opendaylight.yangtools.yang.common.QNameModule;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
23 import org.opendaylight.yangtools.yang.model.api.AnyDataSchemaNode;
24 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
25 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
30 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
32 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
34 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
35 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 final class DataContainerCodecPrototype<T extends WithStatus> implements NodeContextSupplier {
40     private static final Logger LOG = LoggerFactory.getLogger(DataContainerCodecPrototype.class);
41
42     private final T schema;
43     private final QNameModule namespace;
44     private final CodecContextFactory factory;
45     private final Item<?> bindingArg;
46     private final PathArgument yangArg;
47     private final ChildAddressabilitySummary childAddressabilitySummary;
48
49     /*
50      * FIXME: This field is now utterly in the hot path of CodecDataObject's instantiation of data. It is volatile
51      *        support double-checked locking, as detailed in
52      *        https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java.
53      *
54      *        Removing volatile here would seem to be worthwhile, as it would mean that in the initialized case we end
55      *        up with no happens-before synchronization between threads, allowing JIT to perform better.
56      *
57      *        Since we are on Java 5+, DataContainerCodecContext and its subclasses play a role here. All of their
58      *        fields are either final or volatile, so intuition would indicate they themselves could (already?) be
59      *        acting in the same capacity as "FinalWrapper" in the above example does -- in which case we can safely
60      *        remove the volatile access altogether, after documenting this fact there (and potentially placing some
61      *        guards).
62      *
63      *        The presence of volatile fields seems to violate this, as
64      *        http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html mentions "such that all of the
65      *        fields of Helper are final".
66      *
67      *        There are only two violations, both relating to dynamic discovery of augmentations, which should be
68      *        eliminated once we have constant class loader visibility (and thus can discover all valid augmentations
69      *        and aliases). Alternatively, adding an indirection could be considered after the effects are fully
70      *        understood.
71      *
72      *        Alternatively we can consider the use of VarHandle acquire/release mechanics to remove this instance
73      *        from global synchronization order -- that would be a Java 9+ concern, though.
74      *
75      *        Finally, the benefits of addressing this are masked by CodecDataObject operating on volatiles, hence
76      *        already doing a barrier very close to us -- thus reducing the scope in which code can be reordered.
77      *        If CodecDataObject moves to VarHandles and uses acquire/release there, we will be put on the spot here,
78      *        though.
79      */
80     private volatile DataContainerCodecContext<?, T> instance;
81
82     @SuppressWarnings("unchecked")
83     private DataContainerCodecPrototype(final Class<?> cls, final PathArgument arg, final T nodeSchema,
84             final CodecContextFactory factory) {
85         this(Item.of((Class<? extends DataObject>) cls), arg, nodeSchema, factory);
86     }
87
88     private DataContainerCodecPrototype(final Item<?> bindingArg, final PathArgument arg, final T nodeSchema,
89             final CodecContextFactory factory) {
90         this.bindingArg = bindingArg;
91         this.yangArg = arg;
92         this.schema = nodeSchema;
93         this.factory = factory;
94
95         if (arg instanceof AugmentationIdentifier) {
96             this.namespace = Iterables.getFirst(((AugmentationIdentifier) arg).getPossibleChildNames(), null)
97                     .getModule();
98         } else {
99             this.namespace = arg.getNodeType().getModule();
100         }
101
102         this.childAddressabilitySummary = computeChildAddressabilitySummary(nodeSchema);
103     }
104
105     private static ChildAddressabilitySummary computeChildAddressabilitySummary(final WithStatus nodeSchema) {
106         if (nodeSchema instanceof DataNodeContainer) {
107             boolean haveAddressable = false;
108             boolean haveUnaddressable = false;
109             for (DataSchemaNode child : ((DataNodeContainer) nodeSchema).getChildNodes()) {
110                 if (child instanceof ContainerSchemaNode || child instanceof AugmentationSchemaNode) {
111                     haveAddressable = true;
112                 } else if (child instanceof ListSchemaNode) {
113                     if (((ListSchemaNode) child).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) {
122                     switch (computeChildAddressabilitySummary(child)) {
123                         case ADDRESSABLE:
124                             haveAddressable = true;
125                             break;
126                         case MIXED:
127                             haveAddressable = true;
128                             haveUnaddressable = true;
129                             break;
130                         case UNADDRESSABLE:
131                             haveUnaddressable = true;
132                             break;
133                         default:
134                             throw new IllegalStateException("Unhandled accessibility summary for " + child);
135                     }
136                 } else {
137                     LOG.warn("Unhandled child node {}", child);
138                 }
139             }
140
141             if (!haveAddressable) {
142                 // Empty or all are unaddressable
143                 return ChildAddressabilitySummary.UNADDRESSABLE;
144             }
145
146             return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
147         } else if (nodeSchema instanceof ChoiceSchemaNode) {
148             boolean haveAddressable = false;
149             boolean haveUnaddressable = false;
150             for (CaseSchemaNode child : ((ChoiceSchemaNode) nodeSchema).getCases().values()) {
151                 switch (computeChildAddressabilitySummary(child)) {
152                     case ADDRESSABLE:
153                         haveAddressable = true;
154                         break;
155                     case UNADDRESSABLE:
156                         haveUnaddressable = true;
157                         break;
158                     case MIXED:
159                         // A child is mixed, which means we are mixed, too
160                         return ChildAddressabilitySummary.MIXED;
161                     default:
162                         throw new IllegalStateException("Unhandled accessibility summary for " + child);
163                 }
164             }
165
166             if (!haveAddressable) {
167                 // Empty or all are unaddressable
168                 return ChildAddressabilitySummary.UNADDRESSABLE;
169             }
170
171             return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
172         }
173
174         // No child nodes possible: return unaddressable
175         return ChildAddressabilitySummary.UNADDRESSABLE;
176     }
177
178     static DataContainerCodecPrototype<SchemaContext> rootPrototype(final CodecContextFactory factory) {
179         final SchemaContext schema = factory.getRuntimeContext().getSchemaContext();
180         final NodeIdentifier arg = NodeIdentifier.create(schema.getQName());
181         return new DataContainerCodecPrototype<>(DataRoot.class, arg, schema, factory);
182     }
183
184     @SuppressWarnings({ "unchecked", "rawtypes" })
185     static <T extends DataSchemaNode> DataContainerCodecPrototype<T> from(final Class<?> cls, final T schema,
186             final CodecContextFactory factory) {
187         return new DataContainerCodecPrototype(cls, NodeIdentifier.create(schema.getQName()), schema, factory);
188     }
189
190     @SuppressWarnings({ "unchecked", "rawtypes" })
191     static <T extends DataSchemaNode> DataContainerCodecPrototype<T> from(final Item<?> bindingArg, final T schema,
192             final CodecContextFactory factory) {
193         return new DataContainerCodecPrototype(bindingArg, NodeIdentifier.create(schema.getQName()), schema, factory);
194     }
195
196     @SuppressWarnings({ "rawtypes", "unchecked" })
197     static DataContainerCodecPrototype<?> from(final Class<?> augClass, final AugmentationIdentifier arg,
198             final AugmentationSchemaNode schema, final CodecContextFactory factory) {
199         return new DataContainerCodecPrototype(augClass, arg, schema, factory);
200     }
201
202     static DataContainerCodecPrototype<NotificationDefinition> from(final Class<?> augClass,
203             final NotificationDefinition schema, final CodecContextFactory factory) {
204         final PathArgument arg = NodeIdentifier.create(schema.getQName());
205         return new DataContainerCodecPrototype<>(augClass,arg, schema, factory);
206     }
207
208     protected T getSchema() {
209         return schema;
210     }
211
212     ChildAddressabilitySummary getChildAddressabilitySummary() {
213         return childAddressabilitySummary;
214     }
215
216     protected QNameModule getNamespace() {
217         return namespace;
218     }
219
220     protected CodecContextFactory getFactory() {
221         return factory;
222     }
223
224     protected Class<?> getBindingClass() {
225         return bindingArg.getType();
226     }
227
228     protected Item<?> getBindingArg() {
229         return bindingArg;
230     }
231
232     protected PathArgument getYangArg() {
233         return yangArg;
234     }
235
236     @Override
237     public DataContainerCodecContext<?, T> get() {
238         DataContainerCodecContext<?, T> tmp = instance;
239         if (tmp == null) {
240             synchronized (this) {
241                 tmp = instance;
242                 if (tmp == null) {
243                     instance = tmp = createInstance();
244                 }
245             }
246         }
247
248         return tmp;
249     }
250
251     @Holding("this")
252     @SuppressWarnings({ "rawtypes", "unchecked" })
253     private @NonNull DataContainerCodecContext<?, T> createInstance() {
254         // FIXME: make protected abstract
255         if (schema instanceof ContainerSchemaNode) {
256             return new ContainerNodeCodecContext(this);
257         } else if (schema instanceof ListSchemaNode) {
258             return Identifiable.class.isAssignableFrom(getBindingClass())
259                     ? KeyedListNodeCodecContext.create((DataContainerCodecPrototype<ListSchemaNode>) this)
260                             : new ListNodeCodecContext(this);
261         } else if (schema instanceof ChoiceSchemaNode) {
262             return new ChoiceNodeCodecContext(this);
263         } else if (schema instanceof AugmentationSchemaNode) {
264             return new AugmentationNodeContext(this);
265         } else if (schema instanceof CaseSchemaNode) {
266             return new CaseNodeCodecContext(this);
267         }
268         throw new IllegalArgumentException("Unsupported type " + getBindingClass() + " " + schema);
269     }
270
271     boolean isChoice() {
272         return schema instanceof ChoiceSchemaNode;
273     }
274 }