Mass-migrate to java.util.Optional
[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.GenHelperUtil.addImplementedInterfaceFromUses;
15 import static org.opendaylight.mdsal.binding.javav2.generator.impl.GenHelperUtil.addRawInterfaceDefinition;
16 import static org.opendaylight.mdsal.binding.javav2.generator.impl.GenHelperUtil.moduleTypeBuilder;
17 import static org.opendaylight.mdsal.binding.javav2.generator.impl.GenHelperUtil.processUsesImplements;
18 import static org.opendaylight.mdsal.binding.javav2.generator.impl.GenHelperUtil.resolveDataSchemaNodesCheck;
19 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingGeneratorUtil.encodeAngleBrackets;
20 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.ACTION;
21 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.INPUT;
22 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.INSTANCE_IDENTIFIER;
23 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.INSTANTIABLE;
24 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.KEYED_INSTANCE_IDENTIFIER;
25 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.LIST_ACTION;
26 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.OUTPUT;
27 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.RPC;
28 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.RPC_CALLBACK;
29 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.TREE_NODE;
30 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes.augmentable;
31 import static org.opendaylight.mdsal.binding.javav2.generator.util.Types.CLASS;
32 import static org.opendaylight.mdsal.binding.javav2.generator.util.Types.VOID;
33 import static org.opendaylight.mdsal.binding.javav2.generator.util.Types.parameterizedTypeFor;
34
35 import com.google.common.annotations.Beta;
36 import com.google.common.annotations.VisibleForTesting;
37 import com.google.common.base.Preconditions;
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      * 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     @VisibleForTesting
91     static Optional<QName> getRoutingContext(final DataSchemaNode schemaNode) {
92         for (UnknownSchemaNode extension : schemaNode.getUnknownSchemaNodes()) {
93             if (CONTEXT_REFERENCE.equals(extension.getNodeType())) {
94                 return Optional.ofNullable(extension.getQName());
95             }
96         }
97         return Optional.empty();
98     }
99
100     private static void resolveActions(final DataNodeContainer parent, final Module module,
101             final SchemaContext schemaContext, final boolean verboseClassComments,
102             final Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders, final Map<Module, ModuleContext> genCtx,
103             final TypeProvider typeProvider, final BindingNamespaceType namespaceType) {
104         Preconditions.checkNotNull(parent, "Parent should not be NULL.");
105         final Collection<DataSchemaNode> potentials = parent.getChildNodes();
106         for (DataSchemaNode potential : potentials) {
107             if (resolveDataSchemaNodesCheck(module, schemaContext,potential)) {
108                 BindingNamespaceType namespaceType1 = namespaceType;
109                 if (namespaceType.equals(BindingNamespaceType.Data)) {
110                     if (potential instanceof GroupingDefinition) {
111                         namespaceType1 = BindingNamespaceType.Grouping;
112                     }
113                 }
114
115                 if (potential instanceof ActionNodeContainer) {
116                     final Set<ActionDefinition> actions = ((ActionNodeContainer) potential).getActions();
117                     for (ActionDefinition action : actions) {
118                         genCtx.get(module).addTopLevelNodeType(resolveOperation(potential, action, module,
119                             schemaContext, verboseClassComments, genTypeBuilders, genCtx, typeProvider, true,
120                             namespaceType1));
121                     }
122                 }
123
124                 if (potential instanceof DataNodeContainer) {
125                     resolveActions((DataNodeContainer) potential, module, schemaContext, verboseClassComments,
126                         genTypeBuilders, genCtx, typeProvider, namespaceType1);
127                 }
128             }
129         }
130     }
131
132     /**
133      * Converts Yang 1.1 <b>Actions</b> to list of <code>Type</code> objects.
134      * @param module  module from which is obtained set of all Action objects to
135      *            iterate over them
136      * @param genCtx input, generated context
137      * @param verboseClassComments verbosity switch
138      * @return generated context
139      */
140     static Map<Module, ModuleContext> actionMethodsToGenType(final Module module, final Map<Module, ModuleContext> genCtx,
141             final SchemaContext schemaContext, final boolean verboseClassComments,
142             final Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders, final TypeProvider typeProvider) {
143
144         checkModuleAndModuleName(module);
145         resolveActions(module, module, schemaContext, verboseClassComments, genTypeBuilders, genCtx, typeProvider,
146             BindingNamespaceType.Data);
147         return genCtx;
148     }
149
150     /**
151      * Converts global <b>RPCs</b> inputs and outputs sub-statements of the module
152      * to the list of <code>Type</code> objects. In addition, containers
153      * and lists which belong to input or output are also part of returning list.
154      * Detected routed RPCs are turned to Yang 1.1 Actions
155      *
156      * @param module
157      *            module from which is obtained set of all RPC objects to
158      *            iterate over them
159      * @param genCtx input, generated context
160      * @param verboseClassComments verbosity switch
161      *
162      * @throws IllegalArgumentException
163      *             <ul>
164      *             <li>if the module is null</li>
165      *             <li>if the name of module is null</li>
166      *             </ul>
167      * @throws IllegalStateException
168      *             if set of RPCs from module is null
169      *
170      * @return generated context
171      */
172      static Map<Module, ModuleContext> rpcMethodsToGenType(final Module module, final Map<Module, ModuleContext> genCtx,
173             final SchemaContext schemaContext, final boolean verboseClassComments, final Map<String, Map<String,
174              GeneratedTypeBuilder>> genTypeBuilders, final TypeProvider typeProvider) {
175
176         checkModuleAndModuleName(module);
177         final Set<RpcDefinition> rpcDefinitions = module.getRpcs();
178         checkState(rpcDefinitions != null, "Set of RPCs from module " + module.getName() + " cannot be NULL.");
179         if (rpcDefinitions.isEmpty()) {
180             return genCtx;
181         }
182
183         for (final RpcDefinition rpc : rpcDefinitions) {
184             //FIXME: get correct parent for routed RPCs only
185             DataSchemaNode parent = null;
186
187             ContainerSchemaNode input = rpc.getInput();
188             boolean isAction = false;
189             if (input != null) {
190                 for (DataSchemaNode schemaNode : input.getChildNodes()) {
191                     if (getRoutingContext(schemaNode).isPresent()) {
192                         isAction = true;
193                         break;
194                     }
195                 }
196             }
197
198             //routedRPC?
199             if (isAction) {
200                 genCtx.get(module).addTopLevelNodeType(resolveOperation(parent, rpc, module, schemaContext,
201                         verboseClassComments, genTypeBuilders, genCtx, typeProvider, true,
202                         BindingNamespaceType.Data));
203             } else {
204                 //global RPC only
205                 genCtx.get(module).addTopLevelNodeType(resolveOperation(parent, rpc, module, schemaContext,
206                         verboseClassComments, genTypeBuilders, genCtx, typeProvider, false,
207                         BindingNamespaceType.Data));
208
209             }
210         }
211         return genCtx;
212     }
213
214     /**
215      * Converts RPC, Action or routed RPC into generated type
216      * @return generated type
217      */
218     private static GeneratedTypeBuilder resolveOperation(final DataSchemaNode parent, final OperationDefinition operation,
219             final Module module, final SchemaContext schemaContext, final boolean verboseClassComments,
220             final Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders, final Map<Module, ModuleContext> genCtx,
221             final TypeProvider typeProvider, final boolean isAction, final BindingNamespaceType namespaceType) {
222
223         //operation name
224         final String operationName = operation.getQName().getLocalName();
225         //concrete operation name
226         final StringBuilder sb = new StringBuilder(operationName).append('_');
227         if (isAction) {
228             sb.append("Action");
229         } else {
230             sb.append("Rpc");
231         }
232         final GeneratedTypeBuilder interfaceBuilder = moduleTypeBuilder(module, sb.toString(),
233                 verboseClassComments, genCtx.get(module));
234
235         final String basePackageName = interfaceBuilder.getPackageName();
236
237         if (verboseClassComments) {
238             interfaceBuilder.addComment(TypeComments.javadoc(
239                 "Interface for implementing the following YANG Operation defined in module <b>" + module.getName() + "</b>")
240                 .get());
241             YangSourceDefinition.of(module, operation).ifPresent(interfaceBuilder::setYangSourceDefinition);
242         }
243
244         final String operationComment = encodeAngleBrackets(operation.getDescription().orElse(null));
245         final MethodSignatureBuilder operationMethod = interfaceBuilder.addMethod("invoke");
246
247         //input
248         final ContainerSchemaNode input = operation.getInput();
249         final GeneratedTypeBuilder inType = resolveOperationNode(interfaceBuilder, module, operation.getInput(),
250                 basePackageName, schemaContext, operationName, verboseClassComments, typeProvider, genTypeBuilders,
251                 genCtx, true, namespaceType);
252         annotateDeprecatedIfNecessary(operation.getStatus(), inType);
253         inType.setParentTypeForBuilder(interfaceBuilder);
254         genCtx.get(module).addChildNodeType(input, inType);
255
256         //output
257         final ContainerSchemaNode output = operation.getOutput();
258         final GeneratedTypeBuilder outType = resolveOperationNode(interfaceBuilder, module, operation.getOutput(),
259                 basePackageName, schemaContext, operationName, verboseClassComments, typeProvider, genTypeBuilders,
260                 genCtx, false, namespaceType);
261         annotateDeprecatedIfNecessary(operation.getStatus(), outType);
262         outType.setParentTypeForBuilder(interfaceBuilder);
263         genCtx.get(module).addChildNodeType(output, outType);
264
265         final GeneratedType inTypeInstance = inType.toInstance();
266         operationMethod.addParameter(inTypeInstance, "input");
267
268         if (isAction) {
269             if (parent != null) {
270                 //action
271                 GeneratedTypeBuilder parentType = genCtx.get(module).getChildNode(parent.getPath());
272                 checkState(parentType != null, "Parent generated type for " + parent
273                         + " data schema node must have been generated already");
274                 annotateDeprecatedIfNecessary(parent.getStatus(), parentType);
275
276                 if (parent instanceof ListSchemaNode) {
277                     //ListAction
278                     GeneratedTransferObject keyType = null;
279                     for (MethodSignatureBuilder method : parentType.getMethodDefinitions()) {
280                         if (method.getName().equals("getKey")) {
281                             keyType = (GeneratedTransferObject) method.toInstance(parentType).getReturnType();
282                         }
283                     }
284
285                     operationMethod.addParameter(
286                             parameterizedTypeFor(KEYED_INSTANCE_IDENTIFIER, parentType, keyType), "kii");
287                     interfaceBuilder.addImplementsType(parameterizedTypeFor(LIST_ACTION, parentType, inType, outType));
288                 } else {
289                     //Action
290                     operationMethod.addParameter(parameterizedTypeFor(INSTANCE_IDENTIFIER, parentType), "ii");
291                     interfaceBuilder.addImplementsType(parameterizedTypeFor(ACTION, parentType, inType, outType));
292                 }
293             } else {
294                 //TODO:routed RPC
295                 throw new UnsupportedOperationException("Not implemented yet.");
296             }
297         } else {
298             //RPC
299             interfaceBuilder.addImplementsType(parameterizedTypeFor(RPC, inType, outType));
300         }
301
302         interfaceBuilder.addImplementsType(TREE_NODE);
303         operationMethod.addParameter(parameterizedTypeFor(RPC_CALLBACK, outType), "callback");
304
305         operationMethod.setComment(operationComment);
306         operationMethod.setReturnType(VOID);
307
308         return interfaceBuilder;
309     }
310
311     private static GeneratedTypeBuilder resolveOperationNode(final GeneratedTypeBuilder parent, final Module module, final
312             ContainerSchemaNode operationNode, final String basePackageName, final SchemaContext schemaContext, final String
313             operationName, final boolean verboseClassComments, final TypeProvider typeProvider, final Map<String, Map<String,
314             GeneratedTypeBuilder>> genTypeBuilders, final Map<Module, ModuleContext> genCtx, final boolean isInput,
315             final BindingNamespaceType namespaceType) {
316         final GeneratedTypeBuilder nodeType = addRawInterfaceDefinition(basePackageName, operationNode, schemaContext,
317                 operationName, "", verboseClassComments, genTypeBuilders, namespaceType, genCtx.get(module));
318         addImplementedInterfaceFromUses(operationNode, nodeType, genCtx);
319         nodeType.addImplementsType(parameterizedTypeFor(BindingTypes.TREE_CHILD_NODE, parent, parameterizedTypeFor
320                 (BindingTypes.ITEM, nodeType)));
321         if (isInput) {
322             nodeType.addImplementsType(parameterizedTypeFor(INPUT, nodeType));
323         } else {
324             nodeType.addImplementsType(parameterizedTypeFor(OUTPUT, nodeType));
325         }
326         nodeType.addImplementsType(parameterizedTypeFor(INSTANTIABLE, nodeType));
327         nodeType.addImplementsType(augmentable(nodeType));
328         GenHelperUtil.resolveDataSchemaNodes(module, basePackageName, nodeType, nodeType, operationNode.getChildNodes(), genCtx,
329                 schemaContext, verboseClassComments, genTypeBuilders, typeProvider, namespaceType);
330
331         final MethodSignatureBuilder nodeMethod = nodeType.addMethod("implementedInterface");
332         nodeMethod.setReturnType(parameterizedTypeFor(CLASS, nodeType));
333         nodeMethod.addAnnotation("", "Override");
334
335         processUsesImplements(operationNode, module, schemaContext, genCtx, namespaceType);
336
337         return nodeType;
338     }
339 }