Do not emit empty lists to NormalizedNodes
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / DataObjectStreamer.java
1 /*
2  * Copyright (c) 2019 PANTHEON.tech, 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.dom.codec.impl;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verify;
12
13 import com.google.common.annotations.Beta;
14 import com.google.common.collect.ImmutableClassToInstanceMap;
15 import java.io.IOException;
16 import java.lang.reflect.InvocationHandler;
17 import java.lang.reflect.Proxy;
18 import java.util.Collection;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Map.Entry;
22 import org.opendaylight.mdsal.binding.dom.codec.util.AugmentationReader;
23 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
24 import org.opendaylight.yangtools.yang.binding.Augmentable;
25 import org.opendaylight.yangtools.yang.binding.Augmentation;
26 import org.opendaylight.yangtools.yang.binding.BindingSerializer;
27 import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
28 import org.opendaylight.yangtools.yang.binding.DataContainer;
29 import org.opendaylight.yangtools.yang.binding.DataObject;
30 import org.opendaylight.yangtools.yang.binding.DataObjectSerializer;
31 import org.opendaylight.yangtools.yang.binding.DataObjectSerializerImplementation;
32 import org.opendaylight.yangtools.yang.binding.DataObjectSerializerRegistry;
33 import org.opendaylight.yangtools.yang.binding.Identifiable;
34 import org.opendaylight.yangtools.yang.binding.OpaqueObject;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 /**
39  * Base superclass for all concrete streamers, that is objects which are able to turn a concrete DataObject into a
40  * stream of events.
41  *
42  * @param <T> DataObject type
43  */
44 @Beta
45 public abstract class DataObjectStreamer<T extends DataObject> implements DataObjectSerializerImplementation {
46     private static final Logger LOG = LoggerFactory.getLogger(DataObjectStreamer.class);
47
48     protected DataObjectStreamer() {
49
50     }
51
52     protected static final void streamAnydata(final BindingStreamEventWriter writer, final String localName,
53             final Object value) throws IOException {
54         if (value != null && writer instanceof AnydataBindingStreamWriter) {
55             verify(value instanceof OpaqueObject, "Unexpected data %s", value);
56             ((AnydataBindingStreamWriter) writer).anydataNode(localName, (OpaqueObject<?>) value);
57         }
58     }
59
60     protected static final void streamAnyxml(final BindingStreamEventWriter writer, final String localName,
61             final Object value) throws IOException {
62         if (value != null) {
63             writer.anyxmlNode(localName, value);
64         }
65     }
66
67     protected static final void streamAugmentations(final DataObjectSerializerRegistry registry,
68             final BindingStreamEventWriter writer, final Augmentable<?> obj) throws IOException {
69         final Map<Class<? extends Augmentation<?>>, Augmentation<?>> augmentations;
70         if (registry instanceof AugmentationReader) {
71             augmentations = ((AugmentationReader) registry).getAugmentations(obj);
72         } else if (Proxy.isProxyClass(obj.getClass())) {
73             augmentations = getFromProxy(obj);
74         } else {
75             augmentations = BindingReflections.getAugmentations(obj);
76         }
77         for (final Entry<Class<? extends Augmentation<?>>, Augmentation<?>> aug : augmentations.entrySet()) {
78             emitAugmentation(aug.getKey(), aug.getValue(), writer, registry);
79         }
80     }
81
82     private static Map<Class<? extends Augmentation<?>>, Augmentation<?>> getFromProxy(final Augmentable<?> obj) {
83         final InvocationHandler proxy = Proxy.getInvocationHandler(obj);
84         if (proxy instanceof AugmentationReader) {
85             return ((AugmentationReader) proxy).getAugmentations(obj);
86         }
87         return ImmutableClassToInstanceMap.of();
88     }
89
90     protected static final <C extends DataContainer> void streamChoice(final Class<C> choiceClass,
91             final DataObjectSerializerRegistry registry, final BindingStreamEventWriter writer, final C value)
92                     throws IOException {
93         if (value != null) {
94             final Class<? extends DataContainer> caseClass = value.implementedInterface();
95             writer.startChoiceNode(choiceClass, BindingStreamEventWriter.UNKNOWN_SIZE);
96             final DataObjectSerializer caseStreamer = registry.getSerializer(caseClass.asSubclass(DataObject.class));
97             if (caseStreamer != null) {
98                 if (tryCache(writer, (DataObject) value)) {
99                     caseStreamer.serialize((DataObject) value, writer);
100                 }
101             } else {
102                 LOG.warn("No serializer for case {} is available in registry {}", caseClass, registry);
103             }
104
105             writer.endNode();
106         }
107     }
108
109     protected static final <C extends DataObject> void streamContainer(final DataObjectStreamer<C> childStreamer,
110             final DataObjectSerializerRegistry registry, final BindingStreamEventWriter writer, final C value)
111                     throws IOException {
112         if (value != null && tryCache(writer, value)) {
113             childStreamer.serialize(registry, value, writer);
114         }
115     }
116
117     protected static final void streamLeaf(final BindingStreamEventWriter writer, final String localName,
118             final Object value) throws IOException {
119         if (value != null) {
120             writer.leafNode(localName, value);
121         }
122     }
123
124     protected static final void streamLeafList(final BindingStreamEventWriter writer, final String localName,
125             final List<?> value) throws IOException {
126         if (value != null) {
127             writer.startLeafSet(localName, value.size());
128             commonStreamLeafset(writer, value);
129         }
130     }
131
132     protected static final void streamOrderedLeafList(final BindingStreamEventWriter writer,
133             final String localName, final List<?> value) throws IOException {
134         if (value != null) {
135             writer.startOrderedLeafSet(localName, value.size());
136             commonStreamLeafset(writer, value);
137         }
138     }
139
140     protected static final <E extends DataObject> void streamList(final Class<E> childClass,
141             final DataObjectStreamer<E> childStreamer, final DataObjectSerializerRegistry registry,
142             final BindingStreamEventWriter writer, final List<? extends E> value) throws IOException {
143         final int size = nullSize(value);
144         if (size != 0) {
145             writer.startUnkeyedList(childClass, size);
146             commonStreamList(registry, writer, childStreamer, value);
147         }
148     }
149
150     protected static final <E extends DataObject & Identifiable<?>> void streamMap(final Class<E> childClass,
151             final DataObjectStreamer<E> childStreamer, final DataObjectSerializerRegistry registry,
152             final BindingStreamEventWriter writer, final List<? extends E> value) throws IOException {
153         final int size = nullSize(value);
154         if (size != 0) {
155             writer.startMapNode(childClass, size);
156             commonStreamList(registry, writer, childStreamer, value);
157         }
158     }
159
160     protected static final <E extends DataObject & Identifiable<?>> void streamOrderedMap(final Class<E> childClass,
161             final DataObjectStreamer<E> childStreamer, final DataObjectSerializerRegistry registry,
162             final BindingStreamEventWriter writer, final List<? extends E> value) throws IOException {
163         final int size = nullSize(value);
164         if (size != 0) {
165             writer.startOrderedMapNode(childClass, size);
166             commonStreamList(registry, writer, childStreamer, value);
167         }
168     }
169
170     private static <E extends DataObject> void commonStreamList(final DataObjectSerializerRegistry registry,
171             final BindingStreamEventWriter writer, final DataObjectStreamer<E> childStreamer,
172             final Collection<? extends E> value) throws IOException {
173
174         for (E entry : value) {
175             if (tryCache(writer, entry)) {
176                 childStreamer.serialize(registry, entry, writer);
177             }
178         }
179         writer.endNode();
180     }
181
182     private static void commonStreamLeafset(final BindingStreamEventWriter writer, final List<?> value)
183             throws IOException {
184         for (Object entry : value) {
185             writer.leafSetEntryNode(entry);
186         }
187         writer.endNode();
188     }
189
190     @SuppressWarnings("rawtypes")
191     private static void emitAugmentation(final Class type, final Augmentation<?> value,
192             final BindingStreamEventWriter writer, final DataObjectSerializerRegistry registry) throws IOException {
193         /*
194          * Binding Specification allowed to insert augmentation with null for
195          * value, which effectively could be used to remove augmentation
196          * from builder / DTO.
197          */
198         if (value != null) {
199             checkArgument(value instanceof DataObject);
200             @SuppressWarnings("unchecked")
201             final DataObjectSerializer serializer = registry.getSerializer(type);
202             if (serializer != null) {
203                 serializer.serialize((DataObject) value, writer);
204             } else {
205                 LOG.warn("DataObjectSerializer is not present for {} in registry {}", type, registry);
206             }
207         }
208     }
209
210     @SuppressWarnings("unchecked")
211     private static <T extends DataObject> boolean tryCache(final BindingStreamEventWriter writer, final T value) {
212         return writer instanceof BindingSerializer ? ((BindingSerializer<?, T>) writer).serialize(value) == null : true;
213     }
214
215     private static int nullSize(final List<?> list) {
216         return list == null ? 0 : list.size();
217     }
218 }