src/share/classes/com/sun/xml/internal/txw2/Document.java

Mon, 04 May 2009 21:10:41 -0700

author
tbell
date
Mon, 04 May 2009 21:10:41 -0700
changeset 50
42dfec6871f6
parent 45
31822b475baa
permissions
-rw-r--r--

6658158: Mutable statics in SAAJ (findbugs)
6658163: txw2.DatatypeWriter.BUILDIN is a mutable static (findbugs)
Reviewed-by: darcy

duke@1 1 /*
tbell@45 2 * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved.
duke@1 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
duke@1 4 *
duke@1 5 * This code is free software; you can redistribute it and/or modify it
duke@1 6 * under the terms of the GNU General Public License version 2 only, as
duke@1 7 * published by the Free Software Foundation. Sun designates this
duke@1 8 * particular file as subject to the "Classpath" exception as provided
duke@1 9 * by Sun in the LICENSE file that accompanied this code.
duke@1 10 *
duke@1 11 * This code is distributed in the hope that it will be useful, but WITHOUT
duke@1 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
duke@1 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
duke@1 14 * version 2 for more details (a copy is included in the LICENSE file that
duke@1 15 * accompanied this code).
duke@1 16 *
duke@1 17 * You should have received a copy of the GNU General Public License version
duke@1 18 * 2 along with this work; if not, write to the Free Software Foundation,
duke@1 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
duke@1 20 *
duke@1 21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
duke@1 22 * CA 95054 USA or visit www.sun.com if you need additional information or
duke@1 23 * have any questions.
duke@1 24 */
tbell@45 25
duke@1 26 package com.sun.xml.internal.txw2;
duke@1 27
duke@1 28 import com.sun.xml.internal.txw2.output.XmlSerializer;
duke@1 29
duke@1 30 import java.util.Map;
duke@1 31 import java.util.HashMap;
duke@1 32
duke@1 33 /**
duke@1 34 * Coordinates the entire writing process.
duke@1 35 *
duke@1 36 * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
duke@1 37 */
duke@1 38 public final class Document {
duke@1 39
duke@1 40 private final XmlSerializer out;
duke@1 41
duke@1 42 /**
duke@1 43 * Set to true once we invoke {@link XmlSerializer#startDocument()}.
duke@1 44 *
duke@1 45 * <p>
duke@1 46 * This is so that we can defer the writing as much as possible.
duke@1 47 */
duke@1 48 private boolean started=false;
duke@1 49
duke@1 50 /**
duke@1 51 * Currently active writer.
duke@1 52 *
duke@1 53 * <p>
duke@1 54 * This points to the last written token.
duke@1 55 */
duke@1 56 private Content current = null;
duke@1 57
duke@1 58 private final Map<Class,DatatypeWriter> datatypeWriters = new HashMap<Class,DatatypeWriter>();
duke@1 59
duke@1 60 /**
duke@1 61 * Used to generate unique namespace prefix.
duke@1 62 */
duke@1 63 private int iota = 1;
duke@1 64
duke@1 65 /**
duke@1 66 * Used to keep track of in-scope namespace bindings declared in ancestors.
duke@1 67 */
duke@1 68 private final NamespaceSupport inscopeNamespace = new NamespaceSupport();
duke@1 69
duke@1 70 /**
duke@1 71 * Remembers the namespace declarations of the last unclosed start tag,
duke@1 72 * so that we can fix up dummy prefixes in {@link Pcdata}.
duke@1 73 */
duke@1 74 private NamespaceDecl activeNamespaces;
duke@1 75
duke@1 76
duke@1 77 Document(XmlSerializer out) {
duke@1 78 this.out = out;
tbell@50 79 for( DatatypeWriter dw : DatatypeWriter.BUILTIN )
duke@1 80 datatypeWriters.put(dw.getType(),dw);
duke@1 81 }
duke@1 82
duke@1 83 void flush() {
duke@1 84 out.flush();
duke@1 85 }
duke@1 86
duke@1 87 void setFirstContent(Content c) {
duke@1 88 assert current==null;
duke@1 89 current = new StartDocument();
duke@1 90 current.setNext(this,c);
duke@1 91 }
duke@1 92
duke@1 93 /**
duke@1 94 * Defines additional user object -> string conversion logic.
duke@1 95 *
duke@1 96 * <p>
duke@1 97 * Applications can add their own {@link DatatypeWriter} so that
duke@1 98 * application-specific objects can be turned into {@link String}
duke@1 99 * for output.
duke@1 100 *
duke@1 101 * @param dw
duke@1 102 * The {@link DatatypeWriter} to be added. Must not be null.
duke@1 103 */
duke@1 104 public void addDatatypeWriter( DatatypeWriter<?> dw ) {
duke@1 105 datatypeWriters.put(dw.getType(),dw);
duke@1 106 }
duke@1 107
duke@1 108 /**
duke@1 109 * Performs the output as much as possible
duke@1 110 */
duke@1 111 void run() {
duke@1 112 while(true) {
duke@1 113 Content next = current.getNext();
duke@1 114 if(next==null || !next.isReadyToCommit())
duke@1 115 return;
duke@1 116 next.accept(visitor);
duke@1 117 next.written();
duke@1 118 current = next;
duke@1 119 }
duke@1 120 }
duke@1 121
duke@1 122 /**
duke@1 123 * Appends the given object to the end of the given buffer.
duke@1 124 *
duke@1 125 * @param nsResolver
duke@1 126 * use
duke@1 127 */
duke@1 128 void writeValue( Object obj, NamespaceResolver nsResolver, StringBuilder buf ) {
duke@1 129 if(obj==null)
duke@1 130 throw new IllegalArgumentException("argument contains null");
duke@1 131
duke@1 132 if(obj instanceof Object[]) {
duke@1 133 for( Object o : (Object[])obj )
duke@1 134 writeValue(o,nsResolver,buf);
duke@1 135 return;
duke@1 136 }
duke@1 137 if(obj instanceof Iterable) {
duke@1 138 for( Object o : (Iterable<?>)obj )
duke@1 139 writeValue(o,nsResolver,buf);
duke@1 140 return;
duke@1 141 }
duke@1 142
duke@1 143 if(buf.length()>0)
duke@1 144 buf.append(' ');
duke@1 145
duke@1 146 Class c = obj.getClass();
duke@1 147 while(c!=null) {
duke@1 148 DatatypeWriter dw = datatypeWriters.get(c);
duke@1 149 if(dw!=null) {
duke@1 150 dw.print(obj,nsResolver,buf);
duke@1 151 return;
duke@1 152 }
duke@1 153 c = c.getSuperclass();
duke@1 154 }
duke@1 155
duke@1 156 // if nothing applies, just use toString
duke@1 157 buf.append(obj);
duke@1 158 }
duke@1 159
duke@1 160 // I wanted to hide those write method from users
duke@1 161 private final ContentVisitor visitor = new ContentVisitor() {
duke@1 162 public void onStartDocument() {
duke@1 163 // the startDocument token is used as the sentry, so this method shall never
duke@1 164 // be called.
duke@1 165 // out.startDocument() is invoked when we write the start tag of the root element.
duke@1 166 throw new IllegalStateException();
duke@1 167 }
duke@1 168
duke@1 169 public void onEndDocument() {
duke@1 170 out.endDocument();
duke@1 171 }
duke@1 172
duke@1 173 public void onEndTag() {
duke@1 174 out.endTag();
duke@1 175 inscopeNamespace.popContext();
duke@1 176 activeNamespaces = null;
duke@1 177 }
duke@1 178
duke@1 179 public void onPcdata(StringBuilder buffer) {
duke@1 180 if(activeNamespaces!=null)
duke@1 181 buffer = fixPrefix(buffer);
duke@1 182 out.text(buffer);
duke@1 183 }
duke@1 184
duke@1 185 public void onCdata(StringBuilder buffer) {
duke@1 186 if(activeNamespaces!=null)
duke@1 187 buffer = fixPrefix(buffer);
duke@1 188 out.cdata(buffer);
duke@1 189 }
duke@1 190
duke@1 191 public void onComment(StringBuilder buffer) {
duke@1 192 if(activeNamespaces!=null)
duke@1 193 buffer = fixPrefix(buffer);
duke@1 194 out.comment(buffer);
duke@1 195 }
duke@1 196
duke@1 197 public void onStartTag(String nsUri, String localName, Attribute attributes, NamespaceDecl namespaces) {
duke@1 198 assert nsUri!=null;
duke@1 199 assert localName!=null;
duke@1 200
duke@1 201 activeNamespaces = namespaces;
duke@1 202
duke@1 203 if(!started) {
duke@1 204 started = true;
duke@1 205 out.startDocument();
duke@1 206 }
duke@1 207
duke@1 208 inscopeNamespace.pushContext();
duke@1 209
duke@1 210 // declare the explicitly bound namespaces
duke@1 211 for( NamespaceDecl ns=namespaces; ns!=null; ns=ns.next ) {
duke@1 212 ns.declared = false; // reset this flag
duke@1 213
duke@1 214 if(ns.prefix!=null) {
duke@1 215 String uri = inscopeNamespace.getURI(ns.prefix);
duke@1 216 if(uri!=null && uri.equals(ns.uri))
duke@1 217 ; // already declared
duke@1 218 else {
duke@1 219 // declare this new binding
duke@1 220 inscopeNamespace.declarePrefix(ns.prefix,ns.uri);
duke@1 221 ns.declared = true;
duke@1 222 }
duke@1 223 }
duke@1 224 }
duke@1 225
duke@1 226 // then use in-scope namespace to assign prefixes to others
duke@1 227 for( NamespaceDecl ns=namespaces; ns!=null; ns=ns.next ) {
duke@1 228 if(ns.prefix==null) {
duke@1 229 if(inscopeNamespace.getURI("").equals(ns.uri))
duke@1 230 ns.prefix="";
duke@1 231 else {
duke@1 232 String p = inscopeNamespace.getPrefix(ns.uri);
duke@1 233 if(p==null) {
duke@1 234 // assign a new one
duke@1 235 while(inscopeNamespace.getURI(p=newPrefix())!=null)
duke@1 236 ;
duke@1 237 ns.declared = true;
duke@1 238 inscopeNamespace.declarePrefix(p,ns.uri);
duke@1 239 }
duke@1 240 ns.prefix = p;
duke@1 241 }
duke@1 242 }
duke@1 243 }
duke@1 244
duke@1 245 // the first namespace decl must be the one for the element
duke@1 246 assert namespaces.uri.equals(nsUri);
duke@1 247 assert namespaces.prefix!=null : "a prefix must have been all allocated";
duke@1 248 out.beginStartTag(nsUri,localName,namespaces.prefix);
duke@1 249
duke@1 250 // declare namespaces
duke@1 251 for( NamespaceDecl ns=namespaces; ns!=null; ns=ns.next ) {
duke@1 252 if(ns.declared)
duke@1 253 out.writeXmlns( ns.prefix, ns.uri );
duke@1 254 }
duke@1 255
duke@1 256 // writeBody attributes
duke@1 257 for( Attribute a=attributes; a!=null; a=a.next) {
duke@1 258 String prefix;
duke@1 259 if(a.nsUri.length()==0) prefix="";
duke@1 260 else prefix=inscopeNamespace.getPrefix(a.nsUri);
duke@1 261 out.writeAttribute( a.nsUri, a.localName, prefix, fixPrefix(a.value) );
duke@1 262 }
duke@1 263
duke@1 264 out.endStartTag(nsUri,localName,namespaces.prefix);
duke@1 265 }
duke@1 266 };
duke@1 267
duke@1 268 /**
duke@1 269 * Used by {@link #newPrefix()}.
duke@1 270 */
duke@1 271 private final StringBuilder prefixSeed = new StringBuilder("ns");
duke@1 272
duke@1 273 private int prefixIota = 0;
duke@1 274
duke@1 275 /**
duke@1 276 * Allocates a new unique prefix.
duke@1 277 */
duke@1 278 private String newPrefix() {
duke@1 279 prefixSeed.setLength(2);
duke@1 280 prefixSeed.append(++prefixIota);
duke@1 281 return prefixSeed.toString();
duke@1 282 }
duke@1 283
duke@1 284 /**
duke@1 285 * Replaces dummy prefixes in the value to the real ones
duke@1 286 * by using {@link #activeNamespaces}.
duke@1 287 *
duke@1 288 * @return
duke@1 289 * the buffer passed as the <tt>buf</tt> parameter.
duke@1 290 */
duke@1 291 private StringBuilder fixPrefix(StringBuilder buf) {
duke@1 292 assert activeNamespaces!=null;
duke@1 293
duke@1 294 int i;
duke@1 295 int len=buf.length();
duke@1 296 for(i=0;i<len;i++)
duke@1 297 if( buf.charAt(i)==MAGIC )
duke@1 298 break;
duke@1 299 // typically it doens't contain any prefix.
duke@1 300 // just return the original buffer in that case
duke@1 301 if(i==len)
duke@1 302 return buf;
duke@1 303
duke@1 304 while(i<len) {
duke@1 305 char uriIdx = buf.charAt(i+1);
duke@1 306 NamespaceDecl ns = activeNamespaces;
duke@1 307 while(ns!=null && ns.uniqueId!=uriIdx)
duke@1 308 ns=ns.next;
duke@1 309 if(ns==null)
duke@1 310 throw new IllegalStateException("Unexpected use of prefixes "+buf);
duke@1 311
duke@1 312 int length = 2;
duke@1 313 String prefix = ns.prefix;
duke@1 314 if(prefix.length()==0) {
duke@1 315 if(buf.length()<=i+2 || buf.charAt(i+2)!=':')
duke@1 316 throw new IllegalStateException("Unexpected use of prefixes "+buf);
duke@1 317 length=3;
duke@1 318 }
duke@1 319
duke@1 320 buf.replace(i,i+length,prefix);
duke@1 321 len += prefix.length()-length;
duke@1 322
duke@1 323 while(i<len && buf.charAt(i)!=MAGIC)
duke@1 324 i++;
duke@1 325 }
duke@1 326
duke@1 327 return buf;
duke@1 328 }
duke@1 329
duke@1 330 /**
duke@1 331 * The first char of the dummy prefix.
duke@1 332 */
duke@1 333 static final char MAGIC = '\u0000';
duke@1 334
duke@1 335 char assignNewId() {
duke@1 336 return (char)iota++;
duke@1 337 }
duke@1 338 }

mercurial