jaxws/src/java.xml.ws/share/classes/com/sun/xml/internal/messaging/saaj/util/stax/SaajStaxWriter.java
changeset 43126 26c4004e8cc0
parent 33547 e4c76ac38b12
child 45678 65fdff10664d
equal deleted inserted replaced
43040:ab2c8b03c328 43126:26c4004e8cc0
     1 /*
     1 /*
     2  * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
     2  * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     4  *
     5  * This code is free software; you can redistribute it and/or modify it
     5  * This code is free software; you can redistribute it and/or modify it
     6  * under the terms of the GNU General Public License version 2 only, as
     6  * under the terms of the GNU General Public License version 2 only, as
     7  * published by the Free Software Foundation.  Oracle designates this
     7  * published by the Free Software Foundation.  Oracle designates this
    23  * questions.
    23  * questions.
    24  */
    24  */
    25 
    25 
    26 package com.sun.xml.internal.messaging.saaj.util.stax;
    26 package com.sun.xml.internal.messaging.saaj.util.stax;
    27 
    27 
       
    28 import java.util.Iterator;
    28 import java.util.Arrays;
    29 import java.util.Arrays;
    29 import java.util.Iterator;
    30 import java.util.List;
       
    31 import java.util.LinkedList;
    30 
    32 
    31 import javax.xml.namespace.NamespaceContext;
    33 import javax.xml.namespace.NamespaceContext;
    32 import javax.xml.namespace.QName;
    34 import javax.xml.namespace.QName;
    33 import javax.xml.soap.SOAPElement;
    35 import javax.xml.soap.SOAPElement;
    34 import javax.xml.soap.SOAPException;
    36 import javax.xml.soap.SOAPException;
    40 import org.w3c.dom.Node;
    42 import org.w3c.dom.Node;
    41 
    43 
    42 /**
    44 /**
    43  * SaajStaxWriter builds a SAAJ SOAPMessage by using XMLStreamWriter interface.
    45  * SaajStaxWriter builds a SAAJ SOAPMessage by using XMLStreamWriter interface.
    44  *
    46  *
       
    47  * <p>
       
    48  * Defers creation of SOAPElement until all the aspects of the name of the element are known.
       
    49  * In some cases, the namespace uri is indicated only by the {@link #writeNamespace(String, String)} call.
       
    50  * After opening an element ({@code writeStartElement}, {@code writeEmptyElement} methods), all attributes
       
    51  * and namespace assignments are retained within {@link DeferredElement} object ({@code deferredElement} field).
       
    52  * As soon as any other method than {@code writeAttribute}, {@code writeNamespace}, {@code writeDefaultNamespace}
       
    53  * or {@code setNamespace} is called, the contents of {@code deferredElement} is transformed into new SOAPElement
       
    54  * (which is appropriately inserted into the SOAPMessage under construction).
       
    55  * This mechanism is necessary to fix JDK-8159058 issue.
       
    56  * </p>
       
    57  *
    45  * @author shih-chang.chen@oracle.com
    58  * @author shih-chang.chen@oracle.com
    46  */
    59  */
    47 public class SaajStaxWriter implements XMLStreamWriter {
    60 public class SaajStaxWriter implements XMLStreamWriter {
    48 
    61 
    49     protected SOAPMessage soap;
    62     protected SOAPMessage soap;
    50     protected String envURI;
    63     protected String envURI;
    51     protected SOAPElement currentElement;
    64     protected SOAPElement currentElement;
       
    65     protected DeferredElement deferredElement;
    52 
    66 
    53     static final protected String Envelope = "Envelope";
    67     static final protected String Envelope = "Envelope";
    54     static final protected String Header = "Header";
    68     static final protected String Header = "Header";
    55     static final protected String Body = "Body";
    69     static final protected String Body = "Body";
    56     static final protected String xmlns = "xmlns";
    70     static final protected String xmlns = "xmlns";
    57 
    71 
    58     public SaajStaxWriter(final SOAPMessage msg, String uri) throws SOAPException {
    72     public SaajStaxWriter(final SOAPMessage msg, String uri) throws SOAPException {
    59         soap = msg;
    73         soap = msg;
    60         this.envURI = uri;
    74         this.envURI = uri;
       
    75         this.deferredElement = new DeferredElement();
    61     }
    76     }
    62 
    77 
    63     public SOAPMessage getSOAPMessage() {
    78     public SOAPMessage getSOAPMessage() {
    64         return soap;
    79         return soap;
    65     }
    80     }
    68         return soap.getSOAPPart().getEnvelope();
    83         return soap.getSOAPPart().getEnvelope();
    69     }
    84     }
    70 
    85 
    71     @Override
    86     @Override
    72     public void writeStartElement(final String localName) throws XMLStreamException {
    87     public void writeStartElement(final String localName) throws XMLStreamException {
    73         try {
    88         currentElement = deferredElement.flushTo(currentElement);
    74             currentElement = currentElement.addChildElement(localName);
    89         deferredElement.setLocalName(localName);
    75         } catch (SOAPException e) {
       
    76             throw new XMLStreamException(e);
       
    77         }
       
    78     }
    90     }
    79 
    91 
    80     @Override
    92     @Override
    81     public void writeStartElement(final String ns, final String ln) throws XMLStreamException {
    93     public void writeStartElement(final String ns, final String ln) throws XMLStreamException {
    82         writeStartElement(null, ln, ns);
    94         writeStartElement(null, ln, ns);
    83     }
    95     }
    84 
    96 
    85     @Override
    97     @Override
    86     public void writeStartElement(final String prefix, final String ln, final String ns) throws XMLStreamException {
    98     public void writeStartElement(final String prefix, final String ln, final String ns) throws XMLStreamException {
    87         try {
    99         currentElement = deferredElement.flushTo(currentElement);
    88             if (envURI.equals(ns)) {
   100 
       
   101         if (envURI.equals(ns)) {
       
   102             try {
    89                 if (Envelope.equals(ln)) {
   103                 if (Envelope.equals(ln)) {
    90                     currentElement = getEnvelope();
   104                     currentElement = getEnvelope();
    91                     fixPrefix(prefix);
   105                     fixPrefix(prefix);
    92                     return;
   106                     return;
    93                 } else if (Header.equals(ln)) {
   107                 } else if (Header.equals(ln)) {
    97                 } else if (Body.equals(ln)) {
   111                 } else if (Body.equals(ln)) {
    98                     currentElement = soap.getSOAPBody();
   112                     currentElement = soap.getSOAPBody();
    99                     fixPrefix(prefix);
   113                     fixPrefix(prefix);
   100                     return;
   114                     return;
   101                 }
   115                 }
   102             }
   116             } catch (SOAPException e) {
   103             currentElement = (prefix == null) ?
   117                 throw new XMLStreamException(e);
   104                     currentElement.addChildElement(new QName(ns, ln)) :
   118             }
   105                     currentElement.addChildElement(ln, prefix, ns);
   119 
   106         } catch (SOAPException e) {
   120         }
   107             throw new XMLStreamException(e);
   121 
   108         }
   122         deferredElement.setLocalName(ln);
       
   123         deferredElement.setNamespaceUri(ns);
       
   124         deferredElement.setPrefix(prefix);
       
   125 
   109     }
   126     }
   110 
   127 
   111     private void fixPrefix(final String prfx) throws XMLStreamException {
   128     private void fixPrefix(final String prfx) throws XMLStreamException {
   112         fixPrefix(prfx, currentElement);
   129         fixPrefix(prfx, currentElement);
   113     }
   130     }
   134         writeStartElement(null, ln, null);
   151         writeStartElement(null, ln, null);
   135     }
   152     }
   136 
   153 
   137     @Override
   154     @Override
   138     public void writeEndElement() throws XMLStreamException {
   155     public void writeEndElement() throws XMLStreamException {
       
   156         currentElement = deferredElement.flushTo(currentElement);
   139         if (currentElement != null) currentElement = currentElement.getParentElement();
   157         if (currentElement != null) currentElement = currentElement.getParentElement();
   140     }
   158     }
   141 
   159 
   142     @Override
   160     @Override
   143     public void writeEndDocument() throws XMLStreamException {
   161     public void writeEndDocument() throws XMLStreamException {
       
   162         currentElement = deferredElement.flushTo(currentElement);
   144     }
   163     }
   145 
   164 
   146     @Override
   165     @Override
   147     public void close() throws XMLStreamException {
   166     public void close() throws XMLStreamException {
   148     }
   167     }
   156         writeAttribute(null, null, ln, val);
   175         writeAttribute(null, null, ln, val);
   157     }
   176     }
   158 
   177 
   159     @Override
   178     @Override
   160     public void writeAttribute(final String prefix, final String ns, final String ln, final String value) throws XMLStreamException {
   179     public void writeAttribute(final String prefix, final String ns, final String ln, final String value) throws XMLStreamException {
   161         try {
   180         if (ns == null && prefix == null && xmlns.equals(ln)) {
   162             if (ns == null) {
   181             writeNamespace("", value);
   163                 if (prefix == null && xmlns.equals(ln)) {
   182         } else {
   164                     currentElement.addNamespaceDeclaration("", value);
   183             if (deferredElement.isInitialized()) {
   165                 } else {
   184                 deferredElement.addAttribute(prefix, ns, ln, value);
   166                     currentElement.setAttributeNS("", ln, value);
       
   167                 }
       
   168             } else {
   185             } else {
   169                 QName name = (prefix == null) ? new QName(ns, ln) : new QName(ns, ln, prefix);
   186                 addAttibuteToElement(currentElement, prefix, ns, ln, value);
   170                 currentElement.addAttribute(name, value);
   187             }
   171             }
       
   172         } catch (SOAPException e) {
       
   173             throw new XMLStreamException(e);
       
   174         }
   188         }
   175     }
   189     }
   176 
   190 
   177     @Override
   191     @Override
   178     public void writeAttribute(final String ns, final String ln, final String val) throws XMLStreamException {
   192     public void writeAttribute(final String ns, final String ln, final String val) throws XMLStreamException {
   179         writeAttribute(null, ns, ln, val);
   193         writeAttribute(null, ns, ln, val);
   180     }
   194     }
   181 
   195 
   182     @Override
   196     @Override
   183     public void writeNamespace(String prefix, final String uri) throws XMLStreamException {
   197     public void writeNamespace(String prefix, final String uri) throws XMLStreamException {
   184 
       
   185         // make prefix default if null or "xmlns" (according to javadoc)
   198         // make prefix default if null or "xmlns" (according to javadoc)
   186         if (prefix == null || "xmlns".equals(prefix)) {
   199         String thePrefix = prefix == null || "xmlns".equals(prefix) ? "" : prefix;
   187             prefix = "";
   200         if (deferredElement.isInitialized()) {
   188         }
   201             deferredElement.addNamespaceDeclaration(thePrefix, uri);
   189 
   202         } else {
   190         try {
   203             try {
   191             currentElement.addNamespaceDeclaration(prefix, uri);
   204                 currentElement.addNamespaceDeclaration(thePrefix, uri);
   192         } catch (SOAPException e) {
   205             } catch (SOAPException e) {
   193             throw new XMLStreamException(e);
   206                 throw new XMLStreamException(e);
       
   207             }
   194         }
   208         }
   195     }
   209     }
   196 
   210 
   197     @Override
   211     @Override
   198     public void writeDefaultNamespace(final String uri) throws XMLStreamException {
   212     public void writeDefaultNamespace(final String uri) throws XMLStreamException {
   199         writeNamespace("", uri);
   213         writeNamespace("", uri);
   200     }
   214     }
   201 
   215 
   202     @Override
   216     @Override
   203     public void writeComment(final String data) throws XMLStreamException {
   217     public void writeComment(final String data) throws XMLStreamException {
       
   218         currentElement = deferredElement.flushTo(currentElement);
   204         Comment c = soap.getSOAPPart().createComment(data);
   219         Comment c = soap.getSOAPPart().createComment(data);
   205         currentElement.appendChild(c);
   220         currentElement.appendChild(c);
   206     }
   221     }
   207 
   222 
   208     @Override
   223     @Override
   209     public void writeProcessingInstruction(final String target) throws XMLStreamException {
   224     public void writeProcessingInstruction(final String target) throws XMLStreamException {
       
   225         currentElement = deferredElement.flushTo(currentElement);
   210         Node n = soap.getSOAPPart().createProcessingInstruction(target, "");
   226         Node n = soap.getSOAPPart().createProcessingInstruction(target, "");
   211         currentElement.appendChild(n);
   227         currentElement.appendChild(n);
   212     }
   228     }
   213 
   229 
   214     @Override
   230     @Override
   215     public void writeProcessingInstruction(final String target, final String data) throws XMLStreamException {
   231     public void writeProcessingInstruction(final String target, final String data) throws XMLStreamException {
       
   232         currentElement = deferredElement.flushTo(currentElement);
   216         Node n = soap.getSOAPPart().createProcessingInstruction(target, data);
   233         Node n = soap.getSOAPPart().createProcessingInstruction(target, data);
   217         currentElement.appendChild(n);
   234         currentElement.appendChild(n);
   218     }
   235     }
   219 
   236 
   220     @Override
   237     @Override
   221     public void writeCData(final String data) throws XMLStreamException {
   238     public void writeCData(final String data) throws XMLStreamException {
       
   239         currentElement = deferredElement.flushTo(currentElement);
   222         Node n = soap.getSOAPPart().createCDATASection(data);
   240         Node n = soap.getSOAPPart().createCDATASection(data);
   223         currentElement.appendChild(n);
   241         currentElement.appendChild(n);
   224     }
   242     }
   225 
   243 
   226     @Override
   244     @Override
   227     public void writeDTD(final String dtd) throws XMLStreamException {
   245     public void writeDTD(final String dtd) throws XMLStreamException {
   228         //TODO ... Don't do anything here
   246         currentElement = deferredElement.flushTo(currentElement);
   229     }
   247     }
   230 
   248 
   231     @Override
   249     @Override
   232     public void writeEntityRef(final String name) throws XMLStreamException {
   250     public void writeEntityRef(final String name) throws XMLStreamException {
       
   251         currentElement = deferredElement.flushTo(currentElement);
   233         Node n = soap.getSOAPPart().createEntityReference(name);
   252         Node n = soap.getSOAPPart().createEntityReference(name);
   234         currentElement.appendChild(n);
   253         currentElement.appendChild(n);
   235     }
   254     }
   236 
   255 
   237     @Override
   256     @Override
   255         }
   274         }
   256     }
   275     }
   257 
   276 
   258     @Override
   277     @Override
   259     public void writeCharacters(final String text) throws XMLStreamException {
   278     public void writeCharacters(final String text) throws XMLStreamException {
       
   279         currentElement = deferredElement.flushTo(currentElement);
   260         try {
   280         try {
   261             currentElement.addTextNode(text);
   281             currentElement.addTextNode(text);
   262         } catch (SOAPException e) {
   282         } catch (SOAPException e) {
   263             throw new XMLStreamException(e);
   283             throw new XMLStreamException(e);
   264         }
   284         }
   265     }
   285     }
   266 
   286 
   267     @Override
   287     @Override
   268     public void writeCharacters(final char[] text, final int start, final int len) throws XMLStreamException {
   288     public void writeCharacters(final char[] text, final int start, final int len) throws XMLStreamException {
       
   289         currentElement = deferredElement.flushTo(currentElement);
   269         char[] chr = (start == 0 && len == text.length) ? text : Arrays.copyOfRange(text, start, start + len);
   290         char[] chr = (start == 0 && len == text.length) ? text : Arrays.copyOfRange(text, start, start + len);
   270         try {
   291         try {
   271             currentElement.addTextNode(new String(chr));
   292             currentElement.addTextNode(new String(chr));
   272         } catch (SOAPException e) {
   293         } catch (SOAPException e) {
   273             throw new XMLStreamException(e);
   294             throw new XMLStreamException(e);
   279         return currentElement.lookupPrefix(uri);
   300         return currentElement.lookupPrefix(uri);
   280     }
   301     }
   281 
   302 
   282     @Override
   303     @Override
   283     public void setPrefix(final String prefix, final String uri) throws XMLStreamException {
   304     public void setPrefix(final String prefix, final String uri) throws XMLStreamException {
   284         try {
   305         // TODO: this in fact is not what would be expected from XMLStreamWriter
   285             this.currentElement.addNamespaceDeclaration(prefix, uri);
   306         //       (e.g. XMLStreamWriter for writing to output stream does not write anything as result of
   286         } catch (SOAPException e) {
   307         //        this method, it just rememebers that given prefix is associated with the given uri
   287             throw new XMLStreamException(e);
   308         //        for the scope; to actually declare the prefix assignment in the resulting XML, one
       
   309         //        needs to call writeNamespace(...) method
       
   310         // Kept for backwards compatibility reasons - this might be worth of further investigation.
       
   311         if (deferredElement.isInitialized()) {
       
   312             deferredElement.addNamespaceDeclaration(prefix, uri);
       
   313         } else {
       
   314             throw new XMLStreamException("Namespace not associated with any element");
   288         }
   315         }
   289     }
   316     }
   290 
   317 
   291     @Override
   318     @Override
   292     public void setDefaultNamespace(final String uri) throws XMLStreamException {
   319     public void setDefaultNamespace(final String uri) throws XMLStreamException {
   329                     public void remove() {}
   356                     public void remove() {}
   330                 };
   357                 };
   331             }
   358             }
   332         };
   359         };
   333     }
   360     }
       
   361 
       
   362     static void addAttibuteToElement(SOAPElement element, String prefix, String ns, String ln, String value)
       
   363             throws XMLStreamException {
       
   364         try {
       
   365             if (ns == null) {
       
   366                 element.setAttributeNS("", ln, value);
       
   367             } else {
       
   368                 QName name = prefix == null ? new QName(ns, ln) : new QName(ns, ln, prefix);
       
   369                 element.addAttribute(name, value);
       
   370             }
       
   371         } catch (SOAPException e) {
       
   372             throw new XMLStreamException(e);
       
   373         }
       
   374     }
       
   375 
       
   376     /**
       
   377      * Holds details of element that needs to be deferred in order to manage namespace assignments correctly.
       
   378      *
       
   379      * <p>
       
   380      * An instance of can be set with all the aspects of the element name (local name, prefix, namespace uri).
       
   381      * Attributes and namespace declarations (special case of attribute) can be added.
       
   382      * Namespace declarations are handled so that the element namespace is updated if it is implied by the namespace
       
   383      * declaration and the namespace was not set to non-{@code null} value previously.
       
   384      * </p>
       
   385      *
       
   386      * <p>
       
   387      * The state of this object can be {@link #flushTo(SOAPElement) flushed} to SOAPElement - new SOAPElement will
       
   388      * be added a child element; the new element will have exactly the shape as represented by the state of this
       
   389      * object. Note that the {@link #flushTo(SOAPElement)} method does nothing
       
   390      * (and returns the argument immediately) if the state of this object is not initialized
       
   391      * (i.e. local name is null).
       
   392      * </p>
       
   393      *
       
   394      * @author ondrej.cerny@oracle.com
       
   395      */
       
   396     static class DeferredElement {
       
   397         private String prefix;
       
   398         private String localName;
       
   399         private String namespaceUri;
       
   400         private final List<NamespaceDeclaration> namespaceDeclarations;
       
   401         private final List<AttributeDeclaration> attributeDeclarations;
       
   402 
       
   403         DeferredElement() {
       
   404             this.namespaceDeclarations = new LinkedList<NamespaceDeclaration>();
       
   405             this.attributeDeclarations = new LinkedList<AttributeDeclaration>();
       
   406             reset();
       
   407         }
       
   408 
       
   409 
       
   410         /**
       
   411          * Set prefix of the element.
       
   412          * @param prefix namespace prefix
       
   413          */
       
   414         public void setPrefix(final String prefix) {
       
   415             this.prefix = prefix;
       
   416         }
       
   417 
       
   418         /**
       
   419          * Set local name of the element.
       
   420          *
       
   421          * <p>
       
   422          *     This method initializes the element.
       
   423          * </p>
       
   424          *
       
   425          * @param localName local name {@code not null}
       
   426          */
       
   427         public void setLocalName(final String localName) {
       
   428             if (localName == null) {
       
   429                 throw new IllegalArgumentException("localName can not be null");
       
   430             }
       
   431             this.localName = localName;
       
   432         }
       
   433 
       
   434         /**
       
   435          * Set namespace uri.
       
   436          *
       
   437          * @param namespaceUri namespace uri
       
   438          */
       
   439         public void setNamespaceUri(final String namespaceUri) {
       
   440             this.namespaceUri = namespaceUri;
       
   441         }
       
   442 
       
   443         /**
       
   444          * Adds namespace prefix assignment to the element.
       
   445          *
       
   446          * @param prefix prefix (not {@code null})
       
   447          * @param namespaceUri namespace uri
       
   448          */
       
   449         public void addNamespaceDeclaration(final String prefix, final String namespaceUri) {
       
   450             if (null == this.namespaceUri && null != namespaceUri && prefix.equals(emptyIfNull(this.prefix))) {
       
   451                 this.namespaceUri = namespaceUri;
       
   452             }
       
   453             this.namespaceDeclarations.add(new NamespaceDeclaration(prefix, namespaceUri));
       
   454         }
       
   455 
       
   456         /**
       
   457          * Adds attribute to the element.
       
   458          * @param prefix prefix
       
   459          * @param ns namespace
       
   460          * @param ln local name
       
   461          * @param value value
       
   462          */
       
   463         public void addAttribute(final String prefix, final String ns, final String ln, final String value) {
       
   464             if (ns == null && prefix == null && xmlns.equals(ln)) {
       
   465                 this.addNamespaceDeclaration(prefix, value);
       
   466             } else {
       
   467                 this.attributeDeclarations.add(new AttributeDeclaration(prefix, ns, ln, value));
       
   468             }
       
   469         }
       
   470 
       
   471         /**
       
   472          * Flushes state of this element to the {@code target} element.
       
   473          *
       
   474          * <p>
       
   475          * If this element is initialized then it is added with all the namespace declarations and attributes
       
   476          * to the {@code target} element as a child. The state of this element is reset to uninitialized.
       
   477          * The newly added element object is returned.
       
   478          * </p>
       
   479          * <p>
       
   480          * If this element is not initialized then the {@code target} is returned immediately, nothing else is done.
       
   481          * </p>
       
   482          *
       
   483          * @param target target element
       
   484          * @return {@code target} or new element
       
   485          * @throws XMLStreamException on error
       
   486          */
       
   487         public SOAPElement flushTo(final SOAPElement target) throws XMLStreamException {
       
   488             try {
       
   489                 if (this.localName != null) {
       
   490                     // add the element appropriately (based on namespace declaration)
       
   491                     final SOAPElement newElement;
       
   492                     if (this.namespaceUri == null) {
       
   493                         // add element with inherited scope
       
   494                         newElement = target.addChildElement(this.localName);
       
   495                     } else if (prefix == null) {
       
   496                         newElement = target.addChildElement(new QName(this.namespaceUri, this.localName));
       
   497                     } else {
       
   498                         newElement = target.addChildElement(this.localName, this.prefix, this.namespaceUri);
       
   499                     }
       
   500                     // add namespace declarations
       
   501                     for (NamespaceDeclaration namespace : this.namespaceDeclarations) {
       
   502                         target.addNamespaceDeclaration(namespace.prefix, namespace.namespaceUri);
       
   503                     }
       
   504                     // add attribute declarations
       
   505                     for (AttributeDeclaration attribute : this.attributeDeclarations) {
       
   506                         addAttibuteToElement(newElement,
       
   507                                 attribute.prefix, attribute.namespaceUri, attribute.localName, attribute.value);
       
   508                     }
       
   509                     // reset state
       
   510                     this.reset();
       
   511 
       
   512                     return newElement;
       
   513                 } else {
       
   514                     return target;
       
   515                 }
       
   516                 // else after reset state -> not initialized
       
   517             } catch (SOAPException e) {
       
   518                 throw new XMLStreamException(e);
       
   519             }
       
   520         }
       
   521 
       
   522         /**
       
   523          * Is the element initialized?
       
   524          * @return boolean indicating whether it was initialized after last flush
       
   525          */
       
   526         public boolean isInitialized() {
       
   527             return this.localName != null;
       
   528         }
       
   529 
       
   530         private void reset() {
       
   531             this.localName = null;
       
   532             this.prefix = null;
       
   533             this.namespaceUri = null;
       
   534             this.namespaceDeclarations.clear();
       
   535             this.attributeDeclarations.clear();
       
   536         }
       
   537 
       
   538         private static String emptyIfNull(String s) {
       
   539             return s == null ? "" : s;
       
   540         }
       
   541     }
       
   542 
       
   543     static class NamespaceDeclaration {
       
   544         final String prefix;
       
   545         final String namespaceUri;
       
   546 
       
   547         NamespaceDeclaration(String prefix, String namespaceUri) {
       
   548             this.prefix = prefix;
       
   549             this.namespaceUri = namespaceUri;
       
   550         }
       
   551     }
       
   552 
       
   553     static class AttributeDeclaration {
       
   554         final String prefix;
       
   555         final String namespaceUri;
       
   556         final String localName;
       
   557         final String value;
       
   558 
       
   559         AttributeDeclaration(String prefix, String namespaceUri, String localName, String value) {
       
   560             this.prefix = prefix;
       
   561             this.namespaceUri = namespaceUri;
       
   562             this.localName = localName;
       
   563             this.value = value;
       
   564         }
       
   565     }
   334 }
   566 }