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