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