Adjust to yangtools-2.0.0 changes
[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.TypedDataSchemaNode;
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 builder = new StringBuilder();
71         builder.append("{\n");
72         builder.append(statement(assign(TreeNodeSerializerRegistry.class.getName(), REGISTRY, "$1")));
73         builder.append(statement(assign(dtoType.getFullyQualifiedName(), INPUT,
74                 cast(dtoType.getFullyQualifiedName(), "$2"))));
75         builder.append(statement(assign(BindingStreamEventWriter.class.getName(), STREAM,
76             cast(BindingStreamEventWriter.class.getName(), "$3"))));
77         builder.append(statement(assign(BindingSerializer.class.getName(), SERIALIZER, null)));
78         builder.append("if (");
79         builder.append(STREAM);
80         builder.append(" instanceof ");
81         builder.append(BindingSerializer.class.getName());
82         builder.append(") {");
83         builder.append(statement(assign(SERIALIZER, cast(BindingSerializer.class.getName(), STREAM))));
84         builder.append('}');
85         builder.append(statement(emitStartEvent()));
86
87         emitBody(builder);
88         emitAfterBody(builder);
89         builder.append(statement(endNode()));
90         builder.append(statement("return null"));
91         builder.append('}');
92         return builder;
93     }
94
95     /**
96      * Allows for customization of emitting code, which is processed after
97      * normal DataNodeContainer body. Ideal for augmentations or others.
98      */
99     protected void emitAfterBody(final StringBuilder builder) {
100     }
101
102     private static Map<String, Type> collectAllProperties(final GeneratedType type, final Map<String, Type> hashMap) {
103         for (final MethodSignature definition : type.getMethodDefinitions()) {
104             hashMap.put(definition.getName(), definition.getReturnType());
105         }
106         for (final Type parent : type.getImplements()) {
107             if (parent instanceof GeneratedType) {
108                 collectAllProperties((GeneratedType) parent, hashMap);
109             }
110         }
111         return hashMap;
112     }
113
114     private static String getGetterName(final DataSchemaNode node) {
115         final TypeDefinition<?> type;
116         if (node instanceof TypedDataSchemaNode) {
117             type = ((TypedDataSchemaNode) node).getType();
118         } else {
119             type = null;
120         }
121
122         final String prefix;
123         if (type instanceof BooleanTypeDefinition || type instanceof EmptyTypeDefinition) {
124             prefix = "is";
125         } else {
126             prefix = "get";
127         }
128         return prefix + JavaIdentifierNormalizer.normalizeSpecificIdentifier(node.getQName().getLocalName(),
129             JavaIdentifier.CLASS);
130     }
131
132     private void emitBody(final StringBuilder builder) {
133         final Map<String, Type> getterToType = collectAllProperties(dtoType, new HashMap<String, Type>());
134         for (final DataSchemaNode schemaChild : schemaNode.getChildNodes()) {
135             if (!schemaChild.isAugmenting()) {
136                 final String getter = getGetterName(schemaChild);
137                 final Type childType = getterToType.get(getter);
138                 if (childType == null) {
139                     // FIXME AnyXml nodes are ignored, since their type cannot be found in generated bindnig
140                     // Bug-706 https://bugs.opendaylight.org/show_bug.cgi?id=706
141                     if (schemaChild instanceof AnyXmlSchemaNode) {
142                         LOG.warn("Node {} will be ignored. AnyXml is not yet supported from binding aware code."
143                             + "Binding Independent code can be used to serialize anyXml nodes.", schemaChild.getPath());
144                         continue;
145                     }
146
147                     throw new IllegalStateException(
148                         String.format("Unable to find type for child node %s. Expected child nodes: %s",
149                             schemaChild.getPath(), getterToType));
150                 }
151                 emitChild(builder, getter, childType, schemaChild);
152             }
153         }
154     }
155
156     private void emitChild(final StringBuilder builder, final String getterName, final Type childType,
157             final DataSchemaNode schemaChild) {
158         builder.append(statement(assign(childType, getterName, cast(childType, invoke(INPUT, getterName)))));
159
160         builder.append("if (").append(getterName).append(" != null) {\n");
161         emitChildInner(builder, getterName, childType, schemaChild);
162         builder.append("}\n");
163     }
164
165     private void emitChildInner(final StringBuilder builder, final String getterName, final Type childType,
166             final DataSchemaNode child) {
167         if (child instanceof LeafSchemaNode) {
168             builder.append(statement(leafNode(child.getQName().getLocalName(), getterName)));
169         } else if (child instanceof AnyXmlSchemaNode) {
170             builder.append(statement(anyxmlNode(child.getQName().getLocalName(), getterName)));
171         } else if (child instanceof LeafListSchemaNode) {
172             final CharSequence startEvent;
173             if (((LeafListSchemaNode) child).isUserOrdered()) {
174                 startEvent = startOrderedLeafSet(child.getQName().getLocalName(),
175                     invoke(getterName, "size"));
176             } else {
177                 startEvent = startLeafSet(child.getQName().getLocalName(),invoke(getterName, "size"));
178             }
179             builder.append(statement(startEvent));
180             final Type valueType = ((ParameterizedType) childType).getActualTypeArguments()[0];
181             builder.append(forEach(getterName, valueType, statement(leafSetEntryNode(CURRENT))));
182             builder.append(statement(endNode()));
183         } else if (child instanceof ListSchemaNode) {
184             final Type valueType = ((ParameterizedType) childType).getActualTypeArguments()[0];
185             final ListSchemaNode casted = (ListSchemaNode) child;
186             emitList(builder, getterName, valueType, casted);
187         } else if (child instanceof ContainerSchemaNode) {
188             builder.append(tryToUseCacheElse(getterName,statement(staticInvokeEmitter(childType, getterName))));
189         } else if (child instanceof ChoiceSchemaNode) {
190             final String propertyName = CHOICE_PREFIX + childType.getName();
191             staticConstant(propertyName, TreeNodeSerializerImplementation.class,
192                 ChoiceDispatchSerializer.from(loadClass(childType)));
193             builder.append(tryToUseCacheElse(getterName,statement(invoke(propertyName,
194                 StreamWriterGenerator.SERIALIZE_METHOD_NAME, REGISTRY, cast(TreeNode.class.getName(),getterName),
195                 STREAM))));
196         }
197     }
198
199     private static StringBuilder tryToUseCacheElse(final String getterName, final CharSequence statement) {
200         final StringBuilder b = new StringBuilder();
201
202         b.append("if ( ");
203         b.append(SERIALIZER).append("== null || ");
204         b.append(invoke(SERIALIZER, "serialize", getterName)).append("== null");
205         b.append(") {");
206         b.append(statement);
207         b.append('}');
208         return b;
209     }
210
211     private void emitList(final StringBuilder builer, final String getterName, final Type valueType,
212             final ListSchemaNode child) {
213         final CharSequence startEvent;
214
215         builer.append(statement(assign("int", "_count", invoke(getterName, "size"))));
216         if (child.getKeyDefinition().isEmpty()) {
217             startEvent = startUnkeyedList(classReference(valueType), "_count");
218         } else if (child.isUserOrdered()) {
219             startEvent = startOrderedMapNode(classReference(valueType), "_count");
220         } else {
221             startEvent = startMapNode(classReference(valueType), "_count");
222         }
223         builer.append(statement(startEvent));
224         builer.append(forEach(getterName, valueType, tryToUseCacheElse(CURRENT,
225             statement(staticInvokeEmitter(valueType, CURRENT)))));
226         builer.append(statement(endNode()));
227     }
228 }