001: /*
002: * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025: package com.sun.xml.internal.txw2;
026:
027: import com.sun.xml.internal.txw2.output.XmlSerializer;
028:
029: import java.util.Map;
030: import java.util.HashMap;
031:
032: /**
033: * Coordinates the entire writing process.
034: *
035: * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
036: */
037: public final class Document {
038:
039: private final XmlSerializer out;
040:
041: /**
042: * Set to true once we invoke {@link XmlSerializer#startDocument()}.
043: *
044: * <p>
045: * This is so that we can defer the writing as much as possible.
046: */
047: private boolean started = false;
048:
049: /**
050: * Currently active writer.
051: *
052: * <p>
053: * This points to the last written token.
054: */
055: private Content current = null;
056:
057: private final Map<Class, DatatypeWriter> datatypeWriters = new HashMap<Class, DatatypeWriter>();
058:
059: /**
060: * Used to generate unique namespace prefix.
061: */
062: private int iota = 1;
063:
064: /**
065: * Used to keep track of in-scope namespace bindings declared in ancestors.
066: */
067: private final NamespaceSupport inscopeNamespace = new NamespaceSupport();
068:
069: /**
070: * Remembers the namespace declarations of the last unclosed start tag,
071: * so that we can fix up dummy prefixes in {@link Pcdata}.
072: */
073: private NamespaceDecl activeNamespaces;
074:
075: Document(XmlSerializer out) {
076: this .out = out;
077: for (DatatypeWriter dw : DatatypeWriter.BUILDIN)
078: datatypeWriters.put(dw.getType(), dw);
079: }
080:
081: void flush() {
082: out.flush();
083: }
084:
085: void setFirstContent(Content c) {
086: assert current == null;
087: current = new StartDocument();
088: current.setNext(this , c);
089: }
090:
091: /**
092: * Defines additional user object -> string conversion logic.
093: *
094: * <p>
095: * Applications can add their own {@link DatatypeWriter} so that
096: * application-specific objects can be turned into {@link String}
097: * for output.
098: *
099: * @param dw
100: * The {@link DatatypeWriter} to be added. Must not be null.
101: */
102: public void addDatatypeWriter(DatatypeWriter<?> dw) {
103: datatypeWriters.put(dw.getType(), dw);
104: }
105:
106: /**
107: * Performs the output as much as possible
108: */
109: void run() {
110: while (true) {
111: Content next = current.getNext();
112: if (next == null || !next.isReadyToCommit())
113: return;
114: next.accept(visitor);
115: next.written();
116: current = next;
117: }
118: }
119:
120: /**
121: * Appends the given object to the end of the given buffer.
122: *
123: * @param nsResolver
124: * use
125: */
126: void writeValue(Object obj, NamespaceResolver nsResolver,
127: StringBuilder buf) {
128: if (obj == null)
129: throw new IllegalArgumentException("argument contains null");
130:
131: if (obj instanceof Object[]) {
132: for (Object o : (Object[]) obj)
133: writeValue(o, nsResolver, buf);
134: return;
135: }
136: if (obj instanceof Iterable) {
137: for (Object o : (Iterable<?>) obj)
138: writeValue(o, nsResolver, buf);
139: return;
140: }
141:
142: if (buf.length() > 0)
143: buf.append(' ');
144:
145: Class c = obj.getClass();
146: while (c != null) {
147: DatatypeWriter dw = datatypeWriters.get(c);
148: if (dw != null) {
149: dw.print(obj, nsResolver, buf);
150: return;
151: }
152: c = c.getSuperclass();
153: }
154:
155: // if nothing applies, just use toString
156: buf.append(obj);
157: }
158:
159: // I wanted to hide those write method from users
160: private final ContentVisitor visitor = new ContentVisitor() {
161: public void onStartDocument() {
162: // the startDocument token is used as the sentry, so this method shall never
163: // be called.
164: // out.startDocument() is invoked when we write the start tag of the root element.
165: throw new IllegalStateException();
166: }
167:
168: public void onEndDocument() {
169: out.endDocument();
170: }
171:
172: public void onEndTag() {
173: out.endTag();
174: inscopeNamespace.popContext();
175: activeNamespaces = null;
176: }
177:
178: public void onPcdata(StringBuilder buffer) {
179: if (activeNamespaces != null)
180: buffer = fixPrefix(buffer);
181: out.text(buffer);
182: }
183:
184: public void onCdata(StringBuilder buffer) {
185: if (activeNamespaces != null)
186: buffer = fixPrefix(buffer);
187: out.cdata(buffer);
188: }
189:
190: public void onComment(StringBuilder buffer) {
191: if (activeNamespaces != null)
192: buffer = fixPrefix(buffer);
193: out.comment(buffer);
194: }
195:
196: public void onStartTag(String nsUri, String localName,
197: 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,
221: ns.uri);
222: ns.declared = true;
223: }
224: }
225: }
226:
227: // then use in-scope namespace to assign prefixes to others
228: for (NamespaceDecl ns = namespaces; ns != null; ns = ns.next) {
229: if (ns.prefix == null) {
230: if (inscopeNamespace.getURI("").equals(ns.uri))
231: ns.prefix = "";
232: else {
233: String p = inscopeNamespace.getPrefix(ns.uri);
234: if (p == null) {
235: // assign a new one
236: while (inscopeNamespace
237: .getURI(p = newPrefix()) != null)
238: ;
239: ns.declared = true;
240: inscopeNamespace.declarePrefix(p, ns.uri);
241: }
242: ns.prefix = p;
243: }
244: }
245: }
246:
247: // the first namespace decl must be the one for the element
248: assert namespaces.uri.equals(nsUri);
249: assert namespaces.prefix != null : "a prefix must have been all allocated";
250: out.beginStartTag(nsUri, localName, namespaces.prefix);
251:
252: // declare namespaces
253: for (NamespaceDecl ns = namespaces; ns != null; ns = ns.next) {
254: if (ns.declared)
255: out.writeXmlns(ns.prefix, ns.uri);
256: }
257:
258: // writeBody attributes
259: for (Attribute a = attributes; a != null; a = a.next) {
260: String prefix;
261: if (a.nsUri.length() == 0)
262: prefix = "";
263: else
264: prefix = inscopeNamespace.getPrefix(a.nsUri);
265: out.writeAttribute(a.nsUri, a.localName, prefix,
266: fixPrefix(a.value));
267: }
268:
269: out.endStartTag(nsUri, localName, namespaces.prefix);
270: }
271: };
272:
273: /**
274: * Used by {@link #newPrefix()}.
275: */
276: private final StringBuilder prefixSeed = new StringBuilder("ns");
277:
278: private int prefixIota = 0;
279:
280: /**
281: * Allocates a new unique prefix.
282: */
283: private String newPrefix() {
284: prefixSeed.setLength(2);
285: prefixSeed.append(++prefixIota);
286: return prefixSeed.toString();
287: }
288:
289: /**
290: * Replaces dummy prefixes in the value to the real ones
291: * by using {@link #activeNamespaces}.
292: *
293: * @return
294: * the buffer passed as the <tt>buf</tt> parameter.
295: */
296: private StringBuilder fixPrefix(StringBuilder buf) {
297: assert activeNamespaces != null;
298:
299: int i;
300: int len = buf.length();
301: for (i = 0; i < len; i++)
302: if (buf.charAt(i) == MAGIC)
303: break;
304: // typically it doens't contain any prefix.
305: // just return the original buffer in that case
306: if (i == len)
307: return buf;
308:
309: while (i < len) {
310: char uriIdx = buf.charAt(i + 1);
311: NamespaceDecl ns = activeNamespaces;
312: while (ns != null && ns.uniqueId != uriIdx)
313: ns = ns.next;
314: if (ns == null)
315: throw new IllegalStateException(
316: "Unexpected use of prefixes " + buf);
317:
318: int length = 2;
319: String prefix = ns.prefix;
320: if (prefix.length() == 0) {
321: if (buf.length() <= i + 2 || buf.charAt(i + 2) != ':')
322: throw new IllegalStateException(
323: "Unexpected use of prefixes " + buf);
324: length = 3;
325: }
326:
327: buf.replace(i, i + length, prefix);
328: len += prefix.length() - length;
329:
330: while (i < len && buf.charAt(i) != MAGIC)
331: i++;
332: }
333:
334: return buf;
335: }
336:
337: /**
338: * The first char of the dummy prefix.
339: */
340: static final char MAGIC = '\u0000';
341:
342: char assignNewId() {
343: return (char) iota++;
344: }
345: }
|