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