2 * Copyright (c) 2019 Pantheon Technologies, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.data.codec.xml;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.base.Strings;
15 import java.util.Map.Entry;
17 import java.util.concurrent.ConcurrentHashMap;
18 import javax.xml.XMLConstants;
19 import javax.xml.namespace.NamespaceContext;
20 import javax.xml.stream.XMLStreamConstants;
21 import javax.xml.stream.XMLStreamException;
22 import javax.xml.stream.XMLStreamWriter;
23 import org.opendaylight.yangtools.yang.common.QName;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
28 * The sole implementation of {@link ValueWriter}, tasked with synchronizing access to XMLStreamWriter state. The only
29 * class referencing this class should be {@link XMLStreamNormalizedNodeStreamWriter}.
31 final class StreamWriterFacade extends ValueWriter {
32 private static final Logger LOG = LoggerFactory.getLogger(StreamWriterFacade.class);
33 private static final Set<String> BROKEN_NAMESPACES = ConcurrentHashMap.newKeySet();
35 private final XMLStreamWriter writer;
36 private final RandomPrefix prefixes;
38 // QName of an element we delayed emitting. This only happens if it is a naked element, without any attributes,
39 // namespace declarations or value.
40 private QName openElement;
42 StreamWriterFacade(final XMLStreamWriter writer) {
43 this.writer = requireNonNull(writer);
44 prefixes = new RandomPrefix(writer.getNamespaceContext());
48 void writeCharacters(final String text) throws XMLStreamException {
49 if (!Strings.isNullOrEmpty(text)) {
51 writer.writeCharacters(text);
56 void writeNamespace(final String prefix, final String namespaceURI) throws XMLStreamException {
58 writer.writeNamespace(prefix, namespaceURI);
62 void writeAttribute(final String localName, final String value) throws XMLStreamException {
64 writer.writeAttribute(localName, value);
68 void writeAttribute(final String prefix, final String namespaceURI, final String localName, final String value)
69 throws XMLStreamException {
71 writer.writeAttribute(prefix, namespaceURI, localName, value);
75 NamespaceContext getNamespaceContext() {
76 // Accessing namespace context is okay, because a delayed element is known to have no effect on the result
77 return writer.getNamespaceContext();
80 private void flushElement() throws XMLStreamException {
81 if (openElement != null) {
82 writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, openElement.getLocalName(),
83 openElement.getNamespace().toString());
88 void writeStartElement(final QName qname) throws XMLStreamException {
91 final String ns = qname.getNamespace().toString();
92 final NamespaceContext context = writer.getNamespaceContext();
93 final boolean reuseNamespace;
94 if (context != null) {
95 reuseNamespace = ns.equals(context.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX));
97 reuseNamespace = XMLConstants.DEFAULT_NS_PREFIX.equals(writer.getPrefix(ns));
100 if (!reuseNamespace) {
101 writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, qname.getLocalName(), ns);
102 writer.writeDefaultNamespace(ns);
108 void writeEndElement() throws XMLStreamException {
109 if (openElement != null) {
110 writer.writeEmptyElement(XMLConstants.DEFAULT_NS_PREFIX, openElement.getLocalName(),
111 openElement.getNamespace().toString());
114 writer.writeEndElement();
118 void writeAttributes(final Map<QName, String> attributes) throws XMLStreamException {
120 for (final Entry<QName, String> entry : attributes.entrySet()) {
121 final QName qname = entry.getKey();
122 final String namespace = qname.getNamespace().toString();
124 if (!Strings.isNullOrEmpty(namespace)) {
125 final String prefix = getPrefix(qname.getNamespace(), namespace);
126 writer.writeAttribute(prefix, namespace, qname.getLocalName(), entry.getValue());
128 writer.writeAttribute(qname.getLocalName(), entry.getValue());
133 private String getPrefix(final URI uri, final String str) throws XMLStreamException {
134 final String prefix = writer.getPrefix(str);
135 if (prefix != null) {
139 // This is needed to recover from attributes emitted while the namespace was not declared. Ordinarily
140 // attribute namespaces would be bound in the writer, so the resulting XML is efficient, but we cannot rely
141 // on that having been done.
142 if (BROKEN_NAMESPACES.add(str)) {
143 LOG.info("Namespace {} was not bound, please fix the caller", str, new Throwable());
146 return prefixes.encodePrefix(uri);
149 void close() throws XMLStreamException {
150 // Mighty careful stepping here, we must end up closing the writer
151 XMLStreamException failure = null;
154 } catch (XMLStreamException e) {
160 } catch (XMLStreamException e) {
161 if (failure != null) {
162 failure.addSuppressed(e);
170 void flush() throws XMLStreamException {
175 void writeStreamReader(final DOMSourceXMLStreamReader reader) throws XMLStreamException {
178 // We track depth, as we do not want to output the top-most element
180 while (reader.hasNext()) {
181 final int event = reader.next();
183 case XMLStreamConstants.START_ELEMENT:
185 forwardStartElement(reader);
189 case XMLStreamConstants.END_ELEMENT:
191 writer.writeEndElement();
195 case XMLStreamConstants.PROCESSING_INSTRUCTION:
196 forwardProcessingInstruction(reader);
198 case XMLStreamConstants.CHARACTERS:
199 writer.writeCharacters(reader.getText());
201 case XMLStreamConstants.COMMENT:
202 writer.writeComment(reader.getText());
204 case XMLStreamConstants.SPACE:
205 // Ignore insignificant whitespace
207 case XMLStreamConstants.START_DOCUMENT:
208 case XMLStreamConstants.END_DOCUMENT:
209 // We are embedded: ignore start/end document events
211 case XMLStreamConstants.ENTITY_REFERENCE:
212 writer.writeEntityRef(reader.getLocalName());
214 case XMLStreamConstants.ATTRIBUTE:
215 forwardAttributes(reader);
217 case XMLStreamConstants.DTD:
218 writer.writeDTD(reader.getText());
220 case XMLStreamConstants.CDATA:
221 writer.writeCData(reader.getText());
223 case XMLStreamConstants.NAMESPACE:
224 forwardNamespaces(reader);
226 case XMLStreamConstants.NOTATION_DECLARATION:
227 case XMLStreamConstants.ENTITY_DECLARATION:
229 throw new IllegalStateException("Unhandled event " + event);
234 private void forwardAttributes(final DOMSourceXMLStreamReader reader) throws XMLStreamException {
235 for (int i = 0; i < reader.getAttributeCount(); ++i) {
236 final String localName = reader.getAttributeLocalName(i);
237 final String value = reader.getAttributeValue(i);
238 final String prefix = reader.getAttributePrefix(i);
239 if (prefix != null) {
240 writer.writeAttribute(prefix, reader.getAttributeNamespace(i), localName, value);
242 writer.writeAttribute(localName, value);
247 private void forwardNamespaces(final DOMSourceXMLStreamReader reader) throws XMLStreamException {
248 for (int i = 0; i < reader.getNamespaceCount(); ++i) {
249 writer.writeNamespace(reader.getNamespacePrefix(i), reader.getNamespaceURI(i));
253 private void forwardProcessingInstruction(final DOMSourceXMLStreamReader reader) throws XMLStreamException {
254 final String target = reader.getPITarget();
255 final String data = reader.getPIData();
257 writer.writeProcessingInstruction(target, data);
259 writer.writeProcessingInstruction(target);
263 private void forwardStartElement(final DOMSourceXMLStreamReader reader) throws XMLStreamException {
264 final String localName = reader.getLocalName();
265 final String prefix = reader.getPrefix();
266 if (prefix != null) {
267 writer.writeStartElement(prefix, localName, reader.getNamespaceURI());
269 writer.writeStartElement(localName);
272 forwardNamespaces(reader);
273 forwardAttributes(reader);