blob: 5ce064c6d152b0cbd1abe5f5f0fef68da1ba6d5f [file] [log] [blame]
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.xml.internal.txw2;
import javax.xml.namespace.QName;
/**
* Start tag.
*
* <p>
* This object implements {@link NamespaceResolver} for attribute values.
*
* @author Kohsuke Kawaguchi
*/
class StartTag extends Content implements NamespaceResolver {
/**
* Tag name of the element.
*
* <p>
* This field is also used as a flag to indicate
* whether the start tag has been written.
* This field is initially set to non-null, and
* then reset to null when it's written.
*/
private String uri;
// but we keep the local name non-null so that
// we can diagnose an error
private final String localName;
private Attribute firstAtt;
private Attribute lastAtt;
/**
* If this {@link StartTag} has the parent {@link ContainerElement},
* that value. Otherwise null.
*/
private ContainerElement owner;
/**
* Explicitly given namespace declarations on this element.
*
* <p>
* Additional namespace declarations might be necessary to
* generate child {@link QName}s and attributes.
*/
private NamespaceDecl firstNs;
private NamespaceDecl lastNs;
final Document document;
public StartTag(ContainerElement owner, String uri, String localName) {
this(owner.document,uri,localName);
this.owner = owner;
}
public StartTag(Document document, String uri, String localName) {
assert uri!=null;
assert localName!=null;
this.uri = uri;
this.localName = localName;
this.document = document;
// TODO: think about a better way to maintain namespace decls.
// this requires at least one NamespaceDecl per start tag,
// which is rather expensive.
addNamespaceDecl(uri,null,false);
}
public void addAttribute(String nsUri, String localName, Object arg) {
checkWritable();
// look for the existing ones
Attribute a;
for(a=firstAtt; a!=null; a=a.next) {
if(a.hasName(nsUri,localName)) {
break;
}
}
// if not found, declare a new one
if(a==null) {
a = new Attribute(nsUri,localName);
if(lastAtt==null) {
assert firstAtt==null;
firstAtt = lastAtt = a;
} else {
assert firstAtt!=null;
lastAtt.next = a;
lastAtt = a;
}
if(nsUri.length()>0)
addNamespaceDecl(nsUri,null,true);
}
document.writeValue(arg,this,a.value);
}
/**
* Declares a new namespace URI on this tag.
*
* @param uri
* namespace URI to be bound. Can be empty, but must not be null.
* @param prefix
* If non-null and non-empty, this prefix is bound to the URI
* on this element. If empty, then the runtime will still try to
* use the URI as the default namespace, but it may fail to do so
* because of the constraints in the XML.
* <p>
* If this parameter is null, the runtime will allocate an unique prefix.
* @param requirePrefix
* Used only when the prefix parameter is null. If true, this indicates
* that the non-empty prefix must be assigned to this URI. If false,
* then this URI might be used as the default namespace.
* <p>
* Normally you just need to set it to false.
*/
public NamespaceDecl addNamespaceDecl(String uri, String prefix,boolean requirePrefix) {
checkWritable();
if(uri==null)
throw new IllegalArgumentException();
if(uri.length()==0) {
if(requirePrefix)
throw new IllegalArgumentException("The empty namespace cannot have a non-empty prefix");
if(prefix!=null && prefix.length()>0)
throw new IllegalArgumentException("The empty namespace can be only bound to the empty prefix");
prefix = "";
}
// check for the duplicate
for(NamespaceDecl n=firstNs; n!=null; n=n.next) {
if(uri.equals(n.uri)) {
if(prefix==null) {
// reuse this binding
n.requirePrefix |= requirePrefix;
return n;
}
if(n.prefix==null) {
// reuse this binding
n.prefix = prefix;
n.requirePrefix |= requirePrefix;
return n;
}
if(prefix.equals(n.prefix)) {
// reuse this binding
n.requirePrefix |= requirePrefix;
return n;
}
}
if(prefix!=null && n.prefix!=null && n.prefix.equals(prefix))
throw new IllegalArgumentException(
"Prefix '"+prefix+"' is already bound to '"+n.uri+'\'');
}
NamespaceDecl ns = new NamespaceDecl(document.assignNewId(),uri,prefix,requirePrefix);
if(lastNs==null) {
assert firstNs==null;
firstNs = lastNs = ns;
} else {
assert firstNs!=null;
lastNs.next = ns;
lastNs = ns;
}
return ns;
}
/**
* Throws an error if the start tag has already been committed.
*/
private void checkWritable() {
if(isWritten())
throw new IllegalStateException(
"The start tag of "+this.localName+" has already been written. " +
"If you need out of order writing, see the TypedXmlWriter.block method");
}
/**
* Returns true if this start tag has already been written.
*/
boolean isWritten() {
return uri==null;
}
/**
* A {@link StartTag} can be only written after
* we are sure that all the necessary namespace declarations are given.
*/
boolean isReadyToCommit() {
if(owner!=null && owner.isBlocked())
return false;
for( Content c=getNext(); c!=null; c=c.getNext() )
if(c.concludesPendingStartTag())
return true;
return false;
}
public void written() {
firstAtt = lastAtt = null;
uri = null;
if(owner!=null) {
assert owner.startTag==this;
owner.startTag = null;
}
}
boolean concludesPendingStartTag() {
return true;
}
void accept(ContentVisitor visitor) {
visitor.onStartTag(uri,localName,firstAtt,firstNs);
}
public String getPrefix(String nsUri) {
NamespaceDecl ns = addNamespaceDecl(nsUri,null,false);
if(ns.prefix!=null)
// if the prefix has already been declared, use it.
return ns.prefix;
return ns.dummyPrefix;
}
}