2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.config.yangjmxgenerator;
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;
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;
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.
65 public class RuntimeBeanEntry {
67 private static final Function<SchemaNode, QName> QNAME_FROM_NODE = new Function<SchemaNode, QName>() {
69 public QName apply(final SchemaNode input) {
70 return input.getQName();
74 private static final Function<UnknownSchemaNode, String> UNKNOWN_NODE_TO_STRING = new Function<UnknownSchemaNode, String>() {
76 public String apply(final UnknownSchemaNode input) {
77 return input.getQName().getLocalName() + input.getNodeParameter();
81 private final String packageName;
82 private final String yangName, javaNamePrefix;
83 private final boolean isRoot;
84 private final Optional<String> keyYangName, keyJavaName;
85 private final Map<String, AttributeIfc> attributeMap;
86 private final List<RuntimeBeanEntry> children;
87 private final Set<Rpc> rpcs;
90 RuntimeBeanEntry(final String packageName,
91 final DataNodeContainer nodeForReporting, final String yangName,
92 final String javaNamePrefix, final boolean isRoot,
93 final Optional<String> keyYangName, final List<AttributeIfc> attributes,
94 final List<RuntimeBeanEntry> children, final Set<Rpc> rpcs) {
96 checkArgument(isRoot == false || keyYangName.isPresent() == false,
97 "Root RuntimeBeanEntry must not have key set");
98 this.packageName = packageName;
100 this.yangName = yangName;
101 this.javaNamePrefix = javaNamePrefix;
102 this.children = Collections.unmodifiableList(children);
103 this.rpcs = Collections.unmodifiableSet(rpcs);
105 this.keyYangName = keyYangName;
106 Map<String, AttributeIfc> map = new HashMap<>();
108 for (AttributeIfc a : attributes) {
109 checkState(map.containsKey(a.getAttributeYangName()) == false,
110 "Attribute already defined: %s in %s", a.getAttributeYangName(), nodeForReporting);
111 map.put(a.getAttributeYangName(), a);
114 if (keyYangName.isPresent()) {
115 AttributeIfc keyJavaName = map.get(keyYangName.get());
116 checkArgument(keyJavaName != null, "Key %s not found in attribute list %s in %s", keyYangName.get(),
117 attributes, nodeForReporting);
118 this.keyJavaName = Optional
119 .of(keyJavaName.getUpperCaseCammelCase());
121 keyJavaName = Optional.absent();
123 attributeMap = Collections.unmodifiableMap(map);
127 * @return map containing all class names as key, extracted RuntimeBeans as
128 * values. If more than zero values is returned, exactly one
129 * RuntimeBeanEntry will have isRoot set to true, even if yang does
130 * not contain special configuration for it.
132 public static Map<String, RuntimeBeanEntry> extractClassNameToRuntimeBeanMap(
133 final String packageName, final DataNodeContainer container,
134 final String moduleYangName, final TypeProviderWrapper typeProviderWrapper,
135 final String javaNamePrefix, final Module currentModule, final SchemaContext schemaContext) {
138 AttributesRpcsAndRuntimeBeans attributesRpcsAndRuntimeBeans = extractSubtree(
139 packageName, container, typeProviderWrapper, currentModule,
141 Map<String, RuntimeBeanEntry> result = new HashMap<>();
143 List<AttributeIfc> attributes;
145 if (attributesRpcsAndRuntimeBeans.isEmpty() == false) {
146 attributes = attributesRpcsAndRuntimeBeans.getAttributes();
147 rpcs = attributesRpcsAndRuntimeBeans.getRpcs();
149 // create artificial root if not defined in yang
150 attributes = Collections.emptyList();
151 rpcs = Collections.emptySet();
153 RuntimeBeanEntry rootRuntimeBeanEntry = createRoot(packageName,
154 container, moduleYangName, attributes, javaNamePrefix,
155 attributesRpcsAndRuntimeBeans.getRuntimeBeanEntries(), rpcs);
157 Deque<RuntimeBeanEntry> stack = new LinkedList<>();
158 stack.add(rootRuntimeBeanEntry);
160 while (stack.isEmpty() == false) {
161 RuntimeBeanEntry first = stack.pollFirst();
162 if (result.containsKey(first.getJavaNameOfRuntimeMXBean())) {
163 throw new NameConflictException(
164 first.getJavaNameOfRuntimeMXBean(), null, null);
166 result.put(first.getJavaNameOfRuntimeMXBean(), first);
167 stack.addAll(first.getChildren());
172 private static Multimap<QName/* of identity */, RpcDefinition> getIdentitiesToRpcs(
173 final SchemaContext schemaCtx) {
174 Multimap<QName, RpcDefinition> result = HashMultimap.create();
175 for (Module currentModule : schemaCtx.getModules()) {
177 // Find all identities in current module for later identity->rpc mapping
178 Set<QName> allIdentitiesInModule = Sets.newHashSet(Collections2.transform(currentModule.getIdentities(), QNAME_FROM_NODE));
180 for (RpcDefinition rpc : currentModule.getRpcs()) {
181 ContainerSchemaNode input = rpc.getInput();
183 for (UsesNode uses : input.getUses()) {
185 // Check if the rpc is config rpc by looking for input argument rpc-context-ref
186 Iterator<QName> pathFromRoot = uses.getGroupingPath().getPathFromRoot().iterator();
187 if (!pathFromRoot.hasNext() ||
188 !pathFromRoot.next().equals(ConfigConstants.RPC_CONTEXT_REF_GROUPING_QNAME)) {
192 for (SchemaNode refinedNode : uses.getRefines().values()) {
193 for (UnknownSchemaNode unknownSchemaNode : refinedNode
194 .getUnknownSchemaNodes()) {
195 if (ConfigConstants.RPC_CONTEXT_INSTANCE_EXTENSION_QNAME
196 .equals(unknownSchemaNode.getNodeType())) {
197 String localIdentityName = unknownSchemaNode
199 QName identityQName = QName.create(
200 currentModule.getNamespace(),
201 currentModule.getRevision(),
203 Preconditions.checkArgument(allIdentitiesInModule.contains(identityQName),
204 "Identity referenced by rpc not found. Identity: %s, rpc: %s", localIdentityName, rpc);
205 result.put(identityQName, rpc);
217 * Get direct descendants of this subtree, together with attributes defined
220 private static AttributesRpcsAndRuntimeBeans extractSubtree(
221 final String packageName, final DataNodeContainer subtree,
222 final TypeProviderWrapper typeProviderWrapper, final Module currentModule,
223 final SchemaContext ctx) {
225 Multimap<QName, RpcDefinition> identitiesToRpcs = getIdentitiesToRpcs(ctx);
227 List<AttributeIfc> attributes = Lists.newArrayList();
228 List<RuntimeBeanEntry> runtimeBeanEntries = new ArrayList<>();
229 for (DataSchemaNode child : subtree.getChildNodes()) {
230 // child leaves can be java attributes, TO attributes, or child
232 if (child instanceof LeafSchemaNode) {
233 // just save the attribute
234 LeafSchemaNode leaf = (LeafSchemaNode) child;
235 attributes.add(new JavaAttribute(leaf, typeProviderWrapper));
236 } else if (child instanceof ContainerSchemaNode) {
237 ContainerSchemaNode container = (ContainerSchemaNode) child;
238 // this can be either TO or hierarchical RB
239 TOAttribute toAttribute = TOAttribute.create(container,
240 typeProviderWrapper, packageName);
241 attributes.add(toAttribute);
242 } else if (child instanceof ListSchemaNode) {
243 if (isInnerStateBean(child)) {
244 ListSchemaNode listSchemaNode = (ListSchemaNode) child;
245 RuntimeBeanEntry hierarchicalChild = createHierarchical(
246 packageName, listSchemaNode, typeProviderWrapper,
248 runtimeBeanEntries.add(hierarchicalChild);
249 } else /* ordinary list attribute */{
250 ListAttribute listAttribute = ListAttribute.create(
251 (ListSchemaNode) child, typeProviderWrapper, packageName);
252 attributes.add(listAttribute);
255 } else if (child instanceof LeafListSchemaNode) {
256 ListAttribute listAttribute = ListAttribute.create(
257 (LeafListSchemaNode) child, typeProviderWrapper);
258 attributes.add(listAttribute);
260 throw new IllegalStateException("Unexpected running-data node "
264 Set<Rpc> rpcs = new HashSet<>();
265 SchemaNode subtreeSchemaNode = (SchemaNode) subtree;
266 for (UnknownSchemaNode unknownSchemaNode : subtreeSchemaNode
267 .getUnknownSchemaNodes()) {
268 if (ConfigConstants.RPC_CONTEXT_INSTANCE_EXTENSION_QNAME
269 .equals(unknownSchemaNode.getNodeType())) {
270 String localIdentityName = unknownSchemaNode.getNodeParameter();
271 QName identityQName = unknownSchemaNode.isAddedByUses() ?
272 findQNameFromGrouping(subtree, ctx, unknownSchemaNode, localIdentityName) :
273 QName.create(currentModule.getNamespace(), currentModule.getRevision(), localIdentityName);
274 // convert RpcDefinition to Rpc
275 for (RpcDefinition rpcDefinition : identitiesToRpcs.get(identityQName)) {
276 String name = TypeProviderWrapper
277 .findJavaParameter(rpcDefinition);
278 AttributeIfc returnType;
279 if (rpcDefinition.getOutput() == null
280 || rpcDefinition.getOutput().getChildNodes().isEmpty()) {
281 returnType = VoidAttribute.getInstance();
282 } else if (rpcDefinition.getOutput().getChildNodes().size() == 1) {
283 DataSchemaNode returnDSN = rpcDefinition.getOutput()
284 .getChildNodes().iterator().next();
285 returnType = getReturnTypeAttribute(returnDSN, typeProviderWrapper, packageName);
288 throw new IllegalArgumentException(
289 "More than one child node in rpc output is not supported. "
290 + "Error occured in " + rpcDefinition);
292 List<JavaAttribute> parameters = new ArrayList<>();
293 for (DataSchemaNode childNode : sortAttributes(rpcDefinition.getInput()
295 if (childNode.isAddedByUses() == false) { // skip
298 checkArgument(childNode instanceof LeafSchemaNode, "Unexpected type of rpc input type. "
299 + "Currently only leafs and empty output nodes are supported, got " + childNode);
300 JavaAttribute javaAttribute = new JavaAttribute(
301 (LeafSchemaNode) childNode,
302 typeProviderWrapper);
303 parameters.add(javaAttribute);
306 Rpc newRpc = new Rpc(returnType, name, rpcDefinition
307 .getQName().getLocalName(), parameters);
312 return new AttributesRpcsAndRuntimeBeans(runtimeBeanEntries,
317 * Find "proper" qname of unknown node in case it comes from a grouping
319 private static QName findQNameFromGrouping(final DataNodeContainer subtree, final SchemaContext ctx, final UnknownSchemaNode unknownSchemaNode, final String localIdentityName) {
320 QName identityQName = null;
321 for (UsesNode usesNode : subtree.getUses()) {
322 SchemaNode dataChildByName = SchemaContextUtil.findDataSchemaNode(ctx, usesNode.getGroupingPath());
323 Module m = SchemaContextUtil.findParentModule(ctx, dataChildByName);
324 List<UnknownSchemaNode> unknownSchemaNodes = dataChildByName.getUnknownSchemaNodes();
325 if(Collections2.transform(unknownSchemaNodes, UNKNOWN_NODE_TO_STRING).contains(UNKNOWN_NODE_TO_STRING.apply(unknownSchemaNode))) {
326 identityQName = QName.create(dataChildByName.getQName(), localIdentityName);
329 return identityQName;
332 private static AttributeIfc getReturnTypeAttribute(final DataSchemaNode child, final TypeProviderWrapper typeProviderWrapper,
333 final String packageName) {
334 if (child instanceof LeafSchemaNode) {
335 LeafSchemaNode leaf = (LeafSchemaNode) child;
336 return new JavaAttribute(leaf, typeProviderWrapper);
337 } else if (child instanceof ContainerSchemaNode) {
338 ContainerSchemaNode container = (ContainerSchemaNode) child;
339 TOAttribute toAttribute = TOAttribute.create(container, typeProviderWrapper, packageName);
341 } else if (child instanceof ListSchemaNode) {
342 return ListAttribute.create((ListSchemaNode) child, typeProviderWrapper, packageName);
343 } else if (child instanceof LeafListSchemaNode) {
344 return ListAttribute.create((LeafListSchemaNode) child, typeProviderWrapper);
346 throw new IllegalStateException("Unknown output data node " + child + " for rpc");
350 private static Collection<DataSchemaNode> sortAttributes(final Collection<DataSchemaNode> childNodes) {
351 final TreeSet<DataSchemaNode> dataSchemaNodes = new TreeSet<>(new Comparator<DataSchemaNode>() {
353 public int compare(final DataSchemaNode o1, final DataSchemaNode o2) {
354 return o1.getQName().getLocalName().compareTo(o2.getQName().getLocalName());
357 dataSchemaNodes.addAll(childNodes);
358 return dataSchemaNodes;
361 private static boolean isInnerStateBean(final DataSchemaNode child) {
362 for (UnknownSchemaNode unknownSchemaNode : child
363 .getUnknownSchemaNodes()) {
364 if (unknownSchemaNode.getNodeType().equals(
365 ConfigConstants.INNER_STATE_BEAN_EXTENSION_QNAME)) {
372 private static RuntimeBeanEntry createHierarchical(final String packageName,
373 final ListSchemaNode listSchemaNode,
374 final TypeProviderWrapper typeProviderWrapper, final Module currentModule,
375 final SchemaContext ctx) {
377 // supported are numeric types, strings, enums
378 // get all attributes
379 AttributesRpcsAndRuntimeBeans attributesRpcsAndRuntimeBeans = extractSubtree(
380 packageName, listSchemaNode, typeProviderWrapper,
383 Optional<String> keyYangName;
384 if (listSchemaNode.getKeyDefinition().isEmpty()) {
385 keyYangName = Optional.absent();
386 } else if (listSchemaNode.getKeyDefinition().size() == 1) {
387 // key must be either null or one of supported key types
388 QName keyQName = listSchemaNode.getKeyDefinition().iterator()
390 keyYangName = Optional.of(keyQName.getLocalName());
393 throw new IllegalArgumentException(
394 "More than one key is not supported in " + listSchemaNode);
397 String javaNamePrefix = TypeProviderWrapper
398 .findJavaNamePrefix(listSchemaNode);
400 RuntimeBeanEntry rbFromAttributes = new RuntimeBeanEntry(packageName,
401 listSchemaNode, listSchemaNode.getQName().getLocalName(),
402 javaNamePrefix, false, keyYangName,
403 attributesRpcsAndRuntimeBeans.getAttributes(),
404 attributesRpcsAndRuntimeBeans.getRuntimeBeanEntries(),
405 attributesRpcsAndRuntimeBeans.getRpcs());
407 return rbFromAttributes;
410 private static RuntimeBeanEntry createRoot(final String packageName,
411 final DataNodeContainer nodeForReporting, final String attributeYangName,
412 final List<AttributeIfc> attributes, final String javaNamePrefix,
413 final List<RuntimeBeanEntry> children, final Set<Rpc> rpcs) {
414 return new RuntimeBeanEntry(packageName, nodeForReporting,
415 attributeYangName, javaNamePrefix, true,
416 Optional.<String> absent(), attributes, children, rpcs);
419 public boolean isRoot() {
423 public Optional<String> getKeyYangName() {
427 public Optional<String> getKeyJavaName() {
431 public Collection<AttributeIfc> getAttributes() {
432 return attributeMap.values();
435 public Map<String, AttributeIfc> getYangPropertiesToTypesMap() {
439 public String getYangName() {
443 public String getPackageName() {
447 public String getJavaNamePrefix() {
448 return javaNamePrefix;
451 public List<RuntimeBeanEntry> getChildren() {
455 public Set<Rpc> getRpcs() {
459 private static class AttributesRpcsAndRuntimeBeans {
460 private final List<RuntimeBeanEntry> runtimeBeanEntries;
461 private final List<AttributeIfc> attributes;
462 private final Set<Rpc> rpcs;
464 public AttributesRpcsAndRuntimeBeans(
465 final List<RuntimeBeanEntry> runtimeBeanEntries,
466 final List<AttributeIfc> attributes, final Set<Rpc> rpcs) {
467 this.runtimeBeanEntries = runtimeBeanEntries;
468 this.attributes = attributes;
472 private List<AttributeIfc> getAttributes() {
476 public List<RuntimeBeanEntry> getRuntimeBeanEntries() {
477 return runtimeBeanEntries;
480 public boolean isEmpty() {
481 return attributes.isEmpty() && rpcs.isEmpty();
484 private Set<Rpc> getRpcs() {
489 public static class Rpc {
490 private final String name;
491 private final List<JavaAttribute> parameters;
492 private final AttributeIfc returnType;
493 private final String yangName;
495 Rpc(final AttributeIfc returnType, final String name, final String yangName,
496 final List<JavaAttribute> parameters) {
497 this.returnType = returnType;
499 this.parameters = parameters;
500 this.yangName = yangName;
503 public String getYangName() {
507 public String getName() {
511 public List<JavaAttribute> getParameters() {
515 public AttributeIfc getReturnType() {
520 private static final String MXBEAN_SUFFIX = "RuntimeMXBean";
522 public String getJavaNameOfRuntimeMXBean() {
523 return getJavaNameOfRuntimeMXBean(javaNamePrefix);
526 public String getFullyQualifiedName(final String typeName) {
527 return FullyQualifiedNameHelper.getFullyQualifiedName(packageName,
531 private static String getJavaNameOfRuntimeMXBean(final String javaNamePrefix) {
532 return javaNamePrefix + MXBEAN_SUFFIX;
536 public String toString() {
537 return "RuntimeBeanEntry{" + "isRoot=" + isRoot + ", yangName='"
538 + yangName + '\'' + ", packageName='" + packageName + '\''
539 + ", keyYangName=" + keyYangName + '}';