Allow getChildNodes() to become a Collection
[controller.git] / opendaylight / config / yang-jmx-generator / src / main / java / org / opendaylight / controller / config / yangjmxgenerator / RuntimeBeanEntry.java
1 /*
2  * Copyright (c) 2013 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.controller.config.yangjmxgenerator;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkState;
12
13 import com.google.common.annotations.VisibleForTesting;
14 import com.google.common.base.Optional;
15 import com.google.common.collect.Lists;
16
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.Comparator;
21 import java.util.Deque;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28 import java.util.TreeSet;
29
30 import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc;
31 import org.opendaylight.controller.config.yangjmxgenerator.attribute.JavaAttribute;
32 import org.opendaylight.controller.config.yangjmxgenerator.attribute.ListAttribute;
33 import org.opendaylight.controller.config.yangjmxgenerator.attribute.TOAttribute;
34 import org.opendaylight.controller.config.yangjmxgenerator.attribute.VoidAttribute;
35 import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.FullyQualifiedNameHelper;
36 import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.NameConflictException;
37 import org.opendaylight.yangtools.yang.common.QName;
38 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
40 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.Module;
46 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
47 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.UsesNode;
50
51 /**
52  * Holds information about runtime bean to be generated. There are two kinds of
53  * RuntimeBeanEntry instances: if isRoot flag is set to true, this bean
54  * represents state that must be present at time of configuration module
55  * instantiation. Root RB must have depthLevel set to 0 and cannot have
56  * children. There might be other RBs defined in yang, but no other RB can have
57  * isRoot set to true. At least one RB must be root and all other RBs must be
58  * lined via children so that a tree with all beans can be created.
59  */
60 public class RuntimeBeanEntry {
61     private final String packageName;
62     private final String yangName, javaNamePrefix;
63     private final boolean isRoot;
64     private final Optional<String> keyYangName, keyJavaName;
65     private final Map<String, AttributeIfc> attributeMap;
66     private final List<RuntimeBeanEntry> children;
67     private final Set<Rpc> rpcs;
68
69     @VisibleForTesting
70     RuntimeBeanEntry(final String packageName,
71             final DataNodeContainer nodeForReporting, final String yangName,
72             final String javaNamePrefix, final boolean isRoot,
73             final Optional<String> keyYangName, final List<AttributeIfc> attributes,
74             final List<RuntimeBeanEntry> children, final Set<Rpc> rpcs) {
75
76         checkArgument(isRoot == false || keyYangName.isPresent() == false,
77                 "Root RuntimeBeanEntry must not have key " + "set");
78         this.packageName = packageName;
79         this.isRoot = isRoot;
80         this.yangName = yangName;
81         this.javaNamePrefix = javaNamePrefix;
82         this.children = Collections.unmodifiableList(children);
83         this.rpcs = Collections.unmodifiableSet(rpcs);
84
85         this.keyYangName = keyYangName;
86         Map<String, AttributeIfc> map = new HashMap<>();
87
88         for (AttributeIfc a : attributes) {
89             checkState(map.containsKey(a.getAttributeYangName()) == false,
90                     "Attribute already defined: " + a.getAttributeYangName()
91                     + " in " + nodeForReporting);
92             map.put(a.getAttributeYangName(), a);
93         }
94
95         if (keyYangName.isPresent()) {
96             AttributeIfc keyJavaName = map.get(keyYangName.get());
97             checkArgument(keyJavaName != null, "Key " + keyYangName.get()
98                     + " not found in attribute " + "list " + attributes
99                     + " in " + nodeForReporting);
100             this.keyJavaName = Optional
101                     .of(keyJavaName.getUpperCaseCammelCase());
102         } else {
103             keyJavaName = Optional.absent();
104         }
105         attributeMap = Collections.unmodifiableMap(map);
106     }
107
108     /**
109      * @return map containing all class names as key, extracted RuntimeBeans as
110      *         values. If more than zero values is returned, exactly one
111      *         RuntimeBeanEntry will have isRoot set to true, even if yang does
112      *         not contain special configuration for it.
113      */
114     public static Map<String, RuntimeBeanEntry> extractClassNameToRuntimeBeanMap(
115             final String packageName, final DataNodeContainer container,
116             final String moduleYangName, final TypeProviderWrapper typeProviderWrapper,
117             final String javaNamePrefix, final Module currentModule) {
118
119         Map<QName, Set<RpcDefinition>> identitiesToRpcs = getIdentitiesToRpcs(currentModule);
120
121         AttributesRpcsAndRuntimeBeans attributesRpcsAndRuntimeBeans = extractSubtree(
122                 packageName, container, typeProviderWrapper, currentModule,
123                 identitiesToRpcs);
124         Map<String, RuntimeBeanEntry> result = new HashMap<>();
125
126         List<AttributeIfc> attributes;
127         Set<Rpc> rpcs;
128         if (attributesRpcsAndRuntimeBeans.isEmpty() == false) {
129             attributes = attributesRpcsAndRuntimeBeans.getAttributes();
130             rpcs = attributesRpcsAndRuntimeBeans.getRpcs();
131         } else {
132             // create artificial root if not defined in yang
133             attributes = Collections.emptyList();
134             rpcs = Collections.emptySet();
135         }
136         RuntimeBeanEntry rootRuntimeBeanEntry = createRoot(packageName,
137                 container, moduleYangName, attributes, javaNamePrefix,
138                 attributesRpcsAndRuntimeBeans.getRuntimeBeanEntries(), rpcs);
139
140         Deque<RuntimeBeanEntry> stack = new LinkedList<>();
141         stack.add(rootRuntimeBeanEntry);
142
143         while (stack.isEmpty() == false) {
144             RuntimeBeanEntry first = stack.pollFirst();
145             if (result.containsKey(first.getJavaNameOfRuntimeMXBean())) {
146                 throw new NameConflictException(
147                         first.getJavaNameOfRuntimeMXBean(), null, null);
148             }
149             result.put(first.getJavaNameOfRuntimeMXBean(), first);
150             stack.addAll(first.getChildren());
151         }
152         return result;
153     }
154
155     private static Map<QName/* of identity */, Set<RpcDefinition>> getIdentitiesToRpcs(
156             final Module currentModule) {
157         // currently only looks for local identities (found in currentModule)
158         Map<QName, Set<RpcDefinition>> result = new HashMap<>();
159         for (IdentitySchemaNode identity : currentModule.getIdentities()) {
160             // add all
161             result.put(identity.getQName(), new HashSet<RpcDefinition>());
162         }
163
164         for (RpcDefinition rpc : currentModule.getRpcs()) {
165             ContainerSchemaNode input = rpc.getInput();
166             if (input != null) {
167                 for (UsesNode uses : input.getUses()) {
168
169                     if (uses.getGroupingPath().getPath().size() != 1) {
170                         continue;
171                     }
172
173                     // check grouping path
174                     QName qname = uses.getGroupingPath().getPath().get(0);
175                     if (false == qname
176                             .equals(ConfigConstants.RPC_CONTEXT_REF_GROUPING_QNAME)) {
177                         continue;
178                     }
179
180                     for (SchemaNode refinedNode : uses.getRefines().values()) {
181
182                         for (UnknownSchemaNode unknownSchemaNode : refinedNode
183                                 .getUnknownSchemaNodes()) {
184                             if (ConfigConstants.RPC_CONTEXT_INSTANCE_EXTENSION_QNAME
185                                     .equals(unknownSchemaNode.getNodeType())) {
186                                 String localIdentityName = unknownSchemaNode
187                                         .getNodeParameter();
188                                 QName identityQName = QName.create(
189                                         currentModule.getNamespace(),
190                                         currentModule.getRevision(),
191                                         localIdentityName);
192                                 Set<RpcDefinition> rpcDefinitions = result
193                                         .get(identityQName);
194                                 if (rpcDefinitions == null) {
195                                     throw new IllegalArgumentException(
196                                             "Identity referenced by rpc not found. Identity:"
197                                                     + localIdentityName + " , rpc "
198                                                     + rpc);
199                                 }
200                                 rpcDefinitions.add(rpc);
201                             }
202                         }
203                     }
204                 }
205             }
206         }
207         return result;
208     }
209
210     /**
211      * Get direct descendants of this subtree, together with attributes defined
212      * in subtree.
213      */
214     private static AttributesRpcsAndRuntimeBeans extractSubtree(
215             final String packageName, final DataNodeContainer subtree,
216             final TypeProviderWrapper typeProviderWrapper, final Module currentModule,
217             final Map<QName, Set<RpcDefinition>> identitiesToRpcs) {
218
219         List<AttributeIfc> attributes = Lists.newArrayList();
220         // List<JavaAttribute> javaAttributes = new ArrayList<>();
221         // List<TOAttribute> toAttributes = new ArrayList<>();
222         List<RuntimeBeanEntry> runtimeBeanEntries = new ArrayList<>();
223         for (DataSchemaNode child : subtree.getChildNodes()) {
224             // child leaves can be java attributes, TO attributes, or child
225             // runtime beans
226             if (child instanceof LeafSchemaNode) {
227                 // just save the attribute
228                 LeafSchemaNode leaf = (LeafSchemaNode) child;
229                 attributes.add(new JavaAttribute(leaf, typeProviderWrapper));
230             } else if (child instanceof ContainerSchemaNode) {
231                 ContainerSchemaNode container = (ContainerSchemaNode) child;
232                 // this can be either TO or hierarchical RB
233                 TOAttribute toAttribute = TOAttribute.create(container,
234                         typeProviderWrapper, packageName);
235                 attributes.add(toAttribute);
236             } else if (child instanceof ListSchemaNode) {
237                 if (isInnerStateBean(child)) {
238                     ListSchemaNode listSchemaNode = (ListSchemaNode) child;
239                     RuntimeBeanEntry hierarchicalChild = createHierarchical(
240                             packageName, listSchemaNode, typeProviderWrapper,
241                             currentModule, identitiesToRpcs);
242                     runtimeBeanEntries.add(hierarchicalChild);
243                 } else /* ordinary list attribute */{
244                     ListAttribute listAttribute = ListAttribute.create(
245                             (ListSchemaNode) child, typeProviderWrapper, packageName);
246                     attributes.add(listAttribute);
247                 }
248
249             } else if (child instanceof LeafListSchemaNode) {
250                 ListAttribute listAttribute = ListAttribute.create(
251                         (LeafListSchemaNode) child, typeProviderWrapper);
252                 attributes.add(listAttribute);
253             } else {
254                 throw new IllegalStateException("Unexpected running-data node "
255                         + child);
256             }
257         }
258         Set<Rpc> rpcs = new HashSet<>();
259         SchemaNode subtreeSchemaNode = (SchemaNode) subtree;
260         for (UnknownSchemaNode unknownSchemaNode : subtreeSchemaNode
261                 .getUnknownSchemaNodes()) {
262             if (ConfigConstants.RPC_CONTEXT_INSTANCE_EXTENSION_QNAME
263                     .equals(unknownSchemaNode.getNodeType())) {
264                 String localIdentityName = unknownSchemaNode.getNodeParameter();
265                 QName identityQName = QName.create(currentModule.getNamespace(),
266                         currentModule.getRevision(), localIdentityName);
267                 Set<RpcDefinition> rpcDefinitions = identitiesToRpcs
268                         .get(identityQName);
269                 if (rpcDefinitions == null) {
270                     throw new IllegalArgumentException("Cannot find identity "
271                             + localIdentityName + " to be used as "
272                             + "context reference when resolving "
273                             + unknownSchemaNode);
274                 }
275                 // convert RpcDefinition to Rpc
276                 for (RpcDefinition rpcDefinition : rpcDefinitions) {
277                     String name = TypeProviderWrapper
278                             .findJavaParameter(rpcDefinition);
279                     AttributeIfc returnType;
280                     if (rpcDefinition.getOutput() == null
281                             || rpcDefinition.getOutput().getChildNodes().size() == 0) {
282                         returnType = VoidAttribute.getInstance();
283                     } else if (rpcDefinition.getOutput().getChildNodes().size() == 1) {
284                         DataSchemaNode returnDSN = rpcDefinition.getOutput()
285                                 .getChildNodes().iterator().next();
286                         returnType = getReturnTypeAttribute(returnDSN, typeProviderWrapper, packageName);
287
288                     } else {
289                         throw new IllegalArgumentException(
290                                 "More than one child node in rpc output is not supported. "
291                                         + "Error occured in " + rpcDefinition);
292                     }
293                     List<JavaAttribute> parameters = new ArrayList<>();
294                     for (DataSchemaNode childNode : sortAttributes(rpcDefinition.getInput()
295                             .getChildNodes())) {
296                         if (childNode.isAddedByUses() == false) { // skip
297                             // refined
298                             // context-instance
299                             checkArgument(childNode instanceof LeafSchemaNode, "Unexpected type of rpc input type. "
300                                     + "Currently only leafs and empty output nodes are supported, got " + childNode);
301                             JavaAttribute javaAttribute = new JavaAttribute(
302                                     (LeafSchemaNode) childNode,
303                                     typeProviderWrapper);
304                             parameters.add(javaAttribute);
305                         }
306                     }
307                     Rpc newRpc = new Rpc(returnType, name, rpcDefinition
308                             .getQName().getLocalName(), parameters);
309                     rpcs.add(newRpc);
310                 }
311             }
312         }
313         return new AttributesRpcsAndRuntimeBeans(runtimeBeanEntries,
314                 attributes, rpcs);
315     }
316
317     private static AttributeIfc getReturnTypeAttribute(final DataSchemaNode child, final TypeProviderWrapper typeProviderWrapper,
318             final String packageName) {
319         if (child instanceof LeafSchemaNode) {
320             LeafSchemaNode leaf = (LeafSchemaNode) child;
321             return new JavaAttribute(leaf, typeProviderWrapper);
322         } else if (child instanceof ContainerSchemaNode) {
323             ContainerSchemaNode container = (ContainerSchemaNode) child;
324             TOAttribute toAttribute = TOAttribute.create(container, typeProviderWrapper, packageName);
325             return toAttribute;
326         } else if (child instanceof ListSchemaNode) {
327             return ListAttribute.create((ListSchemaNode) child, typeProviderWrapper, packageName);
328         } else if (child instanceof LeafListSchemaNode) {
329             return ListAttribute.create((LeafListSchemaNode) child, typeProviderWrapper);
330         } else {
331             throw new IllegalStateException("Unknown output data node " + child + " for rpc");
332         }
333     }
334
335     private static Collection<DataSchemaNode> sortAttributes(final Collection<DataSchemaNode> childNodes) {
336         final TreeSet<DataSchemaNode> dataSchemaNodes = new TreeSet<>(new Comparator<DataSchemaNode>() {
337             @Override
338             public int compare(final DataSchemaNode o1, final DataSchemaNode o2) {
339                 return o1.getQName().getLocalName().compareTo(o2.getQName().getLocalName());
340             }
341         });
342         dataSchemaNodes.addAll(childNodes);
343         return dataSchemaNodes;
344     }
345
346     private static boolean isInnerStateBean(final DataSchemaNode child) {
347         for (UnknownSchemaNode unknownSchemaNode : child
348                 .getUnknownSchemaNodes()) {
349             if (unknownSchemaNode.getNodeType().equals(
350                     ConfigConstants.INNER_STATE_BEAN_EXTENSION_QNAME)) {
351                 return true;
352             }
353         }
354         return false;
355     }
356
357     private static RuntimeBeanEntry createHierarchical(final String packageName,
358             final ListSchemaNode listSchemaNode,
359             final TypeProviderWrapper typeProviderWrapper, final Module currentModule,
360             final Map<QName, Set<RpcDefinition>> identitiesToRpcs) {
361
362         // supported are numeric types, strings, enums
363         // get all attributes
364         AttributesRpcsAndRuntimeBeans attributesRpcsAndRuntimeBeans = extractSubtree(
365                 packageName, listSchemaNode, typeProviderWrapper,
366                 currentModule, identitiesToRpcs);
367
368         Optional<String> keyYangName;
369         if (listSchemaNode.getKeyDefinition().size() == 0) {
370             keyYangName = Optional.absent();
371         } else if (listSchemaNode.getKeyDefinition().size() == 1) {
372             // key must be either null or one of supported key types
373             QName keyQName = listSchemaNode.getKeyDefinition().iterator()
374                     .next();
375             keyYangName = Optional.of(keyQName.getLocalName());
376
377         } else {
378             throw new IllegalArgumentException(
379                     "More than one key is not supported in " + listSchemaNode);
380         }
381
382         String javaNamePrefix = TypeProviderWrapper
383                 .findJavaNamePrefix(listSchemaNode);
384
385         RuntimeBeanEntry rbFromAttributes = new RuntimeBeanEntry(packageName,
386                 listSchemaNode, listSchemaNode.getQName().getLocalName(),
387                 javaNamePrefix, false, keyYangName,
388                 attributesRpcsAndRuntimeBeans.getAttributes(),
389                 attributesRpcsAndRuntimeBeans.getRuntimeBeanEntries(),
390                 attributesRpcsAndRuntimeBeans.getRpcs());
391
392         return rbFromAttributes;
393     }
394
395     private static RuntimeBeanEntry createRoot(final String packageName,
396             final DataNodeContainer nodeForReporting, final String attributeYangName,
397             final List<AttributeIfc> attributes, final String javaNamePrefix,
398             final List<RuntimeBeanEntry> children, final Set<Rpc> rpcs) {
399         return new RuntimeBeanEntry(packageName, nodeForReporting,
400                 attributeYangName, javaNamePrefix, true,
401                 Optional.<String> absent(), attributes, children, rpcs);
402     }
403
404     public boolean isRoot() {
405         return isRoot;
406     }
407
408     public Optional<String> getKeyYangName() {
409         return keyYangName;
410     }
411
412     public Optional<String> getKeyJavaName() {
413         return keyJavaName;
414     }
415
416     public Collection<AttributeIfc> getAttributes() {
417         return attributeMap.values();
418     }
419
420     public Map<String, AttributeIfc> getYangPropertiesToTypesMap() {
421         return attributeMap;
422     }
423
424     public String getYangName() {
425         return yangName;
426     }
427
428     public String getPackageName() {
429         return packageName;
430     }
431
432     public String getJavaNamePrefix() {
433         return javaNamePrefix;
434     }
435
436     public List<RuntimeBeanEntry> getChildren() {
437         return children;
438     }
439
440     public Set<Rpc> getRpcs() {
441         return rpcs;
442     }
443
444     private static class AttributesRpcsAndRuntimeBeans {
445         private final List<RuntimeBeanEntry> runtimeBeanEntries;
446         private final List<AttributeIfc> attributes;
447         private final Set<Rpc> rpcs;
448
449         public AttributesRpcsAndRuntimeBeans(
450                 final List<RuntimeBeanEntry> runtimeBeanEntries,
451                 final List<AttributeIfc> attributes, final Set<Rpc> rpcs) {
452             this.runtimeBeanEntries = runtimeBeanEntries;
453             this.attributes = attributes;
454             this.rpcs = rpcs;
455         }
456
457         private List<AttributeIfc> getAttributes() {
458             return attributes;
459         }
460
461         public List<RuntimeBeanEntry> getRuntimeBeanEntries() {
462             return runtimeBeanEntries;
463         }
464
465         public boolean isEmpty() {
466             return attributes.isEmpty() && rpcs.isEmpty();
467         }
468
469         private Set<Rpc> getRpcs() {
470             return rpcs;
471         }
472     }
473
474     public static class Rpc {
475         private final String name;
476         private final List<JavaAttribute> parameters;
477         private final AttributeIfc returnType;
478         private final String yangName;
479
480         Rpc(final AttributeIfc returnType, final String name, final String yangName,
481                 final List<JavaAttribute> parameters) {
482             this.returnType = returnType;
483             this.name = name;
484             this.parameters = parameters;
485             this.yangName = yangName;
486         }
487
488         public String getYangName() {
489             return yangName;
490         }
491
492         public String getName() {
493             return name;
494         }
495
496         public List<JavaAttribute> getParameters() {
497             return parameters;
498         }
499
500         public AttributeIfc getReturnType() {
501             return returnType;
502         }
503     }
504
505     private static final String MXBEAN_SUFFIX = "RuntimeMXBean";
506
507     public String getJavaNameOfRuntimeMXBean() {
508         return getJavaNameOfRuntimeMXBean(javaNamePrefix);
509     }
510
511     public String getFullyQualifiedName(final String typeName) {
512         return FullyQualifiedNameHelper.getFullyQualifiedName(packageName,
513                 typeName);
514     }
515
516     private static String getJavaNameOfRuntimeMXBean(final String javaNamePrefix) {
517         return javaNamePrefix + MXBEAN_SUFFIX;
518     }
519
520     @Override
521     public String toString() {
522         return "RuntimeBeanEntry{" + "isRoot=" + isRoot + ", yangName='"
523                 + yangName + '\'' + ", packageName='" + packageName + '\''
524                 + ", keyYangName=" + keyYangName + '}';
525     }
526 }

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.