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