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