diff -r 000000000000 -r 0961a4a21176 src/share/classes/com/sun/xml/internal/txw2/Document.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/com/sun/xml/internal/txw2/Document.java Sat Dec 01 00:00:00 2007 +0000 @@ -0,0 +1,337 @@ +/* + * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package com.sun.xml.internal.txw2; + +import com.sun.xml.internal.txw2.output.XmlSerializer; + +import java.util.Map; +import java.util.HashMap; + +/** + * Coordinates the entire writing process. + * + * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com) + */ +public final class Document { + + private final XmlSerializer out; + + /** + * Set to true once we invoke {@link XmlSerializer#startDocument()}. + * + *

+ * This is so that we can defer the writing as much as possible. + */ + private boolean started=false; + + /** + * Currently active writer. + * + *

+ * This points to the last written token. + */ + private Content current = null; + + private final Map datatypeWriters = new HashMap(); + + /** + * Used to generate unique namespace prefix. + */ + private int iota = 1; + + /** + * Used to keep track of in-scope namespace bindings declared in ancestors. + */ + private final NamespaceSupport inscopeNamespace = new NamespaceSupport(); + + /** + * Remembers the namespace declarations of the last unclosed start tag, + * so that we can fix up dummy prefixes in {@link Pcdata}. + */ + private NamespaceDecl activeNamespaces; + + + Document(XmlSerializer out) { + this.out = out; + for( DatatypeWriter dw : DatatypeWriter.BUILDIN ) + datatypeWriters.put(dw.getType(),dw); + } + + void flush() { + out.flush(); + } + + void setFirstContent(Content c) { + assert current==null; + current = new StartDocument(); + current.setNext(this,c); + } + + /** + * Defines additional user object -> string conversion logic. + * + *

+ * Applications can add their own {@link DatatypeWriter} so that + * application-specific objects can be turned into {@link String} + * for output. + * + * @param dw + * The {@link DatatypeWriter} to be added. Must not be null. + */ + public void addDatatypeWriter( DatatypeWriter dw ) { + datatypeWriters.put(dw.getType(),dw); + } + + /** + * Performs the output as much as possible + */ + void run() { + while(true) { + Content next = current.getNext(); + if(next==null || !next.isReadyToCommit()) + return; + next.accept(visitor); + next.written(); + current = next; + } + } + + /** + * Appends the given object to the end of the given buffer. + * + * @param nsResolver + * use + */ + void writeValue( Object obj, NamespaceResolver nsResolver, StringBuilder buf ) { + if(obj==null) + throw new IllegalArgumentException("argument contains null"); + + if(obj instanceof Object[]) { + for( Object o : (Object[])obj ) + writeValue(o,nsResolver,buf); + return; + } + if(obj instanceof Iterable) { + for( Object o : (Iterable)obj ) + writeValue(o,nsResolver,buf); + return; + } + + if(buf.length()>0) + buf.append(' '); + + Class c = obj.getClass(); + while(c!=null) { + DatatypeWriter dw = datatypeWriters.get(c); + if(dw!=null) { + dw.print(obj,nsResolver,buf); + return; + } + c = c.getSuperclass(); + } + + // if nothing applies, just use toString + buf.append(obj); + } + + // I wanted to hide those write method from users + private final ContentVisitor visitor = new ContentVisitor() { + public void onStartDocument() { + // the startDocument token is used as the sentry, so this method shall never + // be called. + // out.startDocument() is invoked when we write the start tag of the root element. + throw new IllegalStateException(); + } + + public void onEndDocument() { + out.endDocument(); + } + + public void onEndTag() { + out.endTag(); + inscopeNamespace.popContext(); + activeNamespaces = null; + } + + public void onPcdata(StringBuilder buffer) { + if(activeNamespaces!=null) + buffer = fixPrefix(buffer); + out.text(buffer); + } + + public void onCdata(StringBuilder buffer) { + if(activeNamespaces!=null) + buffer = fixPrefix(buffer); + out.cdata(buffer); + } + + public void onComment(StringBuilder buffer) { + if(activeNamespaces!=null) + buffer = fixPrefix(buffer); + out.comment(buffer); + } + + public void onStartTag(String nsUri, String localName, Attribute attributes, NamespaceDecl namespaces) { + assert nsUri!=null; + assert localName!=null; + + activeNamespaces = namespaces; + + if(!started) { + started = true; + out.startDocument(); + } + + inscopeNamespace.pushContext(); + + // declare the explicitly bound namespaces + for( NamespaceDecl ns=namespaces; ns!=null; ns=ns.next ) { + ns.declared = false; // reset this flag + + if(ns.prefix!=null) { + String uri = inscopeNamespace.getURI(ns.prefix); + if(uri!=null && uri.equals(ns.uri)) + ; // already declared + else { + // declare this new binding + inscopeNamespace.declarePrefix(ns.prefix,ns.uri); + ns.declared = true; + } + } + } + + // then use in-scope namespace to assign prefixes to others + for( NamespaceDecl ns=namespaces; ns!=null; ns=ns.next ) { + if(ns.prefix==null) { + if(inscopeNamespace.getURI("").equals(ns.uri)) + ns.prefix=""; + else { + String p = inscopeNamespace.getPrefix(ns.uri); + if(p==null) { + // assign a new one + while(inscopeNamespace.getURI(p=newPrefix())!=null) + ; + ns.declared = true; + inscopeNamespace.declarePrefix(p,ns.uri); + } + ns.prefix = p; + } + } + } + + // the first namespace decl must be the one for the element + assert namespaces.uri.equals(nsUri); + assert namespaces.prefix!=null : "a prefix must have been all allocated"; + out.beginStartTag(nsUri,localName,namespaces.prefix); + + // declare namespaces + for( NamespaceDecl ns=namespaces; ns!=null; ns=ns.next ) { + if(ns.declared) + out.writeXmlns( ns.prefix, ns.uri ); + } + + // writeBody attributes + for( Attribute a=attributes; a!=null; a=a.next) { + String prefix; + if(a.nsUri.length()==0) prefix=""; + else prefix=inscopeNamespace.getPrefix(a.nsUri); + out.writeAttribute( a.nsUri, a.localName, prefix, fixPrefix(a.value) ); + } + + out.endStartTag(nsUri,localName,namespaces.prefix); + } + }; + + /** + * Used by {@link #newPrefix()}. + */ + private final StringBuilder prefixSeed = new StringBuilder("ns"); + + private int prefixIota = 0; + + /** + * Allocates a new unique prefix. + */ + private String newPrefix() { + prefixSeed.setLength(2); + prefixSeed.append(++prefixIota); + return prefixSeed.toString(); + } + + /** + * Replaces dummy prefixes in the value to the real ones + * by using {@link #activeNamespaces}. + * + * @return + * the buffer passed as the buf parameter. + */ + private StringBuilder fixPrefix(StringBuilder buf) { + assert activeNamespaces!=null; + + int i; + int len=buf.length(); + for(i=0;i