Thu, 12 Jan 2017 00:25:07 +0300
8159058: SAXParseException when sending soap message
Reviewed-by: lancea, coffeys
1.1 --- a/src/share/jaxws_classes/com/sun/xml/internal/messaging/saaj/soap/impl/DetailImpl.java Mon May 01 10:54:49 2017 -0700 1.2 +++ b/src/share/jaxws_classes/com/sun/xml/internal/messaging/saaj/soap/impl/DetailImpl.java Thu Jan 12 00:25:07 2017 +0300 1.3 @@ -1,5 +1,5 @@ 1.4 /* 1.5 - * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. 1.6 + * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. 1.7 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1.8 * 1.9 * This code is free software; you can redistribute it and/or modify it 1.10 @@ -47,13 +47,13 @@ 1.11 public DetailEntry addDetailEntry(Name name) throws SOAPException { 1.12 DetailEntry entry = createDetailEntry(name); 1.13 addNode(entry); 1.14 - return (DetailEntry) circumventBug5034339(entry); 1.15 + return entry; 1.16 } 1.17 1.18 public DetailEntry addDetailEntry(QName qname) throws SOAPException { 1.19 DetailEntry entry = createDetailEntry(qname); 1.20 addNode(entry); 1.21 - return (DetailEntry) circumventBug5034339(entry); 1.22 + return entry; 1.23 } 1.24 1.25 protected SOAPElement addElement(Name name) throws SOAPException { 1.26 @@ -119,28 +119,4 @@ 1.27 return true; 1.28 } 1.29 1.30 - //overriding this method since the only two uses of this method 1.31 - // are in ElementImpl and DetailImpl 1.32 - //whereas the original base impl does the correct job for calls to it inside ElementImpl 1.33 - // But it would not work for DetailImpl. 1.34 - protected SOAPElement circumventBug5034339(SOAPElement element) { 1.35 - 1.36 - Name elementName = element.getElementName(); 1.37 - if (!isNamespaceQualified(elementName)) { 1.38 - String prefix = elementName.getPrefix(); 1.39 - String defaultNamespace = getNamespaceURI(prefix); 1.40 - if (defaultNamespace != null) { 1.41 - Name newElementName = 1.42 - NameImpl.create( 1.43 - elementName.getLocalName(), 1.44 - elementName.getPrefix(), 1.45 - defaultNamespace); 1.46 - SOAPElement newElement = createDetailEntry(newElementName); 1.47 - replaceChild(newElement, element); 1.48 - return newElement; 1.49 - } 1.50 - } 1.51 - return element; 1.52 - } 1.53 - 1.54 }
2.1 --- a/src/share/jaxws_classes/com/sun/xml/internal/messaging/saaj/soap/impl/ElementImpl.java Mon May 01 10:54:49 2017 -0700 2.2 +++ b/src/share/jaxws_classes/com/sun/xml/internal/messaging/saaj/soap/impl/ElementImpl.java Thu Jan 12 00:25:07 2017 +0300 2.3 @@ -1,5 +1,5 @@ 2.4 /* 2.5 - * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. 2.6 + * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. 2.7 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 2.8 * 2.9 * This code is free software; you can redistribute it and/or modify it 2.10 @@ -127,8 +127,11 @@ 2.11 } 2.12 2.13 public SOAPElement addChildElement(String localName) throws SOAPException { 2.14 - return (SOAPElement) addChildElement( 2.15 - NameImpl.createFromUnqualifiedName(localName)); 2.16 + String nsUri = getNamespaceURI(""); 2.17 + Name name = (nsUri == null || nsUri.isEmpty()) 2.18 + ? NameImpl.createFromUnqualifiedName(localName) 2.19 + : NameImpl.createFromQualifiedName(localName, nsUri); 2.20 + return addChildElement(name); 2.21 } 2.22 2.23 public SOAPElement addChildElement(String localName, String prefix) 2.24 @@ -372,13 +375,13 @@ 2.25 protected SOAPElement addElement(Name name) throws SOAPException { 2.26 SOAPElement newElement = createElement(name); 2.27 addNode(newElement); 2.28 - return circumventBug5034339(newElement); 2.29 + return newElement; 2.30 } 2.31 2.32 protected SOAPElement addElement(QName name) throws SOAPException { 2.33 SOAPElement newElement = createElement(name); 2.34 addNode(newElement); 2.35 - return circumventBug5034339(newElement); 2.36 + return newElement; 2.37 } 2.38 2.39 protected SOAPElement createElement(Name name) { 2.40 @@ -1201,26 +1204,6 @@ 2.41 return !"".equals(name.getNamespaceURI()); 2.42 } 2.43 2.44 - protected SOAPElement circumventBug5034339(SOAPElement element) { 2.45 - 2.46 - Name elementName = element.getElementName(); 2.47 - if (!isNamespaceQualified(elementName)) { 2.48 - String prefix = elementName.getPrefix(); 2.49 - String defaultNamespace = getNamespaceURI(prefix); 2.50 - if (defaultNamespace != null) { 2.51 - Name newElementName = 2.52 - NameImpl.create( 2.53 - elementName.getLocalName(), 2.54 - elementName.getPrefix(), 2.55 - defaultNamespace); 2.56 - SOAPElement newElement = createElement(newElementName); 2.57 - replaceChild(newElement, element); 2.58 - return newElement; 2.59 - } 2.60 - } 2.61 - return element; 2.62 - } 2.63 - 2.64 //TODO: This is a temporary SAAJ workaround for optimizing XWS 2.65 // should be removed once the corresponding JAXP bug is fixed 2.66 // It appears the bug will be fixed in JAXP 1.4 (not by Appserver 9 timeframe)
3.1 --- a/src/share/jaxws_classes/com/sun/xml/internal/ws/api/message/saaj/SaajStaxWriter.java Mon May 01 10:54:49 2017 -0700 3.2 +++ b/src/share/jaxws_classes/com/sun/xml/internal/ws/api/message/saaj/SaajStaxWriter.java Thu Jan 12 00:25:07 2017 +0300 3.3 @@ -1,5 +1,5 @@ 3.4 /* 3.5 - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. 3.6 + * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. 3.7 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 3.8 * 3.9 * This code is free software; you can redistribute it and/or modify it 3.10 @@ -25,8 +25,10 @@ 3.11 3.12 package com.sun.xml.internal.ws.api.message.saaj; 3.13 3.14 +import java.util.Iterator; 3.15 import java.util.Arrays; 3.16 -import java.util.Iterator; 3.17 +import java.util.List; 3.18 +import java.util.LinkedList; 3.19 3.20 import javax.xml.namespace.NamespaceContext; 3.21 import javax.xml.namespace.QName; 3.22 @@ -42,6 +44,17 @@ 3.23 /** 3.24 * SaajStaxWriter builds a SAAJ SOAPMessage by using XMLStreamWriter interface. 3.25 * 3.26 + * <p> 3.27 + * Defers creation of SOAPElement until all the aspects of the name of the element are known. 3.28 + * In some cases, the namespace uri is indicated only by the {@link #writeNamespace(String, String)} call. 3.29 + * After opening an element ({@code writeStartElement}, {@code writeEmptyElement} methods), all attributes 3.30 + * and namespace assignments are retained within {@link DeferredElement} object ({@code deferredElement} field). 3.31 + * As soon as any other method than {@code writeAttribute}, {@code writeNamespace}, {@code writeDefaultNamespace} 3.32 + * or {@code setNamespace} is called, the contents of {@code deferredElement} is transformed into new SOAPElement 3.33 + * (which is appropriately inserted into the SOAPMessage under construction). 3.34 + * This mechanism is necessary to fix JDK-8159058 issue. 3.35 + * </p> 3.36 + * 3.37 * @author shih-chang.chen@oracle.com 3.38 */ 3.39 public class SaajStaxWriter implements XMLStreamWriter { 3.40 @@ -49,6 +62,7 @@ 3.41 protected SOAPMessage soap; 3.42 protected String envURI; 3.43 protected SOAPElement currentElement; 3.44 + protected DeferredElement deferredElement; 3.45 3.46 static final protected String Envelope = "Envelope"; 3.47 static final protected String Header = "Header"; 3.48 @@ -59,6 +73,7 @@ 3.49 soap = msg; 3.50 currentElement = soap.getSOAPPart().getEnvelope(); 3.51 envURI = currentElement.getNamespaceURI(); 3.52 + this.deferredElement = new DeferredElement(); 3.53 } 3.54 3.55 public SOAPMessage getSOAPMessage() { 3.56 @@ -67,11 +82,8 @@ 3.57 3.58 @Override 3.59 public void writeStartElement(final String localName) throws XMLStreamException { 3.60 - try { 3.61 - currentElement = currentElement.addChildElement(localName); 3.62 - } catch (SOAPException e) { 3.63 - throw new XMLStreamException(e); 3.64 - } 3.65 + currentElement = deferredElement.flushTo(currentElement); 3.66 + deferredElement.setLocalName(localName); 3.67 } 3.68 3.69 @Override 3.70 @@ -81,8 +93,10 @@ 3.71 3.72 @Override 3.73 public void writeStartElement(final String prefix, final String ln, final String ns) throws XMLStreamException { 3.74 - try { 3.75 - if (envURI.equals(ns)) { 3.76 + currentElement = deferredElement.flushTo(currentElement); 3.77 + 3.78 + if (envURI.equals(ns)) { 3.79 + try { 3.80 if (Envelope.equals(ln)) { 3.81 currentElement = soap.getSOAPPart().getEnvelope(); 3.82 fixPrefix(prefix); 3.83 @@ -96,13 +110,16 @@ 3.84 fixPrefix(prefix); 3.85 return; 3.86 } 3.87 + } catch (SOAPException e) { 3.88 + throw new XMLStreamException(e); 3.89 } 3.90 - currentElement = (prefix == null) ? 3.91 - currentElement.addChildElement(new QName(ns, ln)) : 3.92 - currentElement.addChildElement(ln, prefix, ns); 3.93 - } catch (SOAPException e) { 3.94 - throw new XMLStreamException(e); 3.95 + 3.96 } 3.97 + 3.98 + deferredElement.setLocalName(ln); 3.99 + deferredElement.setNamespaceUri(ns); 3.100 + deferredElement.setPrefix(prefix); 3.101 + 3.102 } 3.103 3.104 private void fixPrefix(final String prfx) throws XMLStreamException { 3.105 @@ -129,11 +146,13 @@ 3.106 3.107 @Override 3.108 public void writeEndElement() throws XMLStreamException { 3.109 + currentElement = deferredElement.flushTo(currentElement); 3.110 if (currentElement != null) currentElement = currentElement.getParentElement(); 3.111 } 3.112 3.113 @Override 3.114 public void writeEndDocument() throws XMLStreamException { 3.115 + currentElement = deferredElement.flushTo(currentElement); 3.116 } 3.117 3.118 @Override 3.119 @@ -151,19 +170,14 @@ 3.120 3.121 @Override 3.122 public void writeAttribute(final String prefix, final String ns, final String ln, final String value) throws XMLStreamException { 3.123 - try { 3.124 - if (ns == null) { 3.125 - if (prefix == null && xmlns.equals(ln)) { 3.126 - currentElement.addNamespaceDeclaration("", value); 3.127 - } else { 3.128 - currentElement.setAttributeNS("", ln, value); 3.129 - } 3.130 + if (ns == null && prefix == null && xmlns.equals(ln)) { 3.131 + writeNamespace("", value); 3.132 + } else { 3.133 + if (deferredElement.isInitialized()) { 3.134 + deferredElement.addAttribute(prefix, ns, ln, value); 3.135 } else { 3.136 - QName name = (prefix == null) ? new QName(ns, ln) : new QName(ns, ln, prefix); 3.137 - currentElement.addAttribute(name, value); 3.138 + addAttibuteToElement(currentElement, prefix, ns, ln, value); 3.139 } 3.140 - } catch (SOAPException e) { 3.141 - throw new XMLStreamException(e); 3.142 } 3.143 } 3.144 3.145 @@ -174,16 +188,16 @@ 3.146 3.147 @Override 3.148 public void writeNamespace(String prefix, final String uri) throws XMLStreamException { 3.149 - 3.150 // make prefix default if null or "xmlns" (according to javadoc) 3.151 - if (prefix == null || "xmlns".equals(prefix)) { 3.152 - prefix = ""; 3.153 - } 3.154 - 3.155 - try { 3.156 - currentElement.addNamespaceDeclaration(prefix, uri); 3.157 - } catch (SOAPException e) { 3.158 - throw new XMLStreamException(e); 3.159 + String thePrefix = prefix == null || "xmlns".equals(prefix) ? "" : prefix; 3.160 + if (deferredElement.isInitialized()) { 3.161 + deferredElement.addNamespaceDeclaration(thePrefix, uri); 3.162 + } else { 3.163 + try { 3.164 + currentElement.addNamespaceDeclaration(thePrefix, uri); 3.165 + } catch (SOAPException e) { 3.166 + throw new XMLStreamException(e); 3.167 + } 3.168 } 3.169 } 3.170 3.171 @@ -194,35 +208,40 @@ 3.172 3.173 @Override 3.174 public void writeComment(final String data) throws XMLStreamException { 3.175 + currentElement = deferredElement.flushTo(currentElement); 3.176 Comment c = soap.getSOAPPart().createComment(data); 3.177 currentElement.appendChild(c); 3.178 } 3.179 3.180 @Override 3.181 public void writeProcessingInstruction(final String target) throws XMLStreamException { 3.182 + currentElement = deferredElement.flushTo(currentElement); 3.183 Node n = soap.getSOAPPart().createProcessingInstruction(target, ""); 3.184 currentElement.appendChild(n); 3.185 } 3.186 3.187 @Override 3.188 public void writeProcessingInstruction(final String target, final String data) throws XMLStreamException { 3.189 + currentElement = deferredElement.flushTo(currentElement); 3.190 Node n = soap.getSOAPPart().createProcessingInstruction(target, data); 3.191 currentElement.appendChild(n); 3.192 } 3.193 3.194 @Override 3.195 public void writeCData(final String data) throws XMLStreamException { 3.196 + currentElement = deferredElement.flushTo(currentElement); 3.197 Node n = soap.getSOAPPart().createCDATASection(data); 3.198 currentElement.appendChild(n); 3.199 } 3.200 3.201 @Override 3.202 public void writeDTD(final String dtd) throws XMLStreamException { 3.203 - //TODO ... Don't do anything here 3.204 + currentElement = deferredElement.flushTo(currentElement); 3.205 } 3.206 3.207 @Override 3.208 public void writeEntityRef(final String name) throws XMLStreamException { 3.209 + currentElement = deferredElement.flushTo(currentElement); 3.210 Node n = soap.getSOAPPart().createEntityReference(name); 3.211 currentElement.appendChild(n); 3.212 } 3.213 @@ -250,6 +269,7 @@ 3.214 3.215 @Override 3.216 public void writeCharacters(final String text) throws XMLStreamException { 3.217 + currentElement = deferredElement.flushTo(currentElement); 3.218 try { 3.219 currentElement.addTextNode(text); 3.220 } catch (SOAPException e) { 3.221 @@ -259,6 +279,7 @@ 3.222 3.223 @Override 3.224 public void writeCharacters(final char[] text, final int start, final int len) throws XMLStreamException { 3.225 + currentElement = deferredElement.flushTo(currentElement); 3.226 char[] chr = (start == 0 && len == text.length) ? text : Arrays.copyOfRange(text, start, start + len); 3.227 try { 3.228 currentElement.addTextNode(new String(chr)); 3.229 @@ -274,10 +295,16 @@ 3.230 3.231 @Override 3.232 public void setPrefix(final String prefix, final String uri) throws XMLStreamException { 3.233 - try { 3.234 - this.currentElement.addNamespaceDeclaration(prefix, uri); 3.235 - } catch (SOAPException e) { 3.236 - throw new XMLStreamException(e); 3.237 + // TODO: this in fact is not what would be expected from XMLStreamWriter 3.238 + // (e.g. XMLStreamWriter for writing to output stream does not write anything as result of 3.239 + // this method, it just rememebers that given prefix is associated with the given uri 3.240 + // for the scope; to actually declare the prefix assignment in the resulting XML, one 3.241 + // needs to call writeNamespace(...) method 3.242 + // Kept for backwards compatibility reasons - this might be worth of further investigation. 3.243 + if (deferredElement.isInitialized()) { 3.244 + deferredElement.addNamespaceDeclaration(prefix, uri); 3.245 + } else { 3.246 + throw new XMLStreamException("Namespace not associated with any element"); 3.247 } 3.248 } 3.249 3.250 @@ -308,12 +335,12 @@ 3.251 return currentElement.lookupPrefix(namespaceURI); 3.252 } 3.253 public Iterator getPrefixes(final String namespaceURI) { 3.254 - return new Iterator() { 3.255 + return new Iterator<String>() { 3.256 String prefix = getPrefix(namespaceURI); 3.257 public boolean hasNext() { 3.258 return (prefix != null); 3.259 } 3.260 - public Object next() { 3.261 + public String next() { 3.262 if (!hasNext()) throw new java.util.NoSuchElementException(); 3.263 String next = prefix; 3.264 prefix = null; 3.265 @@ -324,4 +351,209 @@ 3.266 } 3.267 }; 3.268 } 3.269 + 3.270 + static void addAttibuteToElement(SOAPElement element, String prefix, String ns, String ln, String value) 3.271 + throws XMLStreamException { 3.272 + try { 3.273 + if (ns == null) { 3.274 + element.setAttributeNS("", ln, value); 3.275 + } else { 3.276 + QName name = prefix == null ? new QName(ns, ln) : new QName(ns, ln, prefix); 3.277 + element.addAttribute(name, value); 3.278 + } 3.279 + } catch (SOAPException e) { 3.280 + throw new XMLStreamException(e); 3.281 + } 3.282 + } 3.283 + 3.284 + /** 3.285 + * Holds details of element that needs to be deferred in order to manage namespace assignments correctly. 3.286 + * 3.287 + * <p> 3.288 + * An instance of can be set with all the aspects of the element name (local name, prefix, namespace uri). 3.289 + * Attributes and namespace declarations (special case of attribute) can be added. 3.290 + * Namespace declarations are handled so that the element namespace is updated if it is implied by the namespace 3.291 + * declaration and the namespace was not set to non-{@code null} value previously. 3.292 + * </p> 3.293 + * 3.294 + * <p> 3.295 + * The state of this object can be {@link #flushTo(SOAPElement) flushed} to SOAPElement - new SOAPElement will 3.296 + * be added a child element; the new element will have exactly the shape as represented by the state of this 3.297 + * object. Note that the {@link #flushTo(SOAPElement)} method does nothing 3.298 + * (and returns the argument immediately) if the state of this object is not initialized 3.299 + * (i.e. local name is null). 3.300 + * </p> 3.301 + * 3.302 + * @author ondrej.cerny@oracle.com 3.303 + */ 3.304 + static class DeferredElement { 3.305 + private String prefix; 3.306 + private String localName; 3.307 + private String namespaceUri; 3.308 + private final List<NamespaceDeclaration> namespaceDeclarations; 3.309 + private final List<AttributeDeclaration> attributeDeclarations; 3.310 + 3.311 + DeferredElement() { 3.312 + this.namespaceDeclarations = new LinkedList<NamespaceDeclaration>(); 3.313 + this.attributeDeclarations = new LinkedList<AttributeDeclaration>(); 3.314 + reset(); 3.315 + } 3.316 + 3.317 + 3.318 + /** 3.319 + * Set prefix of the element. 3.320 + * @param prefix namespace prefix 3.321 + */ 3.322 + public void setPrefix(final String prefix) { 3.323 + this.prefix = prefix; 3.324 + } 3.325 + 3.326 + /** 3.327 + * Set local name of the element. 3.328 + * 3.329 + * <p> 3.330 + * This method initializes the element. 3.331 + * </p> 3.332 + * 3.333 + * @param localName local name {@code not null} 3.334 + */ 3.335 + public void setLocalName(final String localName) { 3.336 + if (localName == null) { 3.337 + throw new IllegalArgumentException("localName can not be null"); 3.338 + } 3.339 + this.localName = localName; 3.340 + } 3.341 + 3.342 + /** 3.343 + * Set namespace uri. 3.344 + * 3.345 + * @param namespaceUri namespace uri 3.346 + */ 3.347 + public void setNamespaceUri(final String namespaceUri) { 3.348 + this.namespaceUri = namespaceUri; 3.349 + } 3.350 + 3.351 + /** 3.352 + * Adds namespace prefix assignment to the element. 3.353 + * 3.354 + * @param prefix prefix (not {@code null}) 3.355 + * @param namespaceUri namespace uri 3.356 + */ 3.357 + public void addNamespaceDeclaration(final String prefix, final String namespaceUri) { 3.358 + if (null == this.namespaceUri && null != namespaceUri && prefix.equals(emptyIfNull(this.prefix))) { 3.359 + this.namespaceUri = namespaceUri; 3.360 + } 3.361 + this.namespaceDeclarations.add(new NamespaceDeclaration(prefix, namespaceUri)); 3.362 + } 3.363 + 3.364 + /** 3.365 + * Adds attribute to the element. 3.366 + * @param prefix prefix 3.367 + * @param ns namespace 3.368 + * @param ln local name 3.369 + * @param value value 3.370 + */ 3.371 + public void addAttribute(final String prefix, final String ns, final String ln, final String value) { 3.372 + if (ns == null && prefix == null && xmlns.equals(ln)) { 3.373 + this.addNamespaceDeclaration(prefix, value); 3.374 + } else { 3.375 + this.attributeDeclarations.add(new AttributeDeclaration(prefix, ns, ln, value)); 3.376 + } 3.377 + } 3.378 + 3.379 + /** 3.380 + * Flushes state of this element to the {@code target} element. 3.381 + * 3.382 + * <p> 3.383 + * If this element is initialized then it is added with all the namespace declarations and attributes 3.384 + * to the {@code target} element as a child. The state of this element is reset to uninitialized. 3.385 + * The newly added element object is returned. 3.386 + * </p> 3.387 + * <p> 3.388 + * If this element is not initialized then the {@code target} is returned immediately, nothing else is done. 3.389 + * </p> 3.390 + * 3.391 + * @param target target element 3.392 + * @return {@code target} or new element 3.393 + * @throws XMLStreamException on error 3.394 + */ 3.395 + public SOAPElement flushTo(final SOAPElement target) throws XMLStreamException { 3.396 + try { 3.397 + if (this.localName != null) { 3.398 + // add the element appropriately (based on namespace declaration) 3.399 + final SOAPElement newElement; 3.400 + if (this.namespaceUri == null) { 3.401 + // add element with inherited scope 3.402 + newElement = target.addChildElement(this.localName); 3.403 + } else if (prefix == null) { 3.404 + newElement = target.addChildElement(new QName(this.namespaceUri, this.localName)); 3.405 + } else { 3.406 + newElement = target.addChildElement(this.localName, this.prefix, this.namespaceUri); 3.407 + } 3.408 + // add namespace declarations 3.409 + for (NamespaceDeclaration namespace : this.namespaceDeclarations) { 3.410 + target.addNamespaceDeclaration(namespace.prefix, namespace.namespaceUri); 3.411 + } 3.412 + // add attribute declarations 3.413 + for (AttributeDeclaration attribute : this.attributeDeclarations) { 3.414 + addAttibuteToElement(newElement, 3.415 + attribute.prefix, attribute.namespaceUri, attribute.localName, attribute.value); 3.416 + } 3.417 + // reset state 3.418 + this.reset(); 3.419 + 3.420 + return newElement; 3.421 + } else { 3.422 + return target; 3.423 + } 3.424 + // else after reset state -> not initialized 3.425 + } catch (SOAPException e) { 3.426 + throw new XMLStreamException(e); 3.427 + } 3.428 + } 3.429 + 3.430 + /** 3.431 + * Is the element initialized? 3.432 + * @return boolean indicating whether it was initialized after last flush 3.433 + */ 3.434 + public boolean isInitialized() { 3.435 + return this.localName != null; 3.436 + } 3.437 + 3.438 + private void reset() { 3.439 + this.localName = null; 3.440 + this.prefix = null; 3.441 + this.namespaceUri = null; 3.442 + this.namespaceDeclarations.clear(); 3.443 + this.attributeDeclarations.clear(); 3.444 + } 3.445 + 3.446 + private static String emptyIfNull(String s) { 3.447 + return s == null ? "" : s; 3.448 + } 3.449 + } 3.450 + 3.451 + static class NamespaceDeclaration { 3.452 + final String prefix; 3.453 + final String namespaceUri; 3.454 + 3.455 + NamespaceDeclaration(String prefix, String namespaceUri) { 3.456 + this.prefix = prefix; 3.457 + this.namespaceUri = namespaceUri; 3.458 + } 3.459 + } 3.460 + 3.461 + static class AttributeDeclaration { 3.462 + final String prefix; 3.463 + final String namespaceUri; 3.464 + final String localName; 3.465 + final String value; 3.466 + 3.467 + AttributeDeclaration(String prefix, String namespaceUri, String localName, String value) { 3.468 + this.prefix = prefix; 3.469 + this.namespaceUri = namespaceUri; 3.470 + this.localName = localName; 3.471 + this.value = value; 3.472 + } 3.473 + } 3.474 }