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