aoqi@0: /* aoqi@0: * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. aoqi@0: * aoqi@0: * This code is free software; you can redistribute it and/or modify it aoqi@0: * under the terms of the GNU General Public License version 2 only, as aoqi@0: * published by the Free Software Foundation. Oracle designates this aoqi@0: * particular file as subject to the "Classpath" exception as provided aoqi@0: * by Oracle in the LICENSE file that accompanied this code. aoqi@0: * aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that aoqi@0: * accompanied this code). aoqi@0: * aoqi@0: * You should have received a copy of the GNU General Public License version aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation, aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. aoqi@0: * aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA aoqi@0: * or visit www.oracle.com if you need additional information or have any aoqi@0: * questions. aoqi@0: */ aoqi@0: aoqi@0: package com.sun.xml.internal.txw2; aoqi@0: aoqi@0: import com.sun.xml.internal.txw2.output.XmlSerializer; aoqi@0: aoqi@0: import java.util.Map; aoqi@0: import java.util.HashMap; aoqi@0: aoqi@0: /** aoqi@0: * Coordinates the entire writing process. aoqi@0: * aoqi@0: * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com) aoqi@0: */ aoqi@0: public final class Document { aoqi@0: aoqi@0: private final XmlSerializer out; aoqi@0: aoqi@0: /** aoqi@0: * Set to true once we invoke {@link XmlSerializer#startDocument()}. aoqi@0: * aoqi@0: *

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

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

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