Fix star import and enable checkstyle rule to prevent it.
[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.AttributeIfc;
14 import org.opendaylight.controller.config.yangjmxgenerator.attribute.JavaAttribute;
15 import org.opendaylight.controller.config.yangjmxgenerator.attribute.ListAttribute;
16 import org.opendaylight.controller.config.yangjmxgenerator.attribute.TOAttribute;
17 import org.opendaylight.controller.config.yangjmxgenerator.attribute.VoidAttribute;
18 import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.FullyQualifiedNameHelper;
19 import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.NameConflictException;
20 import org.opendaylight.yangtools.yang.common.QName;
21 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
22 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
23 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
24 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
25 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.Module;
30 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
31 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.UsesNode;
34
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.Collections;
38 import java.util.Comparator;
39 import java.util.Deque;
40 import java.util.HashMap;
41 import java.util.HashSet;
42 import java.util.LinkedList;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Set;
46 import java.util.TreeSet;
47
48 import static com.google.common.base.Preconditions.checkArgument;
49 import static com.google.common.base.Preconditions.checkState;
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     public RuntimeBeanEntry(String packageName,
71             DataSchemaNode nodeForReporting, String yangName,
72             String javaNamePrefix, boolean isRoot,
73             Optional<String> keyYangName, List<AttributeIfc> attributes,
74             List<RuntimeBeanEntry> children, 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             String packageName, ChoiceCaseNode container,
116             String moduleYangName, TypeProviderWrapper typeProviderWrapper,
117             String javaNamePrefix, 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             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             for (UsesNode uses : input.getUses()) {
167
168                 if (uses.getGroupingPath().getPath().size() != 1)
169                     continue;
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                 for (SchemaNode refinedNode : uses.getRefines().values()) {
178
179                     for (UnknownSchemaNode unknownSchemaNode : refinedNode
180                             .getUnknownSchemaNodes()) {
181                         if (ConfigConstants.RPC_CONTEXT_INSTANCE_EXTENSION_QNAME
182                                 .equals(unknownSchemaNode.getNodeType())) {
183                             String localIdentityName = unknownSchemaNode
184                                     .getNodeParameter();
185                             QName identityQName = new QName(
186                                     currentModule.getNamespace(),
187                                     currentModule.getRevision(),
188                                     localIdentityName);
189                             Set<RpcDefinition> rpcDefinitions = result
190                                     .get(identityQName);
191                             if (rpcDefinitions == null) {
192                                 throw new IllegalArgumentException(
193                                         "Identity referenced by rpc not found. Identity:"
194                                                 + localIdentityName + " , rpc "
195                                                 + rpc);
196                             }
197                             rpcDefinitions.add(rpc);
198                         }
199                     }
200
201                 }
202             }
203         }
204         return result;
205     }
206
207     /**
208      * Get direct descendants of this subtree, together with attributes defined
209      * in subtree.
210      */
211     private static AttributesRpcsAndRuntimeBeans extractSubtree(
212             String packageName, DataNodeContainer subtree,
213             TypeProviderWrapper typeProviderWrapper, Module currentModule,
214             Map<QName, Set<RpcDefinition>> identitiesToRpcs) {
215
216         List<AttributeIfc> attributes = Lists.newArrayList();
217         // List<JavaAttribute> javaAttributes = new ArrayList<>();
218         // List<TOAttribute> toAttributes = new ArrayList<>();
219         List<RuntimeBeanEntry> runtimeBeanEntries = new ArrayList<>();
220         for (DataSchemaNode child : subtree.getChildNodes()) {
221             // child leaves can be java attributes, TO attributes, or child
222             // runtime beans
223             if (child instanceof LeafSchemaNode) {
224                 // just save the attribute
225                 LeafSchemaNode leaf = (LeafSchemaNode) child;
226                 attributes.add(new JavaAttribute(leaf, typeProviderWrapper));
227             } else if (child instanceof ContainerSchemaNode) {
228                 ContainerSchemaNode container = (ContainerSchemaNode) child;
229                 // this can be either TO or hierarchical RB
230                 TOAttribute toAttribute = TOAttribute.create(container,
231                         typeProviderWrapper);
232                 attributes.add(toAttribute);
233             } else if (child instanceof ListSchemaNode) {
234                 if (isInnerStateBean(child)) {
235                     ListSchemaNode listSchemaNode = (ListSchemaNode) child;
236                     RuntimeBeanEntry hierarchicalChild = createHierarchical(
237                             packageName, listSchemaNode, typeProviderWrapper,
238                             currentModule, identitiesToRpcs);
239                     runtimeBeanEntries.add(hierarchicalChild);
240                 } else /* ordinary list attribute */{
241                     ListAttribute listAttribute = ListAttribute.create(
242                             (ListSchemaNode) child, typeProviderWrapper);
243                     attributes.add(listAttribute);
244                 }
245
246             } else if (child instanceof LeafListSchemaNode) {
247                 ListAttribute listAttribute = ListAttribute.create(
248                         (LeafListSchemaNode) child, typeProviderWrapper);
249                 attributes.add(listAttribute);
250             } else {
251                 throw new IllegalStateException("Unexpected running-data node "
252                         + child);
253             }
254         }
255         Set<Rpc> rpcs = new HashSet<>();
256         SchemaNode subtreeSchemaNode = (SchemaNode) subtree;
257         for (UnknownSchemaNode unknownSchemaNode : subtreeSchemaNode
258                 .getUnknownSchemaNodes()) {
259             if (ConfigConstants.RPC_CONTEXT_INSTANCE_EXTENSION_QNAME
260                     .equals(unknownSchemaNode.getNodeType())) {
261                 String localIdentityName = unknownSchemaNode.getNodeParameter();
262                 QName identityQName = new QName(currentModule.getNamespace(),
263                         currentModule.getRevision(), localIdentityName);
264                 Set<RpcDefinition> rpcDefinitions = identitiesToRpcs
265                         .get(identityQName);
266                 if (rpcDefinitions == null) {
267                     throw new IllegalArgumentException("Cannot find identity "
268                             + localIdentityName + " to be used as "
269                             + "context reference when resolving "
270                             + unknownSchemaNode);
271                 }
272                 // convert RpcDefinition to Rpc
273                 for (RpcDefinition rpcDefinition : rpcDefinitions) {
274                     String name = ModuleMXBeanEntry
275                             .findJavaParameter(rpcDefinition);
276                     AttributeIfc returnType;
277                     if (rpcDefinition.getOutput() == null
278                             || rpcDefinition.getOutput().getChildNodes().size() == 0) {
279                         returnType = VoidAttribute.getInstance();
280                     } else if (rpcDefinition.getOutput().getChildNodes().size() == 1) {
281                         DataSchemaNode returnDSN = rpcDefinition.getOutput()
282                                 .getChildNodes().iterator().next();
283                         returnType = getReturnTypeAttribute(returnDSN, typeProviderWrapper);
284
285                     } else {
286                         throw new IllegalArgumentException(
287                                 "More than one child node in rpc output is not supported. "
288                                         + "Error occured in " + rpcDefinition);
289                     }
290                     List<JavaAttribute> parameters = new ArrayList<>();
291                     for (DataSchemaNode childNode : sortAttributes(rpcDefinition.getInput()
292                             .getChildNodes())) {
293                         if (childNode.isAddedByUses() == false) { // skip
294                                                                   // refined
295                                                                   // context-instance
296                             checkArgument(childNode instanceof LeafSchemaNode, "Unexpected type of rpc input type. "
297                                     + "Currently only leafs and empty output nodes are supported, got " + childNode);
298                             JavaAttribute javaAttribute = new JavaAttribute(
299                                     (LeafSchemaNode) childNode,
300                                     typeProviderWrapper);
301                             parameters.add(javaAttribute);
302                         }
303                     }
304                     Rpc newRpc = new Rpc(returnType, name, rpcDefinition
305                             .getQName().getLocalName(), parameters);
306                     rpcs.add(newRpc);
307                 }
308             }
309         }
310         return new AttributesRpcsAndRuntimeBeans(runtimeBeanEntries,
311                 attributes, rpcs);
312     }
313
314     private static AttributeIfc getReturnTypeAttribute(DataSchemaNode child, TypeProviderWrapper typeProviderWrapper) {
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);
321             return toAttribute;
322         } else if (child instanceof ListSchemaNode) {
323             return ListAttribute.create((ListSchemaNode) child, typeProviderWrapper);
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(Set<DataSchemaNode> childNodes) {
332         final TreeSet<DataSchemaNode> dataSchemaNodes = new TreeSet<>(new Comparator<DataSchemaNode>() {
333             @Override
334             public int compare(DataSchemaNode o1, 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(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         return false;
350     }
351
352     private static RuntimeBeanEntry createHierarchical(String packageName,
353             ListSchemaNode listSchemaNode,
354             TypeProviderWrapper typeProviderWrapper, Module currentModule,
355             Map<QName, Set<RpcDefinition>> identitiesToRpcs) {
356
357         // supported are numeric types, strings, enums
358         // get all attributes
359         AttributesRpcsAndRuntimeBeans attributesRpcsAndRuntimeBeans = extractSubtree(
360                 packageName, listSchemaNode, typeProviderWrapper,
361                 currentModule, identitiesToRpcs);
362
363         Optional<String> keyYangName;
364         if (listSchemaNode.getKeyDefinition().size() == 0) {
365             keyYangName = Optional.absent();
366         } else if (listSchemaNode.getKeyDefinition().size() == 1) {
367             // key must be either null or one of supported key types
368             QName keyQName = listSchemaNode.getKeyDefinition().iterator()
369                     .next();
370             keyYangName = Optional.of(keyQName.getLocalName());
371
372         } else {
373             throw new IllegalArgumentException(
374                     "More than one key is not supported in " + listSchemaNode);
375         }
376
377         String javaNamePrefix = ModuleMXBeanEntry
378                 .findJavaNamePrefix(listSchemaNode);
379
380         RuntimeBeanEntry rbFromAttributes = new RuntimeBeanEntry(packageName,
381                 listSchemaNode, listSchemaNode.getQName().getLocalName(),
382                 javaNamePrefix, false, keyYangName,
383                 attributesRpcsAndRuntimeBeans.getAttributes(),
384                 attributesRpcsAndRuntimeBeans.getRuntimeBeanEntries(),
385                 attributesRpcsAndRuntimeBeans.getRpcs());
386
387         return rbFromAttributes;
388     }
389
390     private static RuntimeBeanEntry createRoot(String packageName,
391             DataSchemaNode nodeForReporting, String attributeYangName,
392             List<AttributeIfc> attributes, String javaNamePrefix,
393             List<RuntimeBeanEntry> children, Set<Rpc> rpcs) {
394         return new RuntimeBeanEntry(packageName, nodeForReporting,
395                 attributeYangName, javaNamePrefix, true,
396                 Optional.<String> absent(), attributes, children, rpcs);
397     }
398
399     public boolean isRoot() {
400         return isRoot;
401     }
402
403     public Optional<String> getKeyYangName() {
404         return keyYangName;
405     }
406
407     public Optional<String> getKeyJavaName() {
408         return keyJavaName;
409     }
410
411     public Collection<AttributeIfc> getAttributes() {
412         return attributeMap.values();
413     }
414
415     public Map<String, AttributeIfc> getYangPropertiesToTypesMap() {
416         return attributeMap;
417     }
418
419     public String getYangName() {
420         return yangName;
421     }
422
423     public String getPackageName() {
424         return packageName;
425     }
426
427     public String getJavaNamePrefix() {
428         return javaNamePrefix;
429     }
430
431     public List<RuntimeBeanEntry> getChildren() {
432         return children;
433     }
434
435     public Set<Rpc> getRpcs() {
436         return rpcs;
437     }
438
439     private static class AttributesRpcsAndRuntimeBeans {
440         private final List<RuntimeBeanEntry> runtimeBeanEntries;
441         private final List<AttributeIfc> attributes;
442         private final Set<Rpc> rpcs;
443
444         public AttributesRpcsAndRuntimeBeans(
445                 List<RuntimeBeanEntry> runtimeBeanEntries,
446                 List<AttributeIfc> attributes, Set<Rpc> rpcs) {
447             this.runtimeBeanEntries = runtimeBeanEntries;
448             this.attributes = attributes;
449             this.rpcs = rpcs;
450         }
451
452         private List<AttributeIfc> getAttributes() {
453             return attributes;
454         }
455
456         public List<RuntimeBeanEntry> getRuntimeBeanEntries() {
457             return runtimeBeanEntries;
458         }
459
460         public boolean isEmpty() {
461             return attributes.isEmpty() && rpcs.isEmpty();
462         }
463
464         private Set<Rpc> getRpcs() {
465             return rpcs;
466         }
467     }
468
469     public static class Rpc {
470         private final String name;
471         private final List<JavaAttribute> parameters;
472         private final AttributeIfc returnType;
473         private final String yangName;
474
475         Rpc(AttributeIfc returnType, String name, String yangName,
476                 List<JavaAttribute> parameters) {
477             this.returnType = returnType;
478             this.name = name;
479             this.parameters = parameters;
480             this.yangName = yangName;
481         }
482
483         public String getYangName() {
484             return yangName;
485         }
486
487         public String getName() {
488             return name;
489         }
490
491         public List<JavaAttribute> getParameters() {
492             return parameters;
493         }
494
495         public AttributeIfc getReturnType() {
496             return returnType;
497         }
498     }
499
500     private static final String MXBEAN_SUFFIX = "RuntimeMXBean";
501
502     public String getJavaNameOfRuntimeMXBean() {
503         return getJavaNameOfRuntimeMXBean(javaNamePrefix);
504     }
505
506     public String getFullyQualifiedName(String typeName) {
507         return FullyQualifiedNameHelper.getFullyQualifiedName(packageName,
508                 typeName);
509     }
510
511     private static String getJavaNameOfRuntimeMXBean(String javaNamePrefix) {
512         return javaNamePrefix + MXBEAN_SUFFIX;
513     }
514
515     @Override
516     public String toString() {
517         return "RuntimeBeanEntry{" + "isRoot=" + isRoot + ", yangName='"
518                 + yangName + '\'' + ", packageName='" + packageName + '\''
519                 + ", keyYangName=" + keyYangName + '}';
520     }
521 }