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