Binding generator v2 - fix action #3
[mdsal.git] / binding2 / mdsal-binding2-generator-impl / src / main / java / org / opendaylight / mdsal / binding / javav2 / generator / impl / RpcActionGenHelper.java
1 /*
2  * Copyright (c) 2017 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
9 package org.opendaylight.mdsal.binding.javav2.generator.impl;
10
11 import static com.google.common.base.Preconditions.checkState;
12 import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.annotateDeprecatedIfNecessary;
13 import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.checkModuleAndModuleName;
14 import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.createDescription;
15 import static org.opendaylight.mdsal.binding.javav2.generator.impl.GenHelperUtil.addImplementedInterfaceFromUses;
16 import static org.opendaylight.mdsal.binding.javav2.generator.impl.GenHelperUtil.addRawInterfaceDefinition;
17 import static org.opendaylight.mdsal.binding.javav2.generator.impl.GenHelperUtil.moduleTypeBuilder;
18 import static org.opendaylight.mdsal.binding.javav2.generator.impl.GenHelperUtil.processUsesImplements;
19 import static org.opendaylight.mdsal.binding.javav2.generator.impl.GenHelperUtil.resolveDataSchemaNodesCheck;
20 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingGeneratorUtil.encodeAngleBrackets;
21 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.ACTION;
22 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.INPUT;
23 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.INSTANCE_IDENTIFIER;
24 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.INSTANTIABLE;
25 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.KEYED_INSTANCE_IDENTIFIER;
26 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.LIST_ACTION;
27 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.OUTPUT;
28 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.RPC;
29 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.RPC_CALLBACK;
30 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.TREE_NODE;
31 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.augmentable;
32 import static org.opendaylight.mdsal.binding.javav2.generator.util.Types.CLASS;
33 import static org.opendaylight.mdsal.binding.javav2.generator.util.Types.VOID;
34 import static org.opendaylight.mdsal.binding.javav2.generator.util.Types.parameterizedTypeFor;
35
36 import com.google.common.annotations.Beta;
37 import com.google.common.base.Optional;
38 import com.google.common.base.Preconditions;
39 import java.util.Collection;
40 import java.util.Map;
41 import java.util.Set;
42 import org.opendaylight.mdsal.binding.javav2.generator.context.ModuleContext;
43 import org.opendaylight.mdsal.binding.javav2.generator.spi.TypeProvider;
44 import org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes;
45 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedTransferObject;
46 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
47 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTypeBuilder;
48 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.MethodSignatureBuilder;
49 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingNamespaceType;
50 import org.opendaylight.yangtools.yang.common.QName;
51 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
52 import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer;
53 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
54 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
55 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
56 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
57 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
58 import org.opendaylight.yangtools.yang.model.api.Module;
59 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
60 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
61 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
62 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
63
64 /**
65  *
66  * Util class used for generation of types for RPCs, routedRPCs and Actions (YANG 1.1 only)
67  * in Binding spec. v2. In case of routed RPC detected in input YANG, RPC is turned to Action.
68  *
69  */
70 @Beta
71 final class RpcActionGenHelper {
72
73     private static final QName CONTEXT_REFERENCE =
74             QName.create("urn:opendaylight:yang:extension:yang-ext", "2013-07-09", "context-reference").intern();
75
76     private RpcActionGenHelper() {
77         throw new UnsupportedOperationException("Util class");
78     }
79
80     /**
81      * Let's find out what context we are talking about
82      * 1. routed RPC
83      * 2. global RPC
84      *
85      * In 1st case, we need Binding Generator behave like YANG 1.1 Action
86      *
87      * @param schemaNode RPC input node
88      * @return presence optional
89      */
90     private static Optional<QName> getRoutingContext(final DataSchemaNode schemaNode) {
91         for (UnknownSchemaNode extension : schemaNode.getUnknownSchemaNodes()) {
92             if (CONTEXT_REFERENCE.equals(extension.getNodeType())) {
93                 return Optional.fromNullable(extension.getQName());
94             }
95         }
96         return Optional.absent();
97     }
98
99     private static void resolveActions(final DataNodeContainer parent, final Module module,
100             final SchemaContext schemaContext, final boolean verboseClassComments,
101             Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders, final Map<Module, ModuleContext> genCtx,
102             TypeProvider typeProvider, final BindingNamespaceType namespaceType) {
103         Preconditions.checkNotNull(parent, "Parent should not be NULL.");
104         final Collection<DataSchemaNode> potentials = parent.getChildNodes();
105         for (DataSchemaNode potential : potentials) {
106             if (resolveDataSchemaNodesCheck(module, schemaContext,potential)) {
107                 BindingNamespaceType namespaceType1 = namespaceType;
108                 if (namespaceType.equals(BindingNamespaceType.Data)) {
109                     if (potential instanceof GroupingDefinition) {
110                         namespaceType1 = BindingNamespaceType.Grouping;
111                     }
112                 }
113
114                 if (potential instanceof ActionNodeContainer) {
115                     final Set<ActionDefinition> actions = ((ActionNodeContainer) potential).getActions();
116                     for (ActionDefinition action : actions) {
117                         genCtx.get(module).addTopLevelNodeType(resolveOperation(potential, action, module,
118                             schemaContext, verboseClassComments, genTypeBuilders, genCtx, typeProvider, true,
119                             namespaceType1));
120                     }
121                 }
122
123                 if (potential instanceof DataNodeContainer) {
124                     resolveActions((DataNodeContainer) potential, module, schemaContext, verboseClassComments,
125                         genTypeBuilders, genCtx, typeProvider, namespaceType1);
126                 }
127             }
128         }
129     }
130
131     /**
132      * Converts Yang 1.1 <b>Actions</b> to list of <code>Type</code> objects.
133      * @param module  module from which is obtained set of all Action objects to
134      *            iterate over them
135      * @param genCtx input, generated context
136      * @param verboseClassComments verbosity switch
137      * @return generated context
138      */
139     static Map<Module, ModuleContext> actionMethodsToGenType(final Module module, Map<Module, ModuleContext> genCtx,
140             final SchemaContext schemaContext, final boolean verboseClassComments,
141             Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders, TypeProvider typeProvider) {
142
143         checkModuleAndModuleName(module);
144         resolveActions(module, module, schemaContext, verboseClassComments, genTypeBuilders, genCtx, typeProvider,
145             BindingNamespaceType.Data);
146         return genCtx;
147     }
148
149     /**
150      * Converts global <b>RPCs</b> inputs and outputs sub-statements of the module
151      * to the list of <code>Type</code> objects. In addition, containers
152      * and lists which belong to input or output are also part of returning list.
153      * Detected routed RPCs are turned to Yang 1.1 Actions
154      *
155      * @param module
156      *            module from which is obtained set of all RPC objects to
157      *            iterate over them
158      * @param genCtx input, generated context
159      * @param verboseClassComments verbosity switch
160      *
161      * @throws IllegalArgumentException
162      *             <ul>
163      *             <li>if the module is null</li>
164      *             <li>if the name of module is null</li>
165      *             </ul>
166      * @throws IllegalStateException
167      *             if set of RPCs from module is null
168      *
169      * @return generated context
170      */
171      static Map<Module, ModuleContext> rpcMethodsToGenType(final Module module, Map<Module, ModuleContext> genCtx,
172             final SchemaContext schemaContext, final boolean verboseClassComments, Map<String, Map<String,
173              GeneratedTypeBuilder>> genTypeBuilders, TypeProvider typeProvider) {
174
175         checkModuleAndModuleName(module);
176         final Set<RpcDefinition> rpcDefinitions = module.getRpcs();
177         checkState(rpcDefinitions != null, "Set of RPCs from module " + module.getName() + " cannot be NULL.");
178         if (rpcDefinitions.isEmpty()) {
179             return genCtx;
180         }
181
182         for (final RpcDefinition rpc : rpcDefinitions) {
183             //FIXME: get correct parent for routed RPCs only
184             DataSchemaNode parent = null;
185
186             ContainerSchemaNode input = rpc.getInput();
187             boolean isAction = false;
188             if (input != null) {
189                 for (DataSchemaNode schemaNode : input.getChildNodes()) {
190                     if (getRoutingContext(schemaNode).isPresent()) {
191                         isAction = true;
192                         break;
193                     }
194                 }
195             }
196
197             //routedRPC?
198             if (isAction) {
199                 genCtx.get(module).addTopLevelNodeType(resolveOperation(parent, rpc, module, schemaContext,
200                         verboseClassComments, genTypeBuilders, genCtx, typeProvider, true,
201                         BindingNamespaceType.Data));
202             } else {
203                 //global RPC only
204                 genCtx.get(module).addTopLevelNodeType(resolveOperation(parent, rpc, module, schemaContext,
205                         verboseClassComments, genTypeBuilders, genCtx, typeProvider, false,
206                         BindingNamespaceType.Data));
207
208             }
209         }
210         return genCtx;
211     }
212
213     /**
214      * Converts RPC, Action or routed RPC into generated type
215      * @return generated type
216      */
217     private static GeneratedTypeBuilder resolveOperation(final DataSchemaNode parent, final OperationDefinition operation,
218             final Module module, final SchemaContext schemaContext, final boolean verboseClassComments,
219             Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders, final Map<Module, ModuleContext> genCtx,
220             TypeProvider typeProvider, final boolean isAction, final BindingNamespaceType namespaceType) {
221
222         //operation name
223         final String operationName = operation.getQName().getLocalName();
224         //concrete operation name
225         final StringBuilder sb = new StringBuilder(operationName).append('_');
226         if (isAction) {
227             sb.append("Action");
228         } else {
229             sb.append("Rpc");
230         }
231         final GeneratedTypeBuilder interfaceBuilder = moduleTypeBuilder(module, sb.toString(),
232                 verboseClassComments, genCtx.get(module));
233
234         final String basePackageName = interfaceBuilder.getPackageName();
235
236         interfaceBuilder.setDescription(createDescription(operation, interfaceBuilder.getFullyQualifiedName(),
237                 schemaContext, verboseClassComments, namespaceType));
238         final String operationComment = encodeAngleBrackets(operation.getDescription());
239         final MethodSignatureBuilder operationMethod = interfaceBuilder.addMethod("invoke");
240
241         //input
242         final ContainerSchemaNode input = operation.getInput();
243         final GeneratedTypeBuilder inType = resolveOperationNode(interfaceBuilder, module, operation.getInput(),
244                 basePackageName, schemaContext, operationName, verboseClassComments, typeProvider, genTypeBuilders,
245                 genCtx, true, namespaceType);
246         annotateDeprecatedIfNecessary(operation.getStatus(), inType);
247         inType.setParentTypeForBuilder(interfaceBuilder);
248         genCtx.get(module).addChildNodeType(input, inType);
249
250         //output
251         final ContainerSchemaNode output = operation.getOutput();
252         final GeneratedTypeBuilder outType = resolveOperationNode(interfaceBuilder, module, operation.getOutput(),
253                 basePackageName, schemaContext, operationName, verboseClassComments, typeProvider, genTypeBuilders,
254                 genCtx, false, namespaceType);
255         annotateDeprecatedIfNecessary(operation.getStatus(), outType);
256         outType.setParentTypeForBuilder(interfaceBuilder);
257         genCtx.get(module).addChildNodeType(output, outType);
258
259         final GeneratedType inTypeInstance = inType.toInstance();
260         operationMethod.addParameter(inTypeInstance, "input");
261
262         if (isAction) {
263             if (parent != null) {
264                 //action
265                 GeneratedTypeBuilder parentType = genCtx.get(module).getChildNode(parent.getPath());
266                 checkState(parentType != null, "Parent generated type for " + parent
267                         + " data schema node must have been generated already");
268                 annotateDeprecatedIfNecessary(parent.getStatus(), parentType);
269
270                 if (parent instanceof ListSchemaNode) {
271                     //ListAction
272                     GeneratedTransferObject keyType = null;
273                     for (MethodSignatureBuilder method : parentType.getMethodDefinitions()) {
274                         if (method.getName().equals("getKey")) {
275                             keyType = (GeneratedTransferObject) method.toInstance(parentType).getReturnType();
276                         }
277                     }
278
279                     operationMethod.addParameter(
280                             parameterizedTypeFor(KEYED_INSTANCE_IDENTIFIER, parentType, keyType), "kii");
281                     interfaceBuilder.addImplementsType(parameterizedTypeFor(LIST_ACTION, parentType, inType, outType));
282                 } else {
283                     //Action
284                     operationMethod.addParameter(parameterizedTypeFor(INSTANCE_IDENTIFIER, parentType), "ii");
285                     interfaceBuilder.addImplementsType(parameterizedTypeFor(ACTION, parentType, inType, outType));
286                 }
287             } else {
288                 //TODO:routed RPC
289                 throw new UnsupportedOperationException("Not implemented yet.");
290             }
291         } else {
292             //RPC
293             interfaceBuilder.addImplementsType(parameterizedTypeFor(RPC, inType, outType));
294         }
295
296         interfaceBuilder.addImplementsType(TREE_NODE);
297         operationMethod.addParameter(parameterizedTypeFor(RPC_CALLBACK, outType), "callback");
298
299         operationMethod.setComment(operationComment);
300         operationMethod.setReturnType(VOID);
301
302         return interfaceBuilder;
303     }
304
305     private static GeneratedTypeBuilder resolveOperationNode(GeneratedTypeBuilder parent, final Module module, final
306             ContainerSchemaNode operationNode, final String basePackageName, final SchemaContext schemaContext, final String
307             operationName, final boolean verboseClassComments, TypeProvider typeProvider, Map<String, Map<String,
308             GeneratedTypeBuilder>> genTypeBuilders, final Map<Module, ModuleContext> genCtx, final boolean isInput,
309             final BindingNamespaceType namespaceType) {
310         final GeneratedTypeBuilder nodeType = addRawInterfaceDefinition(basePackageName, operationNode, schemaContext,
311                 operationName, "", verboseClassComments, genTypeBuilders, namespaceType, genCtx.get(module));
312         addImplementedInterfaceFromUses(operationNode, nodeType, genCtx);
313         nodeType.addImplementsType(parameterizedTypeFor(BindingTypes.TREE_CHILD_NODE, parent, parameterizedTypeFor
314                 (BindingTypes.ITEM, parent)));
315         if (isInput) {
316             nodeType.addImplementsType(parameterizedTypeFor(INPUT, nodeType));
317         } else {
318             nodeType.addImplementsType(parameterizedTypeFor(OUTPUT, nodeType));
319         }
320         nodeType.addImplementsType(parameterizedTypeFor(INSTANTIABLE, nodeType));
321         nodeType.addImplementsType(augmentable(nodeType));
322         GenHelperUtil.resolveDataSchemaNodes(module, basePackageName, nodeType, nodeType, operationNode.getChildNodes(), genCtx,
323                 schemaContext, verboseClassComments, genTypeBuilders, typeProvider, namespaceType);
324
325         final MethodSignatureBuilder nodeMethod = nodeType.addMethod("implementedInterface");
326         nodeMethod.setReturnType(parameterizedTypeFor(CLASS, nodeType));
327         nodeMethod.addAnnotation("", "Override");
328
329         processUsesImplements(operationNode, module, schemaContext, genCtx, namespaceType);
330
331         return nodeType;
332     }
333 }