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 javax.xml.namespace.QName;
028:
029: /**
030: * Start tag.
031: *
032: * <p>
033: * This object implements {@link NamespaceResolver} for attribute values.
034: *
035: * @author Kohsuke Kawaguchi
036: */
037: class StartTag extends Content implements NamespaceResolver {
038: /**
039: * Tag name of the element.
040: *
041: * <p>
042: * This field is also used as a flag to indicate
043: * whether the start tag has been written.
044: * This field is initially set to non-null, and
045: * then reset to null when it's written.
046: */
047: private String uri;
048: // but we keep the local name non-null so that
049: // we can diagnose an error
050: private final String localName;
051:
052: private Attribute firstAtt;
053: private Attribute lastAtt;
054:
055: /**
056: * If this {@link StartTag} has the parent {@link ContainerElement},
057: * that value. Otherwise null.
058: */
059: private ContainerElement owner;
060:
061: /**
062: * Explicitly given namespace declarations on this element.
063: *
064: * <p>
065: * Additional namespace declarations might be necessary to
066: * generate child {@link QName}s and attributes.
067: */
068: private NamespaceDecl firstNs;
069: private NamespaceDecl lastNs;
070:
071: final Document document;
072:
073: public StartTag(ContainerElement owner, String uri, String localName) {
074: this (owner.document, uri, localName);
075: this .owner = owner;
076: }
077:
078: public StartTag(Document document, String uri, String localName) {
079: assert uri != null;
080: assert localName != null;
081:
082: this .uri = uri;
083: this .localName = localName;
084: this .document = document;
085:
086: // TODO: think about a better way to maintain namespace decls.
087: // this requires at least one NamespaceDecl per start tag,
088: // which is rather expensive.
089: addNamespaceDecl(uri, null, false);
090: }
091:
092: public void addAttribute(String nsUri, String localName, Object arg) {
093: checkWritable();
094:
095: // look for the existing ones
096: Attribute a;
097: for (a = firstAtt; a != null; a = a.next) {
098: if (a.hasName(nsUri, localName)) {
099: break;
100: }
101: }
102:
103: // if not found, declare a new one
104: if (a == null) {
105: a = new Attribute(nsUri, localName);
106: if (lastAtt == null) {
107: assert firstAtt == null;
108: firstAtt = lastAtt = a;
109: } else {
110: assert firstAtt != null;
111: lastAtt.next = a;
112: lastAtt = a;
113: }
114: if (nsUri.length() > 0)
115: addNamespaceDecl(nsUri, null, true);
116: }
117:
118: document.writeValue(arg, this , a.value);
119: }
120:
121: /**
122: * Declares a new namespace URI on this tag.
123: *
124: * @param uri
125: * namespace URI to be bound. Can be empty, but must not be null.
126: * @param prefix
127: * If non-null and non-empty, this prefix is bound to the URI
128: * on this element. If empty, then the runtime will still try to
129: * use the URI as the default namespace, but it may fail to do so
130: * because of the constraints in the XML.
131: * <p>
132: * If this parameter is null, the runtime will allocate an unique prefix.
133: * @param requirePrefix
134: * Used only when the prefix parameter is null. If true, this indicates
135: * that the non-empty prefix must be assigned to this URI. If false,
136: * then this URI might be used as the default namespace.
137: * <p>
138: * Normally you just need to set it to false.
139: */
140: public NamespaceDecl addNamespaceDecl(String uri, String prefix,
141: boolean requirePrefix) {
142: checkWritable();
143:
144: if (uri == null)
145: throw new IllegalArgumentException();
146: if (uri.length() == 0) {
147: if (requirePrefix)
148: throw new IllegalArgumentException(
149: "The empty namespace cannot have a non-empty prefix");
150: if (prefix != null && prefix.length() > 0)
151: throw new IllegalArgumentException(
152: "The empty namespace can be only bound to the empty prefix");
153: prefix = "";
154: }
155:
156: // check for the duplicate
157: for (NamespaceDecl n = firstNs; n != null; n = n.next) {
158: if (uri.equals(n.uri)) {
159: if (prefix == null) {
160: // reuse this binding
161: n.requirePrefix |= requirePrefix;
162: return n;
163: }
164: if (n.prefix == null) {
165: // reuse this binding
166: n.prefix = prefix;
167: n.requirePrefix |= requirePrefix;
168: return n;
169: }
170: if (prefix.equals(n.prefix)) {
171: // reuse this binding
172: n.requirePrefix |= requirePrefix;
173: return n;
174: }
175: }
176: if (prefix != null && n.prefix != null
177: && n.prefix.equals(prefix))
178: throw new IllegalArgumentException("Prefix '" + prefix
179: + "' is already bound to '" + n.uri + '\'');
180: }
181:
182: NamespaceDecl ns = new NamespaceDecl(document.assignNewId(),
183: uri, prefix, requirePrefix);
184: if (lastNs == null) {
185: assert firstNs == null;
186: firstNs = lastNs = ns;
187: } else {
188: assert firstNs != null;
189: lastNs.next = ns;
190: lastNs = ns;
191: }
192: return ns;
193: }
194:
195: /**
196: * Throws an error if the start tag has already been committed.
197: */
198: private void checkWritable() {
199: if (isWritten())
200: throw new IllegalStateException(
201: "The start tag of "
202: + this .localName
203: + " has already been written. "
204: + "If you need out of order writing, see the TypedXmlWriter.block method");
205: }
206:
207: /**
208: * Returns true if this start tag has already been written.
209: */
210: boolean isWritten() {
211: return uri == null;
212: }
213:
214: /**
215: * A {@link StartTag} can be only written after
216: * we are sure that all the necessary namespace declarations are given.
217: */
218: boolean isReadyToCommit() {
219: if (owner != null && owner.isBlocked())
220: return false;
221:
222: for (Content c = getNext(); c != null; c = c.getNext())
223: if (c.concludesPendingStartTag())
224: return true;
225:
226: return false;
227: }
228:
229: public void written() {
230: firstAtt = lastAtt = null;
231: uri = null;
232: if (owner != null) {
233: assert owner.startTag == this ;
234: owner.startTag = null;
235: }
236: }
237:
238: boolean concludesPendingStartTag() {
239: return true;
240: }
241:
242: void accept(ContentVisitor visitor) {
243: visitor.onStartTag(uri, localName, firstAtt, firstNs);
244: }
245:
246: public String getPrefix(String nsUri) {
247: NamespaceDecl ns = addNamespaceDecl(nsUri, null, false);
248: if (ns.prefix != null)
249: // if the prefix has already been declared, use it.
250: return ns.prefix;
251: return ns.dummyPrefix;
252: }
253: }
|