All Downloads are FREE. Search and download functionalities are using the official Maven repository.

uk.org.retep.util.xml.XMLStreamParser Maven / Gradle / Ivy

/*
 * 

Copyright (c) 1998-2010, Peter T Mount
* All rights reserved.

* *

Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met:

* *
    *
  • Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer.
  • * *
  • Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution.
  • * *
  • Neither the name of the retep.org.uk nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission.
  • * *
* *

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/ package uk.org.retep.util.xml; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.xml.parsers.ParserConfigurationException; import javax.xml.stream.FactoryConfigurationError; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.annotation.concurrent.ThreadSafe; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import uk.org.retep.annotations.ReadLock; import uk.org.retep.annotations.WriteLock; import uk.org.retep.util.concurrent.ConcurrencySupport; /** * A class that can read from an XMLStreamReader, dispatching a Node when it has * been read at a specific level within a Document. * *

* For example with a depth of two then when the following is parsed: * <myRoot><myNode/></myRoot> * Then the listeners will be notified of the Node representing myNode, including * any child nodes. *

* *

* This is useful when implementing XML based protocols like XMPP/Jabber. *

* * @param Type of the XMLStreamParser * @author peter * @since 9.4 */ @ThreadSafe public class XMLStreamParser extends ConcurrencySupport { private static final int DEFAULT_DEPTH = 2; private final Set> listeners; private Listener[] listenerCache; private final Map nameSpaceMap; private final Document document; private Node node; private final int triggerDepth; private int depth; private boolean triggerRootNode = false; private int appendDepth = 1; public XMLStreamParser( final Document document ) throws ParserConfigurationException, IllegalArgumentException, XMLStreamException, FactoryConfigurationError { this( DEFAULT_DEPTH, document ); } public XMLStreamParser( final int depth, final Document document ) throws ParserConfigurationException, IllegalArgumentException, XMLStreamException, FactoryConfigurationError { this.triggerDepth = depth; appendDepth = Math.max( appendDepth, this.triggerDepth-1 ); this.document = document; nameSpaceMap = new HashMap(); listeners = new HashSet>(); updateCache(); } @ReadLock public boolean isTriggerRootNode() { return triggerRootNode; } @WriteLock public void setTriggerRootNode( boolean triggerRootNode ) { this.triggerRootNode = triggerRootNode; } @WriteLock public boolean addListener( Listener l ) { try { return listeners.add( l ); } finally { updateCache(); } } @WriteLock public boolean removeListener( Listener l ) { try { return listeners.remove( l ); } finally { updateCache(); } } @WriteLock public void removeAllListeners() { try { listeners.clear(); } finally { updateCache(); } } @SuppressWarnings( "unchecked" ) private void updateCache() { listenerCache = listeners.toArray( new Listener[ listeners.size() ] ); } @ReadLock public Listener[] listeners() { return listenerCache; } public void setNameSpaceSubstitution( String orig, String newValue ) { writeLock().lock(); try { nameSpaceMap.put( orig, newValue ); } finally { writeLock().unlock(); } } /** * The current append depth. If serialising the entire stream, this should * be 1 but it determines the point from the root where nodes start being * appended to their parent's, or just replaced. * * For example, with XMPP this should be set to 2, as you want to process * the root and then each node beneath the root, with no association between * them. * * @return appendDepth, always >= 1 */ @ReadLock public int getAppendDepth() { return appendDepth; } /** * The current append depth. If serialising the entire stream, this should * be 1 but it determines the point from the root where nodes start being * appended to their parent's, or just replaced. * * For example, with XMPP this should be set to 2, as you want to process * the root and then each node beneath the root, with no association between * them. * * @param appendDepth always >= 1 * @throws IllegalArgumentException if appendDepth is < 1 */ @WriteLock public void setAppendDepth( final int appendDepth ) { if( appendDepth < 1 ) { throw new IllegalArgumentException( "appendDepth must be >= 1" ); } this.appendDepth = appendDepth; } public void processEvents( final XMLStreamReader xmlStreamReader) throws XMLStreamException { if( xmlStreamReader.hasNext() ) { switch( xmlStreamReader.next() ) { case XMLStreamConstants.START_ELEMENT: depth++; Element e = null; if( xmlStreamReader.getNamespaceURI() == null ) { e = document.createElement( xmlStreamReader.getLocalName() ); } else { String ns = nameSpaceMap.get( xmlStreamReader.getNamespaceURI() ); e = document.createElementNS( ns == null ? xmlStreamReader.getNamespaceURI() : ns, xmlStreamReader.getLocalName() ); } e.setPrefix( xmlStreamReader.getPrefix() ); for( int i = 0; i < xmlStreamReader.getAttributeCount(); i++ ) { e.setAttributeNS( xmlStreamReader.getAttributeNamespace( i ), xmlStreamReader.getAttributeLocalName( i ), xmlStreamReader.getAttributeValue( i ) ); } if( depth > appendDepth ) { node.appendChild( e ); } node = e; if( triggerRootNode && depth == 1 && triggerDepth > 1 ) { notifyNode( node ); } break; case XMLStreamConstants.END_ELEMENT: if( depth == triggerDepth ) { notifyNode( node ); } else { node = node.getParentNode(); } depth--; break; case XMLStreamConstants.CHARACTERS: node.appendChild( document.createTextNode( xmlStreamReader.getText() ) ); break; default: break; } } } @SuppressWarnings( "unchecked" ) protected final void notifyNode( final Node node ) { for( Listener l : listeners() ) { l.STaXThreadNodeReceived( (T) this, node ); } } @SuppressWarnings( "unchecked" ) public final void notifyStreamClosed() { for( Listener l : listeners() ) { l.STaXThreadClosed( (T) this ); } } @SuppressWarnings( "unchecked" ) public final void notifyStreamError( final Exception ex ) { for( Listener l : listeners() ) { l.STaXThreadError( (T) this, ex ); } } public static interface Listener { /** * Called when the thread closes normally * @param t XMLStreamParser for the event */ void STaXThreadClosed( T t ); /** * Called when the thread closes due to an exception * @param t XMLStreamParser for the event * @param e Exception of the error */ void STaXThreadError( T t, Exception e ); /** * Called when a complete node at the trigger level (or also the root * if triggerRootNode is true) * @param t XMLStreamParser for the event * @param n Node to process */ void STaXThreadNodeReceived( T t, Node n ); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy