FileDocCategorySizeDatePackage
DOMStreamReader.javaAPI DocGlassfish v2 API32616Fri May 04 22:30:28 BST 2007com.sun.enterprise.jbi.serviceengine.util

DOMStreamReader

public final class DOMStreamReader extends Object implements XMLStreamReader, NamespaceContext
Create an {@link XMLStreamReader} on top of a DOM tree.

Since various libraries as well as users often create "incorrect" DOM node, this class spends a lot of efforts making sure that broken DOM trees are nevertheless interpreted correctly.

For example, if a DOM level 1 tree is passed, each method will attempt to return the correct value by using {@link Node#getNodeName()}.

Similarly, if DOM is missing explicit namespace declarations, this class attempts to emulate necessary declarations.

author
Santiago.PericasGeertsen@sun.com
author
Kohsuke Kawaguchi

Fields Summary
private Node
_current
Current DOM node being traversed.
private Node
_start
Starting node of the subtree being traversed.
private NamedNodeMap
_namedNodeMap
Named mapping for attributes and NS decls for the current node.
private String
wholeText
If the reader points at {@link #CHARACTERS the text node}, its whole value.

This is simply a cache of {@link Text#getWholeText()} of {@link #_current}, but when a large binary data sent as base64 text, this could get very much non-trivial.

private final ArrayList
_currentAttributes
List of attributes extracted from _namedNodeMap.
private Scope[]
scopes
{@link Scope} buffer.
private int
depth
Depth of the current element. The first element gets depth==0. Also used as the index to {@link #scopes}.
boolean
_needAttributesSplit
Flag indicating if {@link #_namedNodeMap} is already split into {@link #_currentAttributes} and {@link Scope#currentNamespaces}.
int
_state
State of this reader. Any of the valid states defined in StAX' XMLStreamConstants class.
private static Location
dummyLocation
Constructors Summary
public DOMStreamReader()

    
public DOMStreamReader(Node node)

        setCurrentNode(node);
    
Methods Summary
private int_next()

        Node child;
        
        // Indicate that attributes still need processing
        _needAttributesSplit = true;
        
        switch (_state) {
            case END_DOCUMENT:
                throw new IllegalStateException("DOMStreamReader: Calling next() at END_DOCUMENT");
            case START_DOCUMENT:
                // Don't skip document element if this is a fragment
                if (_current.getNodeType() == ELEMENT_NODE) {
                    return (_state = START_ELEMENT);
                }
                
                child = _current.getFirstChild();
                if (child == null) {
                    return (_state = END_DOCUMENT);
                } else {
                    _current = child;
                    return (_state = mapNodeTypeToState(_current.getNodeType()));
                }
            case START_ELEMENT:
                depth++;
                
                child = _current.getFirstChild();
                if (child == null) {
                    return (_state = END_ELEMENT);
                } else {
                    _current = child;
                    return (_state = mapNodeTypeToState(_current.getNodeType()));
                }
            case END_ELEMENT:
                depth--;
                // fall through next
            case CHARACTERS:
            case COMMENT:
            case CDATA:
            case ENTITY_REFERENCE:
            case PROCESSING_INSTRUCTION:
                // If at the end of this fragment, then terminate traversal
                if (_current == _start) {
                    return (_state = END_DOCUMENT);
                }
                
                Node sibling = _current.getNextSibling();
                if (sibling == null) {
                    _current = _current.getParentNode();
                    // getParentNode() returns null for fragments
                    _state = (_current == null || _current.getNodeType() == DOCUMENT_NODE) ?
                        END_DOCUMENT : END_ELEMENT;
                    return _state;
                } else {
                    _current = sibling;
                    return (_state = mapNodeTypeToState(_current.getNodeType()));
                }
            case DTD:
            case ATTRIBUTE:
            case NAMESPACE:
            default:
                throw new RuntimeException("DOMStreamReader: Unexpected internal state");
        }
    
private com.sun.enterprise.jbi.serviceengine.util.DOMStreamReader$ScopeallocateScope()
Allocate new {@link Scope} for {@link #splitAttributes()}.

        if(scopes.length==depth) {
            Scope[] newBuf = new Scope[scopes.length*2];
            System.arraycopy(scopes,0,newBuf,0,scopes.length);
            scopes = newBuf;
        }
        Scope scope = scopes[depth];
        if(scope==null) {
            scope = scopes[depth] = new Scope(scopes[depth-1]);
        } else {
            scope.reset();
        }
        return scope;
    
public voidclose()

    
private static voiddisplayDOM(org.w3c.dom.Node node, java.io.OutputStream ostream)

        try {
            System.out.println("\n====\n");
            javax.xml.transform.TransformerFactory.newInstance().newTransformer().transform(
                new javax.xml.transform.dom.DOMSource(node),
                new javax.xml.transform.stream.StreamResult(ostream));
            System.out.println("\n====\n");
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    
private voidensureNs(org.w3c.dom.Node n)
Sub-routine of {@link #splitAttributes()}.

Makes sure that the namespace URI/prefix used in the given node is available, and if not, declare it on the current scope to "fix" it. It's often common to create DOM trees without putting namespace declarations, and this makes sure that such DOM tree will be properly marshalled.

        String prefix = fixNull(n.getPrefix());
        String uri = fixNull(n.getNamespaceURI());
        
        Scope scope = scopes[depth];
        
        String currentUri = scope.getNamespaceURI(prefix);
        
        if(prefix.length()==0) {
            currentUri = fixNull(currentUri);
            if(currentUri.equals(uri))
                return; // declared correctly
        } else {
            if(currentUri!=null && currentUri.equals(uri))
                return; // declared correctly
        }
        
        // needs to be declared
        
        scope.additionalNamespaces.add(prefix);
        scope.additionalNamespaces.add(uri);
    
private org.w3c.dom.NodefindRootElement()
Finds the root element node of the traversal.

        int type;
        
        Node node = _start;
        while ((type = node.getNodeType()) != DOCUMENT_NODE
                && type != ELEMENT_NODE) {
            node = node.getParentNode();
        }
        return node;
    
private static java.lang.StringfixNull(java.lang.String s)

        if(s==null) return "";
        else        return s;
    
public intgetAttributeCount()

        if (_state == START_ELEMENT) {
            splitAttributes();
            return _currentAttributes.size();
        }
        throw new IllegalStateException("DOMStreamReader: getAttributeCount() called in illegal state");
    
public java.lang.StringgetAttributeLocalName(int index)
Return an attribute's local name. Handle the case of DOM level 1 nodes.

        if (_state == START_ELEMENT) {
            splitAttributes();
            
            String localName = _currentAttributes.get(index).getLocalName();
            return (localName != null) ? localName :
                QName.valueOf(_currentAttributes.get(index).getNodeName()).getLocalPart();
        }
        throw new IllegalStateException("DOMStreamReader: getAttributeLocalName() called in illegal state");
    
public javax.xml.namespace.QNamegetAttributeName(int index)
Return an attribute's qname. Handle the case of DOM level 1 nodes.

        if (_state == START_ELEMENT) {
            splitAttributes();
            
            Node attr = _currentAttributes.get(index);
            String localName = attr.getLocalName();
            if (localName != null) {
                String prefix = attr.getPrefix();
                String uri = attr.getNamespaceURI();
                return new QName(fixNull(uri), localName, fixNull(prefix));
            } else {
                return QName.valueOf(attr.getNodeName());
            }
        }
        throw new IllegalStateException("DOMStreamReader: getAttributeName() called in illegal state");
    
public java.lang.StringgetAttributeNamespace(int index)

        if (_state == START_ELEMENT) {
            splitAttributes();
            String uri = _currentAttributes.get(index).getNamespaceURI();
            return fixNull(uri);
        }
        throw new IllegalStateException("DOMStreamReader: getAttributeNamespace() called in illegal state");
    
public java.lang.StringgetAttributePrefix(int index)

        if (_state == START_ELEMENT) {
            splitAttributes();
            String prefix = _currentAttributes.get(index).getPrefix();
            return fixNull(prefix);
        }
        throw new IllegalStateException("DOMStreamReader: getAttributePrefix() called in illegal state");
    
public java.lang.StringgetAttributeType(int index)

        if (_state == START_ELEMENT) {
            return "CDATA";
        }
        throw new IllegalStateException("DOMStreamReader: getAttributeType() called in illegal state");
    
public java.lang.StringgetAttributeValue(int index)

        if (_state == START_ELEMENT) {
            splitAttributes();
            return _currentAttributes.get(index).getNodeValue();
        }
        throw new IllegalStateException("DOMStreamReader: getAttributeValue() called in illegal state");
    
public java.lang.StringgetAttributeValue(java.lang.String namespaceURI, java.lang.String localName)

        if (_state == START_ELEMENT) {
            splitAttributes();
            if (_namedNodeMap != null) {
                Node attr = _namedNodeMap.getNamedItemNS(namespaceURI, localName);
                return attr != null ? attr.getNodeValue() : null;
            }
            return null;
        }
        throw new IllegalStateException("DOMStreamReader: getAttributeValue() called in illegal state");
    
public java.lang.StringgetCharacterEncodingScheme()

        return null;
    
private com.sun.enterprise.jbi.serviceengine.util.DOMStreamReader$ScopegetCheckedScope()
Verifies the current state to see if we can return the scope, and do so if appropriate. Used to implement a bunch of StAX API methods that have the same usage restriction.

        if (_state == START_ELEMENT || _state == END_ELEMENT) {
            splitAttributes();
            return scopes[depth];
        }
        throw new IllegalStateException("DOMStreamReader: neither on START_ELEMENT nor END_ELEMENT");
    
public java.lang.StringgetElementText()

        throw new RuntimeException("DOMStreamReader: getElementText() not implemented");
    
public java.lang.StringgetEncoding()

        return null;
    
public intgetEventType()

        return _state;
    
public java.lang.StringgetLocalName()
Return an element's local name. Handle the case of DOM level 1 nodes.

        if (_state == START_ELEMENT || _state == END_ELEMENT) {
            String localName = _current.getLocalName();
            return localName != null ? localName :
                QName.valueOf(_current.getNodeName()).getLocalPart();
        } else if (_state == ENTITY_REFERENCE) {
            return _current.getNodeName();
        }
        throw new IllegalStateException("DOMStreamReader: getAttributeValue() called in illegal state");
    
public javax.xml.stream.LocationgetLocation()

        return dummyLocation;
    
public javax.xml.namespace.QNamegetName()
Return an element's qname. Handle the case of DOM level 1 nodes.

        if (_state == START_ELEMENT || _state == END_ELEMENT) {
            String localName = _current.getLocalName();
            if (localName != null) {
                String prefix = _current.getPrefix();
                String uri = _current.getNamespaceURI();
                return new QName(fixNull(uri), localName, fixNull(prefix));
            } else {
                return QName.valueOf(_current.getNodeName());
            }
        }
        throw new IllegalStateException("DOMStreamReader: getName() called in illegal state");
    
public javax.xml.namespace.NamespaceContextgetNamespaceContext()

        return this;
    
public intgetNamespaceCount()

        return getCheckedScope().getNamespaceCount();
    
public java.lang.StringgetNamespacePrefix(int index)

        return getCheckedScope().getNamespacePrefix(index);
    
public java.lang.StringgetNamespaceURI(int index)

        return getCheckedScope().getNamespaceURI(index);
    
public java.lang.StringgetNamespaceURI()

        if (_state == START_ELEMENT || _state == END_ELEMENT) {
            String uri = _current.getNamespaceURI();
            return fixNull(uri);
        }
        return null;
    
public java.lang.StringgetNamespaceURI(java.lang.String prefix)
This method is not particularly fast, but shouldn't be called very often. If we start to use it more, we should keep track of the NS declarations using a NamespaceContext implementation instead.

        if (prefix == null) {
            throw new IllegalArgumentException("DOMStreamReader: getNamespaceURI(String) call with a null prefix");
        } else if (prefix.equals("xml")) {
            return "http://www.w3.org/XML/1998/namespace";
        } else if (prefix.equals("xmlns")) {
            return "http://www.w3.org/2000/xmlns/";
        }
        
        // check scopes
        splitAttributes();
        String nsUri = scopes[depth].getNamespaceURI(prefix);
        if(nsUri!=null)    return nsUri;
        
        // then ancestors above start node
        Node node = findRootElement();
        String nsDeclName = prefix.length()==0 ? "xmlns" : "xmlns:"+prefix;
        while (node.getNodeType() != DOCUMENT_NODE) {
            // Is ns declaration on this element?
            NamedNodeMap namedNodeMap = node.getAttributes();
            Attr attr = (Attr) namedNodeMap.getNamedItem(nsDeclName);
            if (attr != null)
                return attr.getValue();
            node = node.getParentNode();
        }
        return null;
    
public java.lang.StringgetPIData()

        if (_state == PROCESSING_INSTRUCTION) {
            return ((ProcessingInstruction) _current).getData();
        }
        return null;
    
public java.lang.StringgetPITarget()

        if (_state == PROCESSING_INSTRUCTION) {
            return ((ProcessingInstruction) _current).getTarget();
        }
        return null;
    
public java.lang.StringgetPrefix(java.lang.String nsUri)

        if (nsUri == null) {
            throw new IllegalArgumentException("DOMStreamReader: getPrefix(String) call with a null namespace URI");
        } else if (nsUri.equals("http://www.w3.org/XML/1998/namespace")) {
            return "xml";
        } else if (nsUri.equals("http://www.w3.org/2000/xmlns/")) {
            return "xmlns";
        }
        
        // check scopes
        splitAttributes();
        String prefix = scopes[depth].getPrefix(nsUri);
        if(prefix!=null)    return prefix;
        
        // then ancestors above start node
        Node node = findRootElement();
        
        while (node.getNodeType() != DOCUMENT_NODE) {
            // Is ns declaration on this element?
            NamedNodeMap namedNodeMap = node.getAttributes();
            for( int i=namedNodeMap.getLength()-1; i>=0; i-- ) {
                Attr attr = (Attr)namedNodeMap.item(i);
                prefix = getPrefixForAttr(attr,nsUri);
                if(prefix!=null)
                    return prefix;
            }
            node = node.getParentNode();
        }
        return null;
    
public java.lang.StringgetPrefix()

        if (_state == START_ELEMENT || _state == END_ELEMENT) {
            String prefix = _current.getPrefix();
            return fixNull(prefix);
        }
        return null;
    
private static java.lang.StringgetPrefixForAttr(org.w3c.dom.Attr attr, java.lang.String nsUri)
If the given attribute is a namespace declaration for the given namespace URI, return its prefix. Otherwise null.

        String attrName = attr.getNodeName();
        if (!attrName.startsWith("xmlns:") && !attrName.equals("xmlns"))
            return null;    // not nsdecl
        
        if(attr.getValue().equals(nsUri)) {
            if(attrName.equals("xmlns"))
                return "";
            String localName = attr.getLocalName();
            return (localName != null) ? localName :
                QName.valueOf(attrName).getLocalPart();
        }
        
        return null;
    
public java.util.IteratorgetPrefixes(java.lang.String nsUri)

        // This is an incorrect implementation,
        // but AFAIK it's not used in the JAX-WS runtime
        String prefix = getPrefix(nsUri);
        if(prefix==null)    return Collections.emptyList().iterator();
        else                return Collections.singletonList(prefix).iterator();
    
public java.lang.ObjectgetProperty(java.lang.String str)

        return null;
    
public java.lang.StringgetText()

        if (_state == CHARACTERS)
            return wholeText;
        if(_state == CDATA || _state == COMMENT || _state == ENTITY_REFERENCE)
            return _current.getNodeValue();
        throw new IllegalStateException("DOMStreamReader: getTextLength() called in illegal state");
    
public char[]getTextCharacters()

        return getText().toCharArray();
    
public intgetTextCharacters(int sourceStart, char[] target, int targetStart, int targetLength)

        String text = getText();
        int copiedSize = Math.min(targetLength, text.length() - sourceStart);
        text.getChars(sourceStart, sourceStart + copiedSize, target, targetStart);
        
        return copiedSize;
    
public intgetTextLength()

        return getText().length();
    
public intgetTextStart()

        if (_state == CHARACTERS || _state == CDATA || _state == COMMENT || _state == ENTITY_REFERENCE) {
            return 0;
        }
        throw new IllegalStateException("DOMStreamReader: getTextStart() called in illegal state");
    
public java.lang.StringgetVersion()

        return null;
    
public booleanhasName()

        return (_state == START_ELEMENT || _state == END_ELEMENT);
    
public booleanhasNext()

        return (_state != END_DOCUMENT);
    
public booleanhasText()

        if (_state == CHARACTERS || _state == CDATA || _state == COMMENT || _state == ENTITY_REFERENCE) {
            return getText().trim().length() > 0;
        }
        return false;
    
public booleanisAttributeSpecified(int param)

        return false;
    
public booleanisCharacters()

        return (_state == CHARACTERS);
    
public booleanisEndElement()

        return (_state == END_ELEMENT);
    
public booleanisStandalone()

        return true;
    
public booleanisStartElement()

        return (_state == START_ELEMENT);
    
public booleanisWhiteSpace()

        if (_state == CHARACTERS || _state == CDATA)
            return getText().trim().length()==0;
        return false;
    
private static intmapNodeTypeToState(int nodetype)

        switch (nodetype) {
            case CDATA_SECTION_NODE:
                return CDATA;
            case COMMENT_NODE:
                return COMMENT;
            case ELEMENT_NODE:
                return START_ELEMENT;
            case ENTITY_NODE:
                return ENTITY_DECLARATION;
            case ENTITY_REFERENCE_NODE:
                return ENTITY_REFERENCE;
            case NOTATION_NODE:
                return NOTATION_DECLARATION;
            case PROCESSING_INSTRUCTION_NODE:
                return PROCESSING_INSTRUCTION;
            case TEXT_NODE:
                return CHARACTERS;
            default:
                throw new RuntimeException("DOMStreamReader: Unexpected node type");
        }
    
public intnext()

        while(true) {
            int r = _next();
            if(r!=CHARACTERS)   return r;
            
            // if we are currently at text node, make sure that this is a meaningful text node.
            Node prev = _current.getPreviousSibling();
            if(prev!=null && prev.getNodeType()==Node.TEXT_NODE)
                continue;   // nope. this is just a continuation of previous text that should be invisible
            
            Text t = (Text)_current;
            wholeText = t.getWholeText();
            if(wholeText.length()==0)
                continue;   // nope. this is empty text.
            return CHARACTERS;
        }
    
public intnextTag()

        int eventType = next();
        while (eventType == CHARACTERS && isWhiteSpace()
        || eventType == CDATA && isWhiteSpace()
        || eventType == SPACE
                || eventType == PROCESSING_INSTRUCTION
                || eventType == COMMENT) {
            eventType = next();
        }
        if (eventType != START_ELEMENT && eventType != END_ELEMENT) {
            throw new XMLStreamException("DOMStreamReader: Expected start or end tag");
        }
        return eventType;
    
public voidrequire(int type, java.lang.String namespaceURI, java.lang.String localName)

        if (type != _state) {
            throw new XMLStreamException("DOMStreamReader: Required event type not found");
        }
        if (namespaceURI != null && !namespaceURI.equals(getNamespaceURI())) {
            throw new XMLStreamException("DOMStreamReader: Required namespaceURI not found");
        }
        if (localName != null && !localName.equals(getLocalName())) {
            throw new XMLStreamException("DOMStreamReader: Required localName not found");
        }
    
public voidsetCurrentNode(org.w3c.dom.Node node)

        scopes[0] = new Scope(null);
        depth=0;
        
        _start = _current = node;
        _state = START_ELEMENT;
        // verifyDOMIntegrity(node);
        // displayDOM(node, System.out);
    
private voidsplitAttributes()
Called when the current node is {@link Element} to look at attribute list (which contains both ns decl and attributes in DOM) and split them to attributes-proper and namespace decls.

        if (!_needAttributesSplit) return;
        _needAttributesSplit = false;
        
        // Clear attribute and namespace lists
        _currentAttributes.clear();
        
        Scope scope = allocateScope();
        
        _namedNodeMap = _current.getAttributes();
        if (_namedNodeMap != null) {
            final int n = _namedNodeMap.getLength();
            for (int i = 0; i < n; i++) {
                final Attr attr = (Attr) _namedNodeMap.item(i);
                final String attrName = attr.getNodeName();
                if (attrName.startsWith("xmlns:") || attrName.equals("xmlns")) {     // NS decl?
                    scope.currentNamespaces.add(attr);
                } else {
                    _currentAttributes.add(attr);
                }
            }
        }
        
        // verify that all the namespaces used in element and attributes are indeed available
        ensureNs(_current);
        for( int i=_currentAttributes.size()-1; i>=0; i-- ) {
            Attr a = _currentAttributes.get(i);
            ensureNs(a);
        }
    
public booleanstandaloneSet()

        return true;
    
private static voidverifyDOMIntegrity(org.w3c.dom.Node node)

        switch (node.getNodeType()) {
            case ELEMENT_NODE:
            case ATTRIBUTE_NODE:
                
                // DOM level 1?
                if (node.getLocalName() == null) {
                    System.out.println("WARNING: DOM level 1 node found");
                    System.out.println(" -> node.getNodeName() = " + node.getNodeName());
                    System.out.println(" -> node.getNamespaceURI() = " + node.getNamespaceURI());
                    System.out.println(" -> node.getLocalName() = " + node.getLocalName());
                    System.out.println(" -> node.getPrefix() = " + node.getPrefix());
                }
                
                if (node.getNodeType() == ATTRIBUTE_NODE) return;
                
                NamedNodeMap attrs = node.getAttributes();
                for (int i = 0; i < attrs.getLength(); i++) {
                    verifyDOMIntegrity(attrs.item(i));
                }
            case DOCUMENT_NODE:
                NodeList children = node.getChildNodes();
                for (int i = 0; i < children.getLength(); i++) {
                    verifyDOMIntegrity(children.item(i));
                }
        }