summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/isode/stroke/parser/PullXMLParser.java')
-rw-r--r--src/com/isode/stroke/parser/PullXMLParser.java219
1 files changed, 219 insertions, 0 deletions
diff --git a/src/com/isode/stroke/parser/PullXMLParser.java b/src/com/isode/stroke/parser/PullXMLParser.java
new file mode 100644
index 0000000..8b42afe
--- /dev/null
+++ b/src/com/isode/stroke/parser/PullXMLParser.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.eventloop.Event.Callback;
+import com.isode.stroke.eventloop.EventLoop;
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.xmlpull.mxp1.MXParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * Parser based around the XmlPullParser
+ */
+class PullXMLParser extends XMLParser {
+
+ private final Logger logger_ = Logger.getLogger(this.getClass().getName());
+ private final XmlPullParser parser_ = new MXParser();
+ private final PipedInputStream reader_;
+ private final PipedOutputStream writer_;
+ private final ArrayBlockingQueue<Event> events_ = new ArrayBlockingQueue<Event>(20);
+ private final Thread parserThread_;
+ private boolean error_ = false;
+ private final EventLoop eventLoop_;
+
+
+ private enum EventType {Start, End, Text};
+
+ /**
+ * XML Event struct.
+ */
+ private class Event {
+ private final EventType type;
+ private final String name;
+ private final String namespace;
+ private final AttributeMap attributes;
+ public Event(EventType type, String name, String namespace, AttributeMap attributes) {
+ this.type = type;
+ this.name = name;
+ this.namespace = namespace;
+ this.attributes = attributes;
+ }
+
+ public Event(String name) {
+ this(EventType.Text, name, null, null);
+ }
+
+
+ public Event(String name, String namespace) {
+ this(EventType.End, name, namespace, null);
+ }
+
+ public Event(String name, String namespace, AttributeMap attributes) {
+ this(EventType.Start, name, namespace, attributes);
+ }
+
+ }
+
+ /**
+ * Put an XML event onto the queue ready for the main thread to pick up later.
+ */
+ private void addEvent(Event event) throws InterruptedException {
+ events_.put(event);
+ }
+
+ /**
+ * Deal with whatever was just read out of the parser_.
+ */
+ private void handleEvent(int eventType) throws XmlPullParserException, InterruptedException {
+ if (eventType == XmlPullParser.START_TAG) {
+ AttributeMap map = new AttributeMap();
+ for (int i = 0; i < parser_.getAttributeCount(); i++) {
+ map.put(parser_.getAttributeName(i), parser_.getAttributeValue(i));
+ }
+ addEvent(new Event(parser_.getName(), parser_.getNamespace(), map));
+ bump();
+ } else if (eventType == XmlPullParser.END_TAG) {
+ addEvent(new Event(parser_.getName(), parser_.getNamespace()));
+ bump();
+ } else if (eventType == XmlPullParser.TEXT) {
+ StringBuilder text = new StringBuilder();
+ int holderForStartAndLength[] = new int[2];
+ char ch[] = parser_.getTextCharacters(holderForStartAndLength);
+ int start = holderForStartAndLength[0];
+ int length = holderForStartAndLength[1];
+ for (int i = start; i < start + length; i++) {
+ text.append(ch[i]);
+ }
+ addEvent(new Event(text.toString()));
+ bump();
+ } else if (eventType == XmlPullParser.START_DOCUMENT) {
+ //System.out.println("Starting document");
+ } else if (eventType == XmlPullParser.END_DOCUMENT) {
+ //System.out.println("Ending document");
+
+ } else {
+ //System.out.println("Unhandled event");
+ }
+ }
+
+ /**
+ * Cause the main thread to process any outstanding events.
+ */
+ private void bump() {
+ eventLoop_.postEvent(new Callback() {
+ public void run() {
+ processEvents();
+ }
+ });
+ }
+
+ public PullXMLParser(XMLParserClient client, EventLoop eventLoop) {
+ super(client);
+ eventLoop_ = eventLoop;
+ writer_ = new PipedOutputStream();
+ try {
+ reader_ = new PipedInputStream(writer_, 128000);
+ } catch (IOException ex) {
+ Logger.getLogger(PullXMLParser.class.getName()).log(Level.SEVERE, null, ex);
+ throw new IllegalStateException(ex);
+ }
+ try {
+ parser_.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ parser_.setInput(reader_, "UTF-8");
+ } catch (XmlPullParserException ex) {
+ Logger.getLogger(PullXMLParser.class.getName()).log(Level.SEVERE, null, ex);
+ throw new IllegalStateException(ex);
+ }
+ Runnable parserRunnable = new Runnable() {
+ public void run() {
+ int eventType = XmlPullParser.END_DOCUMENT - 1; /* Anything to make the following not true*/
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ try {
+ parser_.next();
+ eventType = parser_.getEventType();
+ handleEvent(eventType);
+ } catch (XmlPullParserException ex) {
+ error_ = true;
+ Logger.getLogger(PullXMLParser.class.getName()).log(Level.SEVERE, null, ex);
+ break;
+ } catch (IOException ex) {
+ error_ = true;
+ Logger.getLogger(PullXMLParser.class.getName()).log(Level.SEVERE, null, ex);
+ break;
+ } catch (InterruptedException ex) {
+ /* The thread was interrupted while trying to process an event - presumably this is because we're shutting down.*/
+ error_ = true;
+ Logger.getLogger(PullXMLParser.class.getName()).log(Level.SEVERE, null, ex);
+ break;
+ }
+ }
+ }
+ };
+ parserThread_ = new Thread(parserRunnable);
+ parserThread_.setDaemon(true);
+ parserThread_.start();
+ }
+
+ /**
+ * Do not do this!
+ * This is only to allow the unit tests to join onto it.
+ * @return
+ */
+ Thread getParserThread() {
+ return parserThread_;
+ }
+
+ /**
+ * Process outstanding events.
+ * Call in the main thread only.
+ */
+ private void processEvents() {
+ while (events_.size() > 0) {
+ processEvent(events_.poll());
+ }
+ }
+
+ /**
+ * Main thread only.
+ */
+ private void processEvent(Event event) {
+ String name = event.name;
+ String namespace = event.namespace;
+ AttributeMap attributes = event.attributes;
+ switch (event.type) {
+ case Start: getClient().handleStartElement(name, namespace, attributes); break;
+ case End: getClient().handleEndElement(name, namespace); break;
+ case Text: getClient().handleCharacterData(name); break;
+ }
+ }
+
+ /**
+ * Cause the parser thread to parse these data later.
+ * Note that the return code is a best guess based on previous parsing,
+ * and will almost always give a false negative on a stanza before a
+ * true negative. True negatives will always mean an error in the stream.
+ */
+ @Override
+ public boolean parse(String data) {
+ try {
+ writer_.write(new ByteArray(data).getData());
+ writer_.flush();
+ } catch (IOException ex) {
+ error_ = true;
+ Logger.getLogger(PullXMLParser.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ return !error_;
+ }
+
+
+}