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