Switch to Objects.requireNonNull
[mdsal.git] / binding2 / mdsal-binding2-dom-codec / src / main / java / org / opendaylight / mdsal / binding / javav2 / dom / codec / generator / spi / source / AbstractDataNodeContainerSerializerSource.java
1 /*
2  * Copyright (c) 2017 Pantheon Technologies s.r.o. 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.mdsal.binding.javav2.dom.codec.generator.spi.source;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.Beta;
13 import java.util.Collection;
14 import java.util.HashMap;
15 import java.util.Map;
16 import org.opendaylight.mdsal.binding.javav2.dom.codec.generator.impl.StreamWriterGenerator;
17 import org.opendaylight.mdsal.binding.javav2.dom.codec.generator.spi.generator.AbstractGenerator;
18 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.serializer.ChoiceDispatchSerializer;
19 import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifier;
20 import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifierNormalizer;
21 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
22 import org.opendaylight.mdsal.binding.javav2.model.api.MethodSignature;
23 import org.opendaylight.mdsal.binding.javav2.model.api.ParameterizedType;
24 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
25 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
26 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingSerializer;
27 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingStreamEventWriter;
28 import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializerImplementation;
29 import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializerRegistry;
30 import org.opendaylight.yangtools.yang.common.QName;
31 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
35 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
40 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 @Beta
46 public abstract class AbstractDataNodeContainerSerializerSource extends AbstractTreeNodeSerializerSource {
47
48     private static final Logger LOG = LoggerFactory.getLogger(AbstractDataNodeContainerSerializerSource.class);
49
50     protected static final String INPUT = "_input";
51     private static final String CHOICE_PREFIX = "CHOICE_";
52
53     //Note: the field takes no effects by augmentation.
54     private final DataNodeContainer schemaNode;
55     private final GeneratedType dtoType;
56
57     public AbstractDataNodeContainerSerializerSource(final AbstractGenerator generator, final GeneratedType type,
58             final DataNodeContainer node) {
59         super(generator);
60         this.dtoType = requireNonNull(type);
61         this.schemaNode = requireNonNull(node);
62     }
63
64     /**
65      * Return the character sequence which should be used for start event.
66      *
67      * @return Start event character sequence
68      */
69     protected abstract CharSequence emitStartEvent();
70
71     @Override
72     public CharSequence getSerializerBody() {
73         final StringBuilder builder = new StringBuilder();
74         builder.append("{\n");
75         builder.append(statement(assign(TreeNodeSerializerRegistry.class.getName(), REGISTRY, "$1")));
76         builder.append(statement(assign(dtoType.getFullyQualifiedName(), INPUT,
77                 cast(dtoType.getFullyQualifiedName(), "$2"))));
78         builder.append(statement(assign(BindingStreamEventWriter.class.getName(), STREAM,
79             cast(BindingStreamEventWriter.class.getName(), "$3"))));
80         builder.append(statement(assign(BindingSerializer.class.getName(), SERIALIZER, null)));
81         builder.append("if (");
82         builder.append(STREAM);
83         builder.append(" instanceof ");
84         builder.append(BindingSerializer.class.getName());
85         builder.append(") {");
86         builder.append(statement(assign(SERIALIZER, cast(BindingSerializer.class.getName(), STREAM))));
87         builder.append('}');
88         builder.append(statement(emitStartEvent()));
89
90         emitBody(builder);
91         emitAfterBody(builder);
92         builder.append(statement(endNode()));
93         builder.append(statement("return null"));
94         builder.append('}');
95         return builder;
96     }
97
98     /**
99      * Allows for customization of emitting code, which is processed after
100      * normal DataNodeContainer body. Ideal for augmentations or others.
101      */
102     protected void emitAfterBody(final StringBuilder builder) {
103     }
104
105     private static Map<String, Type> collectAllProperties(final GeneratedType type, final Map<String, Type> hashMap) {
106         for (final MethodSignature definition : type.getMethodDefinitions()) {
107             hashMap.put(definition.getName(), definition.getReturnType());
108         }
109
110        /**
111         * According to binding v2 spec., uses nodes are processed as-if they are direct children
112         * of parent node, so we can't get properties from implements any more. Uses nodes are processed by invoking
113         * {@link org.opendaylight.mdsal.binding.javav2.generator.impl.GenHelperUtil#resolveDataSchemaNodes()} in which
114         * {@link org.opendaylight.mdsal.binding.javav2.generator.impl.GenHelperUtil#resolveDataSchemaNodesCheck()}
115         * allows them to be resolved.
116         */
117
118         return hashMap;
119     }
120
121     private static String getGetterName(final DataSchemaNode node) {
122         final TypeDefinition<?> type;
123         if (node instanceof TypedDataSchemaNode) {
124             type = ((TypedDataSchemaNode) node).getType();
125         } else {
126             type = null;
127         }
128
129         final String prefix;
130         // Bug 8903: If it is a derived type of boolean, not a built-in type, then the return type
131         // of method would be the generated type and the prefix should be 'get'.
132         if (type instanceof BooleanTypeDefinition
133                 && (type.getPath().equals(node.getPath()) || type.getBaseType() == null)) {
134             prefix = "is";
135         } else {
136             prefix = "get";
137         }
138         return prefix + JavaIdentifierNormalizer.normalizeSpecificIdentifier(node.getQName().getLocalName(),
139             JavaIdentifier.CLASS);
140     }
141
142     private boolean emitCheck(final DataSchemaNode schemaChild) {
143         if (schemaChild.isAugmenting()) {
144             QName root = schemaChild.getPath().getPathFromRoot().iterator().next();
145             return root.getModule().equals(schemaChild.getQName().getModule());
146         }
147
148         return true;
149     }
150
151     /**
152      * Note: the method would be overrided by {@link AbstractAugmentSerializerSource#getChildNodes()},
153      * since all augmentation schema nodes of same target would be grouped into sort of one node,
154      * so call {@link AbstractAugmentSerializerSource#getChildNodes()} to get all these children
155      * nodes of same target augmentation schemas.
156      */
157     protected Collection<DataSchemaNode> getChildNodes() {
158         return schemaNode.getChildNodes();
159     }
160
161     private void emitBody(final StringBuilder builder) {
162         final Map<String, Type> getterToType = collectAllProperties(dtoType, new HashMap<String, Type>());
163         for (final DataSchemaNode schemaChild : getChildNodes()) {
164             /**
165              * As before, it only emitted data nodes which were not added by uses or augment, now
166              * according to binding v2 specification, augment of the same module is same as inlining,
167              * all data node children should be processed as-if they were directly defined inside
168              * target node.
169              */
170             if (emitCheck(schemaChild)) {
171                 final String getter = getGetterName(schemaChild);
172                 final Type childType = getterToType.get(getter);
173                 if (childType == null) {
174                     // FIXME AnyXml nodes are ignored, since their type cannot be found in generated bindnig
175                     // Bug-706 https://bugs.opendaylight.org/show_bug.cgi?id=706
176                     if (schemaChild instanceof AnyXmlSchemaNode) {
177                         LOG.warn("Node {} will be ignored. AnyXml is not yet supported from binding aware code."
178                             + "Binding Independent code can be used to serialize anyXml nodes.", schemaChild.getPath());
179                         continue;
180                     }
181
182                     throw new IllegalStateException(
183                         String.format("Unable to find type for child node %s. Expected child nodes: %s",
184                             schemaChild.getPath(), getterToType));
185                 }
186                 emitChild(builder, getter, childType, schemaChild);
187             }
188         }
189     }
190
191     private void emitChild(final StringBuilder builder, final String getterName, final Type childType,
192             final DataSchemaNode schemaChild) {
193         builder.append(statement(assign(childType, getterName, cast(childType, invoke(INPUT, getterName)))));
194
195         builder.append("if (").append(getterName).append(" != null) {\n");
196         emitChildInner(builder, getterName, childType, schemaChild);
197         builder.append("}\n");
198     }
199
200     private void emitChildInner(final StringBuilder builder, final String getterName, final Type childType,
201             final DataSchemaNode child) {
202         if (child instanceof LeafSchemaNode) {
203             builder.append(statement(leafNode(child.getQName().getLocalName(), getterName)));
204         } else if (child instanceof AnyXmlSchemaNode) {
205             builder.append(statement(anyxmlNode(child.getQName().getLocalName(), getterName)));
206         } else if (child instanceof LeafListSchemaNode) {
207             final CharSequence startEvent;
208             if (((LeafListSchemaNode) child).isUserOrdered()) {
209                 startEvent = startOrderedLeafSet(child.getQName().getLocalName(),
210                     invoke(getterName, "size"));
211             } else {
212                 startEvent = startLeafSet(child.getQName().getLocalName(),invoke(getterName, "size"));
213             }
214             builder.append(statement(startEvent));
215             final Type valueType = ((ParameterizedType) childType).getActualTypeArguments()[0];
216             builder.append(forEach(getterName, valueType, statement(leafSetEntryNode(CURRENT))));
217             builder.append(statement(endNode()));
218         } else if (child instanceof ListSchemaNode) {
219             final Type valueType = ((ParameterizedType) childType).getActualTypeArguments()[0];
220             final ListSchemaNode casted = (ListSchemaNode) child;
221             emitList(builder, getterName, valueType, casted);
222         } else if (child instanceof ContainerSchemaNode) {
223             builder.append(tryToUseCacheElse(getterName,statement(staticInvokeEmitter(childType, getterName))));
224         } else if (child instanceof ChoiceSchemaNode) {
225             final String propertyName = CHOICE_PREFIX + childType.getName();
226             staticConstant(propertyName, TreeNodeSerializerImplementation.class,
227                 ChoiceDispatchSerializer.from(loadClass(childType)));
228             builder.append(tryToUseCacheElse(getterName,statement(invoke(propertyName,
229                 StreamWriterGenerator.SERIALIZE_METHOD_NAME, REGISTRY, cast(TreeNode.class.getName(),getterName),
230                 STREAM))));
231         }
232     }
233
234     private static StringBuilder tryToUseCacheElse(final String getterName, final CharSequence statement) {
235         final StringBuilder b = new StringBuilder();
236
237         b.append("if ( ");
238         b.append(SERIALIZER).append("== null || ");
239         b.append(invoke(SERIALIZER, "serialize", getterName)).append("== null");
240         b.append(") {");
241         b.append(statement);
242         b.append('}');
243         return b;
244     }
245
246     private void emitList(final StringBuilder builer, final String getterName, final Type valueType,
247             final ListSchemaNode child) {
248         final CharSequence startEvent;
249
250         builer.append(statement(assign("int", "_count", invoke(getterName, "size"))));
251         if (child.getKeyDefinition().isEmpty()) {
252             startEvent = startUnkeyedList(classReference(valueType), "_count");
253         } else if (child.isUserOrdered()) {
254             startEvent = startOrderedMapNode(classReference(valueType), "_count");
255         } else {
256             startEvent = startMapNode(classReference(valueType), "_count");
257         }
258         builer.append(statement(startEvent));
259         builer.append(forEach(getterName, valueType, tryToUseCacheElse(CURRENT,
260             statement(staticInvokeEmitter(valueType, CURRENT)))));
261         builer.append(statement(endNode()));
262     }
263 }