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