001: /*
002: * The contents of this file are subject to the terms
003: * of the Common Development and Distribution License
004: * (the "License"). You may not use this file except
005: * in compliance with the License.
006: *
007: * You can obtain a copy of the license at
008: * https://jwsdp.dev.java.net/CDDLv1.0.html
009: * See the License for the specific language governing
010: * permissions and limitations under the License.
011: *
012: * When distributing Covered Code, include this CDDL
013: * HEADER in each file and include the License file at
014: * https://jwsdp.dev.java.net/CDDLv1.0.html If applicable,
015: * add the following below this CDDL HEADER, with the
016: * fields enclosed by brackets "[]" replaced with your
017: * own identifying information: Portions Copyright [yyyy]
018: * [name of copyright owner]
019: */
020:
021: package com.sun.xml.txw2;
022:
023: import com.sun.xml.txw2.output.XmlSerializer;
024:
025: import java.util.Map;
026: import java.util.HashMap;
027:
028: /**
029: * Coordinates the entire writing process.
030: *
031: * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
032: */
033: public final class Document {
034:
035: private final XmlSerializer out;
036:
037: /**
038: * Set to true once we invoke {@link XmlSerializer#startDocument()}.
039: *
040: * <p>
041: * This is so that we can defer the writing as much as possible.
042: */
043: private boolean started = false;
044:
045: /**
046: * Currently active writer.
047: *
048: * <p>
049: * This points to the last written token.
050: */
051: private Content current = null;
052:
053: private final Map<Class, DatatypeWriter> datatypeWriters = new HashMap<Class, DatatypeWriter>();
054:
055: /**
056: * Used to generate unique namespace prefix.
057: */
058: private int iota = 1;
059:
060: /**
061: * Used to keep track of in-scope namespace bindings declared in ancestors.
062: */
063: private final NamespaceSupport inscopeNamespace = new NamespaceSupport();
064:
065: /**
066: * Remembers the namespace declarations of the last unclosed start tag,
067: * so that we can fix up dummy prefixes in {@link Pcdata}.
068: */
069: private NamespaceDecl activeNamespaces;
070:
071: Document(XmlSerializer out) {
072: this .out = out;
073: for (DatatypeWriter dw : DatatypeWriter.BUILDIN)
074: datatypeWriters.put(dw.getType(), dw);
075: }
076:
077: void flush() {
078: out.flush();
079: }
080:
081: void setFirstContent(Content c) {
082: assert current == null;
083: current = new StartDocument();
084: current.setNext(this , c);
085: }
086:
087: /**
088: * Defines additional user object -> string conversion logic.
089: *
090: * <p>
091: * Applications can add their own {@link DatatypeWriter} so that
092: * application-specific objects can be turned into {@link String}
093: * for output.
094: *
095: * @param dw
096: * The {@link DatatypeWriter} to be added. Must not be null.
097: */
098: public void addDatatypeWriter(DatatypeWriter<?> dw) {
099: datatypeWriters.put(dw.getType(), dw);
100: }
101:
102: /**
103: * Performs the output as much as possible
104: */
105: void run() {
106: while (true) {
107: Content next = current.getNext();
108: if (next == null || !next.isReadyToCommit())
109: return;
110: next.accept(visitor);
111: next.written();
112: current = next;
113: }
114: }
115:
116: /**
117: * Appends the given object to the end of the given buffer.
118: *
119: * @param nsResolver
120: * use
121: */
122: void writeValue(Object obj, NamespaceResolver nsResolver,
123: StringBuilder buf) {
124: if (obj == null)
125: throw new IllegalArgumentException("argument contains null");
126:
127: if (obj instanceof Object[]) {
128: for (Object o : (Object[]) obj)
129: writeValue(o, nsResolver, buf);
130: return;
131: }
132: if (obj instanceof Iterable) {
133: for (Object o : (Iterable<?>) obj)
134: writeValue(o, nsResolver, buf);
135: return;
136: }
137:
138: if (buf.length() > 0)
139: buf.append(' ');
140:
141: Class c = obj.getClass();
142: while (c != null) {
143: DatatypeWriter dw = datatypeWriters.get(c);
144: if (dw != null) {
145: dw.print(obj, nsResolver, buf);
146: return;
147: }
148: c = c.getSuperclass();
149: }
150:
151: // if nothing applies, just use toString
152: buf.append(obj);
153: }
154:
155: // I wanted to hide those write method from users
156: private final ContentVisitor visitor = new ContentVisitor() {
157: public void onStartDocument() {
158: // the startDocument token is used as the sentry, so this method shall never
159: // be called.
160: // out.startDocument() is invoked when we write the start tag of the root element.
161: throw new IllegalStateException();
162: }
163:
164: public void onEndDocument() {
165: out.endDocument();
166: }
167:
168: public void onEndTag() {
169: out.endTag();
170: inscopeNamespace.popContext();
171: activeNamespaces = null;
172: }
173:
174: public void onPcdata(StringBuilder buffer) {
175: if (activeNamespaces != null)
176: buffer = fixPrefix(buffer);
177: out.text(buffer);
178: }
179:
180: public void onCdata(StringBuilder buffer) {
181: if (activeNamespaces != null)
182: buffer = fixPrefix(buffer);
183: out.cdata(buffer);
184: }
185:
186: public void onComment(StringBuilder buffer) {
187: if (activeNamespaces != null)
188: buffer = fixPrefix(buffer);
189: out.comment(buffer);
190: }
191:
192: public void onStartTag(String nsUri, String localName,
193: Attribute attributes, NamespaceDecl namespaces) {
194: assert nsUri != null;
195: assert localName != null;
196:
197: activeNamespaces = namespaces;
198:
199: if (!started) {
200: started = true;
201: out.startDocument();
202: }
203:
204: inscopeNamespace.pushContext();
205:
206: // declare the explicitly bound namespaces
207: for (NamespaceDecl ns = namespaces; ns != null; ns = ns.next) {
208: ns.declared = false; // reset this flag
209:
210: if (ns.prefix != null) {
211: String uri = inscopeNamespace.getURI(ns.prefix);
212: if (uri != null && uri.equals(ns.uri))
213: ; // already declared
214: else {
215: // declare this new binding
216: inscopeNamespace.declarePrefix(ns.prefix,
217: ns.uri);
218: ns.declared = true;
219: }
220: }
221: }
222:
223: // then use in-scope namespace to assign prefixes to others
224: for (NamespaceDecl ns = namespaces; ns != null; ns = ns.next) {
225: if (ns.prefix == null) {
226: if (inscopeNamespace.getURI("").equals(ns.uri))
227: ns.prefix = "";
228: else {
229: String p = inscopeNamespace.getPrefix(ns.uri);
230: if (p == null) {
231: // assign a new one
232: while (inscopeNamespace
233: .getURI(p = newPrefix()) != null)
234: ;
235: ns.declared = true;
236: inscopeNamespace.declarePrefix(p, ns.uri);
237: }
238: ns.prefix = p;
239: }
240: }
241: }
242:
243: // the first namespace decl must be the one for the element
244: assert namespaces.uri.equals(nsUri);
245: assert namespaces.prefix != null : "a prefix must have been all allocated";
246: out.beginStartTag(nsUri, localName, namespaces.prefix);
247:
248: // declare namespaces
249: for (NamespaceDecl ns = namespaces; ns != null; ns = ns.next) {
250: if (ns.declared)
251: out.writeXmlns(ns.prefix, ns.uri);
252: }
253:
254: // writeBody attributes
255: for (Attribute a = attributes; a != null; a = a.next) {
256: String prefix;
257: if (a.nsUri.length() == 0)
258: prefix = "";
259: else
260: prefix = inscopeNamespace.getPrefix(a.nsUri);
261: out.writeAttribute(a.nsUri, a.localName, prefix,
262: fixPrefix(a.value));
263: }
264:
265: out.endStartTag(nsUri, localName, namespaces.prefix);
266: }
267: };
268:
269: /**
270: * Used by {@link #newPrefix()}.
271: */
272: private final StringBuilder prefixSeed = new StringBuilder("ns");
273:
274: private int prefixIota = 0;
275:
276: /**
277: * Allocates a new unique prefix.
278: */
279: private String newPrefix() {
280: prefixSeed.setLength(2);
281: prefixSeed.append(++prefixIota);
282: return prefixSeed.toString();
283: }
284:
285: /**
286: * Replaces dummy prefixes in the value to the real ones
287: * by using {@link #activeNamespaces}.
288: *
289: * @return
290: * the buffer passed as the <tt>buf</tt> parameter.
291: */
292: private StringBuilder fixPrefix(StringBuilder buf) {
293: assert activeNamespaces != null;
294:
295: int i;
296: int len = buf.length();
297: for (i = 0; i < len; i++)
298: if (buf.charAt(i) == MAGIC)
299: break;
300: // typically it doens't contain any prefix.
301: // just return the original buffer in that case
302: if (i == len)
303: return buf;
304:
305: while (i < len) {
306: char uriIdx = buf.charAt(i + 1);
307: NamespaceDecl ns = activeNamespaces;
308: while (ns != null && ns.uniqueId != uriIdx)
309: ns = ns.next;
310: if (ns == null)
311: throw new IllegalStateException(
312: "Unexpected use of prefixes " + buf);
313:
314: int length = 2;
315: String prefix = ns.prefix;
316: if (prefix.length() == 0) {
317: if (buf.length() <= i + 2 || buf.charAt(i + 2) != ':')
318: throw new IllegalStateException(
319: "Unexpected use of prefixes " + buf);
320: length = 3;
321: }
322:
323: buf.replace(i, i + length, prefix);
324: len += prefix.length() - length;
325:
326: while (i < len && buf.charAt(i) != MAGIC)
327: i++;
328: }
329:
330: return buf;
331: }
332:
333: /**
334: * The first char of the dummy prefix.
335: */
336: static final char MAGIC = '\u0000';
337:
338: char assignNewId() {
339: return (char) iota++;
340: }
341: }
|