/*
* Copyright 2008 Lockheed Martin Corporation, except as stated in the file
* entitled Licensing-Information.
*
* All modifications copyright 2009-2015 Data Access Technologies, Inc.
*
* Licensed under the Academic Free License
* version 3.0 (http://www.opensource.org/licenses/afl-3.0.php), except as stated
* in the file entitled Licensing-Information.
*
* Contributors:
* MDS - initial API and implementation
*
*/
package org.modeldriven.fuml.xmi.stream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import javanet.staxutils.events.EventAllocator;
import javax.xml.namespace.QName;
import javax.xml.stream.Location;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLResolver;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.events.XMLEvent;
import javax.xml.stream.util.XMLEventAllocator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.modeldriven.fuml.xmi.XmiConstants;
import org.modeldriven.fuml.xmi.XmiException;
import org.modeldriven.fuml.xmi.XmiReader;
/**
* Stream based XmiReader implementation utilizing the StAX parser. For more information
* on the StAX parser, see the package-level documentation. Only start-element,
* end-element and character(s) events are processed, other events being ignored.
* Certain start-element events are also ignored based on namespace-sensitive configuration
* information. Events of interest are allocated from the stream into immutable nodes
* and arranged into a hierarchy for ease of processing by interested clients. Clients
* are notified using a standard event-listener pattern. NOTE: event queues are not
* currently used to asynchronously publish events, as clients are intended to
* completely process relevant portions of the stream freeing or "clipping" any and
* all possible allocated nodes from the hierarchy for garbage collection.
*
* @author Scott Cinnamond
*/
public class StreamReader implements XmiReader {
private static Log log = LogFactory.getLog(StreamReader.class);
private Stack<StreamNode> nodes = new Stack<StreamNode>();
private Map<String, List<StreamNodeListener>> streamNodeListenerMap;
private static XMLEventAllocator allocator = null;
public StreamReader () {
}
public Collection<?> read(InputStream stream)
{
List<Object> results = new ArrayList<Object>();
InputStream source = stream;
StreamContext context = null;
try {
XMLInputFactory factory = XMLInputFactory.newInstance();
factory.setXMLResolver(new XMLResolver() {
public Object resolveEntity(String publicID,
String systemID,
String baseURI,
String namespace) throws XMLStreamException {
// TODO Auto-generated method stub
return null;
}
});
/*
XStream xstream = new XStream(new StaxDriver() {
protected XMLStreamReader createParser(Reader xml) throws
XMLStreamException {
return getInputFactory().createXMLStreamReader(xml);
}
protected XMLStreamReader createParser(InputStream xml) throws
XMLStreamException {
return getInputFactory().createXMLStreamReader(xml);
}
});
*/
//factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES,Boolean.FALSE);
//factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES,Boolean.TRUE);
//set the IS_COALESCING property to true , if application desires to
//get whole text data as one event.
//factory.setProperty(XMLInputFactory.IS_COALESCING , Boolean.TRUE);
factory.setEventAllocator(new EventAllocator());
allocator = factory.getEventAllocator();
XMLStreamReader streamReader = factory.createXMLStreamReader(stream);
int eventType = streamReader.getEventType();
StreamNode node = null;
StreamNode parent = null;
int level = 0;
int ignoredNodeLevel = -1;
while(streamReader.hasNext()){
eventType = streamReader.next();
//if (log.isDebugEnabled())
// log.debug(this.getEventTypeString(eventType));
switch (eventType){
case XMLEvent.START_ELEMENT:
level++;
if (ignoredNodeLevel >= 0)
break;
XMLEvent event = allocateXMLEvent(streamReader);
if (level == 1)
{
if (context != null)
throw new XmiException("existing context unexpected");
context = new StreamContext(event);
}
node = new StreamNode(event, context);
if (node.isIgnored())
{
if (log.isDebugEnabled())
{
Location loc = event.getLocation();
String msg = "start ignoring elements - level: "
+ String.valueOf(level)
+ " - line:col[" + loc.getLineNumber() + ":" + loc.getColumnNumber() + "] - ";
log.debug(msg);
}
ignoredNodeLevel = level;
break;
}
logEventInfo(event);
parent = null;
if (nodes.size() > 0)
{
parent = nodes.peek();
parent.add(node);
node.setParent(parent);
if (isRootNode(node, context))
results.add(node);
}
else
{
if (isRootNode(node, context))
results.add(node);
}
nodes.push(node);
fireStreamNodeCreated(node, parent);
break;
case XMLEvent.END_ELEMENT:
if (ignoredNodeLevel >= 0)
{
if (ignoredNodeLevel == level)
{
if (log.isDebugEnabled())
{
event = allocateXMLEvent(streamReader);
Location loc = event.getLocation();
String msg = "end ignoring elements - level: "
+ String.valueOf(level)
+ " - line:col[" + loc.getLineNumber() + ":" + loc.getColumnNumber() + "] - ";
log.debug(msg);
}
ignoredNodeLevel = -1;
}
level--;
break;
}
level--;
node = nodes.pop();
parent = null;
if (nodes.size() > 0)
parent = nodes.peek();
fireStreamNodeCompleted(node, parent);
break;
case XMLEvent.CHARACTERS:
if (ignoredNodeLevel >= 0)
break;
node = nodes.peek();
event = allocateXMLEvent(streamReader);
String data = event.asCharacters().getData();
if (data != null)
{
data = data.trim();
if (data.length() > 0)
{
if (log.isDebugEnabled())
log.debug("CHARACTERS: '" + data + "'");
if (data.length() > 0)
{
node = nodes.peek();
node.addCharactersEvent(event);
}
}
}
break;
default:
if (log.isDebugEnabled())
{
event = allocateXMLEvent(streamReader);
logEventInfo(event);
}
break;
}
}
if (results.size() > 1)
throw new XmiException("found multiple root nodes (" + results.size() + ")");
}catch(XMLStreamException e){
throw new XmiException(e);
}
finally {
try {
source.close();
}
catch (IOException e) {
}
}
return results;
}
// FIXME: need event queue
private void fireStreamNodeCreated(StreamNode node, StreamNode parent)
{
if (streamNodeListenerMap != null)
{
List<StreamNodeListener> list = streamNodeListenerMap.get(node.getLocalName());
if (list != null) {
for (StreamNodeListener listener : list)
listener.nodeCreated(
new StreamNodeEvent(node, parent));
}
}
}
// FIXME: need event queue
private void fireStreamNodeCompleted(StreamNode node, StreamNode parent)
{
if (streamNodeListenerMap != null)
{
List<StreamNodeListener> list = streamNodeListenerMap.get(node.getLocalName());
if (list != null) {
for (StreamNodeListener listener : list)
listener.nodeCompleted(
new StreamNodeEvent(node, parent));
}
}
}
private boolean isRootNode(StreamNode node, StreamContext context)
{
QName name = node.getStartElementEvent().asStartElement().getName();
if (name.getNamespaceURI().equals(context.getUmlNamespace().getNamespaceURI()))
if (XmiConstants.ELEMENT_XMI_ROOT.equalsIgnoreCase(name.getLocalPart()))
return true;
return false;
}
private void logEventInfo(XMLEvent event)
{
if (log.isDebugEnabled())
{
Location loc = event.getLocation();
String msg = getEventTypeString(event.getEventType());
msg += " line:col[" + loc.getLineNumber() + ":" + loc.getColumnNumber() + "] - ";
msg += event.toString();
log.debug(msg);
}
}
/** Get the immutable XMLEvent from given XMLStreamReader using XMLEventAllocator */
private static XMLEvent allocateXMLEvent(XMLStreamReader reader) throws XMLStreamException{
return allocator.allocate(reader);
}
/**
* Returns the String representation of the given integer constant.
*
* @param eventType Type of event.
* @return String representation of the event
*/
public final static String getEventTypeString(int eventType) {
switch (eventType){
case XMLEvent.START_ELEMENT:
return "START_ELEMENT";
case XMLEvent.END_ELEMENT:
return "END_ELEMENT";
case XMLEvent.PROCESSING_INSTRUCTION:
return "PROCESSING_INSTRUCTION";
case XMLEvent.CHARACTERS:
return "CHARACTERS";
case XMLEvent.COMMENT:
return "COMMENT";
case XMLEvent.START_DOCUMENT:
return "START_DOCUMENT";
case XMLEvent.END_DOCUMENT:
return "END_DOCUMENT";
case XMLEvent.ENTITY_REFERENCE:
return "ENTITY_REFERENCE";
case XMLEvent.ATTRIBUTE:
return "ATTRIBUTE";
case XMLEvent.DTD:
return "DTD";
case XMLEvent.CDATA:
return "CDATA";
case XMLEvent.SPACE:
return "SPACE";
}
return "UNKNOWN_EVENT_TYPE , " + eventType;
}
public void addStreamNodeListener(StreamNodeListener listener) {
if (streamNodeListenerMap == null)
streamNodeListenerMap = new HashMap<String, List<StreamNodeListener>>();
String[] names = listener.getElementNames();
for (int i = 0; i < names.length; i++)
{
List<StreamNodeListener> list = streamNodeListenerMap.get(names[i]);
if (list == null)
{
list = new ArrayList<StreamNodeListener>();
streamNodeListenerMap.put(names[i], list);
}
list.add(listener);
}
}
}