Binding v2 DOM Codec - generator - SPI - part 2
[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.model.api.AnyXmlSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
32 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
37 import org.opendaylight.yangtools.yang.model.api.TypedSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
39 import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 @Beta
44 public abstract class AbstractDataNodeContainerSerializerSource extends AbstractTreeNodeSerializerSource {
45
46     private static final Logger LOG = LoggerFactory.getLogger(AbstractDataNodeContainerSerializerSource.class);
47
48     protected static final String INPUT = "_input";
49     private static final String CHOICE_PREFIX = "CHOICE_";
50
51     private final DataNodeContainer schemaNode;
52     private final GeneratedType dtoType;
53
54     public AbstractDataNodeContainerSerializerSource(final AbstractGenerator generator, final GeneratedType type,
55             final DataNodeContainer node) {
56         super(generator);
57         this.dtoType = Preconditions.checkNotNull(type);
58         this.schemaNode = Preconditions.checkNotNull(node);
59     }
60
61     /**
62      * Return the character sequence which should be used for start event.
63      *
64      * @return Start event character sequence
65      */
66     protected abstract CharSequence emitStartEvent();
67
68     @Override
69     public CharSequence getSerializerBody() {
70         final StringBuilder b = new StringBuilder();
71         b.append("{\n");
72         b.append(statement(assign(TreeNodeSerializerRegistry.class.getName(), REGISTRY, "$1")));
73         b.append(statement(assign(dtoType.getFullyQualifiedName(), INPUT,
74                 cast(dtoType.getFullyQualifiedName(), "$2"))));
75         b.append(statement(assign(BindingStreamEventWriter.class.getName(), STREAM, cast(BindingStreamEventWriter.class.getName(), "$3"))));
76         b.append(statement(assign(BindingSerializer.class.getName(), SERIALIZER, null)));
77         b.append("if (");
78         b.append(STREAM);
79         b.append(" instanceof ");
80         b.append(BindingSerializer.class.getName());
81         b.append(") {");
82         b.append(statement(assign(SERIALIZER, cast(BindingSerializer.class.getName(), STREAM))));
83         b.append('}');
84         b.append(statement(emitStartEvent()));
85
86         emitBody(b);
87         emitAfterBody(b);
88         b.append(statement(endNode()));
89         b.append(statement("return null"));
90         b.append('}');
91         return b;
92     }
93
94     /**
95      * Allows for customization of emitting code, which is processed after
96      * normal DataNodeContainer body. Ideal for augmentations or others.
97      */
98     protected void emitAfterBody(final StringBuilder b) {
99     }
100
101     private static Map<String, Type> collectAllProperties(final GeneratedType type, final Map<String, Type> hashMap) {
102         for (final MethodSignature definition : type.getMethodDefinitions()) {
103             hashMap.put(definition.getName(), definition.getReturnType());
104         }
105         for (final Type parent : type.getImplements()) {
106             if (parent instanceof GeneratedType) {
107                 collectAllProperties((GeneratedType) parent, hashMap);
108             }
109         }
110         return hashMap;
111     }
112
113     private static String getGetterName(final DataSchemaNode node) {
114         final TypeDefinition<?> type;
115         if (node instanceof TypedSchemaNode) {
116             type = ((TypedSchemaNode) node).getType();
117         } else {
118             type = null;
119         }
120
121         final String prefix;
122         if (type instanceof BooleanTypeDefinition || type instanceof EmptyTypeDefinition) {
123             prefix = "is";
124         } else {
125             prefix = "get";
126         }
127         return prefix + JavaIdentifierNormalizer.normalizeSpecificIdentifier(node.getQName().getLocalName(), JavaIdentifier.CLASS);
128     }
129
130     private void emitBody(final StringBuilder b) {
131         final Map<String, Type> getterToType = collectAllProperties(dtoType, new HashMap<String, Type>());
132         for (final DataSchemaNode schemaChild : schemaNode.getChildNodes()) {
133             if (!schemaChild.isAugmenting()) {
134                 final String getter = getGetterName(schemaChild);
135                 final Type childType = getterToType.get(getter);
136                 if (childType == null) {
137                     // FIXME AnyXml nodes are ignored, since their type cannot be found in generated bindnig
138                     // Bug-706 https://bugs.opendaylight.org/show_bug.cgi?id=706
139                     if (schemaChild instanceof AnyXmlSchemaNode) {
140                         LOG.warn("Node {} will be ignored. AnyXml is not yet supported from binding aware code." +
141                                 "Binding Independent code can be used to serialize anyXml nodes.", schemaChild.getPath());
142                         continue;
143                     }
144
145                     throw new IllegalStateException(
146                         String.format("Unable to find type for child node %s. Expected child nodes: %s",
147                             schemaChild.getPath(), getterToType));
148                 }
149                 emitChild(b, getter, childType, schemaChild);
150             }
151         }
152     }
153
154     private void emitChild(final StringBuilder b, final String getterName, final Type childType,
155             final DataSchemaNode schemaChild) {
156         b.append(statement(assign(childType, getterName, cast(childType, invoke(INPUT, getterName)))));
157
158         b.append("if (").append(getterName).append(" != null) {\n");
159         emitChildInner(b, getterName, childType, schemaChild);
160         b.append("}\n");
161     }
162
163     private void emitChildInner(final StringBuilder b, final String getterName, final Type childType,
164             final DataSchemaNode child) {
165         if (child instanceof LeafSchemaNode) {
166             b.append(statement(leafNode(child.getQName().getLocalName(), getterName)));
167         } else if (child instanceof AnyXmlSchemaNode) {
168             b.append(statement(anyxmlNode(child.getQName().getLocalName(), getterName)));
169         } else if (child instanceof LeafListSchemaNode) {
170             final CharSequence startEvent;
171             if (((LeafListSchemaNode) child).isUserOrdered()) {
172                 startEvent = startOrderedLeafSet(child.getQName().getLocalName(),invoke(getterName, "size"));
173             } else {
174                 startEvent = startLeafSet(child.getQName().getLocalName(),invoke(getterName, "size"));
175             }
176             b.append(statement(startEvent));
177             final Type valueType = ((ParameterizedType) childType).getActualTypeArguments()[0];
178             b.append(forEach(getterName, valueType, statement(leafSetEntryNode(CURRENT))));
179             b.append(statement(endNode()));
180         } else if (child instanceof ListSchemaNode) {
181             final Type valueType = ((ParameterizedType) childType).getActualTypeArguments()[0];
182             final ListSchemaNode casted = (ListSchemaNode) child;
183             emitList(b, getterName, valueType, casted);
184         } else if (child instanceof ContainerSchemaNode) {
185             b.append(tryToUseCacheElse(getterName,statement(staticInvokeEmitter(childType, getterName))));
186         } else if (child instanceof ChoiceSchemaNode) {
187             final String propertyName = CHOICE_PREFIX + childType.getName();
188             staticConstant(propertyName, TreeNodeSerializerImplementation.class, ChoiceDispatchSerializer.from(loadClass(childType)));
189             b.append(tryToUseCacheElse(getterName,statement(invoke(propertyName, StreamWriterGenerator.SERIALIZE_METHOD_NAME, REGISTRY, cast(TreeNode.class.getName(),getterName), STREAM))));
190         }
191     }
192
193     private static StringBuilder tryToUseCacheElse(final String getterName, final CharSequence statement) {
194         final StringBuilder b = new StringBuilder();
195
196         b.append("if ( ");
197         b.append(SERIALIZER).append("== null || ");
198         b.append(invoke(SERIALIZER, "serialize", getterName)).append("== null");
199         b.append(") {");
200         b.append(statement);
201         b.append('}');
202         return b;
203     }
204
205     private void emitList(final StringBuilder b, final String getterName, final Type valueType,
206             final ListSchemaNode child) {
207         final CharSequence startEvent;
208
209         b.append(statement(assign("int", "_count", invoke(getterName, "size"))));
210         if (child.getKeyDefinition().isEmpty()) {
211             startEvent = startUnkeyedList(classReference(valueType), "_count");
212         } else if (child.isUserOrdered()) {
213             startEvent = startOrderedMapNode(classReference(valueType), "_count");
214         } else {
215             startEvent = startMapNode(classReference(valueType), "_count");
216         }
217         b.append(statement(startEvent));
218         b.append(forEach(getterName, valueType, tryToUseCacheElse(CURRENT,statement(staticInvokeEmitter(valueType, CURRENT)))));
219         b.append(statement(endNode()));
220     }
221 }