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