D3181
D3181

Reputation: 2092

Blocking thread running SAX Parser

I am having issues using multi-threading to stop my application running the SAX parser.

I have a class reading from an XML file, line-by-line, and I would like it to be able to be stopped when a button is pressed:

private String filename;
private boolean paused = false;

public xmlReader(String filename) {
    this.filename = filename;
}

public void run() {
   try {

     SAXParserFactory factory = SAXParserFactory.newInstance();
     SAXParser saxParser = factory.newSAXParser();

     DefaultHandler handler = new DefaultHandler() {

        // when an open row is encountered in the posts.xml file
        @Override
        public void startElement(String uri, String localName, String qName,
                                 Attributes attributes) throws SAXException {
           if (!paused) {
              System.out.println("Start Element :" 
                                 + attributes.getValue("name"));
           }

        }
     }
     saxParser.parse (filename, handler);

This is the code reading the xml file, however since I wish to be able to pause and resume the run method I am calling it from a thread:

public class myThread implements Runnable {

   private String file;
   private TextArea Suggestions;
   private volatile boolean running = true;
   private volatile boolean paused = false;
   databaseReader db = new databaseReader(this.file);

public void setSuggestions(TextArea Suggestions) {
   this.Suggestions = Suggestions;
}

public void setFile(String dbFile) {
   this.file = dbFile;
}

public void stop() {
   running = false;
   // you might also want to do this:
}

public void pause() {
   // you may want to throw an IllegalStateException if !running
   paused = true;
}

public void resume() {
   synchronized (db) {
      paused = false;
      db.notifyAll(); // Unblocks thread
   }
}

@Override
public void run() {
   while (running) {
      synchronized (db) {
         db.run();
            if (!running) {
               break;
            }
            if (paused) {
              try {
                 System.out.println("paused");
                  db.wait();
              } catch (InterruptedException ex) {
                  break;
              }
              if (!running) {
                 break;
              }
            }
        }
    }
    throw new UnsupportedOperationException("Not supported yet.");   
    //To change body of generated methods, choose Tools | Templates.
}

Likewise, I have a button that changes the value of isRunning on the thread when pressed:

 if (!isRunning) {
    myThread search = new myThread();
    search.start();
 }

However, none of this seems to stop the thread, so much so that even after closing the application the thread continues to run.

Other things I have tried - swapping implementing Runnable rather than extending the thread class and calling interrupt, wait()

Upvotes: 2

Views: 878

Answers (1)

Codo
Codo

Reputation: 78915

A SAXParser is an event-based parser – the oldest and most inconvenient type of XML parser. The problem as you have experienced yourself is that it takes full control of the parsing and you can only react; you can no longer act.

You have two options:

  1. Create a stoppable InputStream so you can stop the parser by stopping its input. It might still have some data buffered but it will quickly stop.

  2. Use a streaming or pull-parser parser instead (see an example). It doesn't take control of the parsing. Instead, you drive the parsing and ask for the next XML element. As you're in control, stopping it is a no-brainer.

Option 1 more or less works like this:

public class StoppedException extends RuntimeException {

}

public class StoppableInputStream extends InputStream {

    private boolean stopped;
    private InputStream wrappedStream;

    public StoppableInputStream(InputStream wrapped) {
        wrappedStream = wrapped;
    }

    public boolean isStopped() {
        return stopped;
    }

    public void setStopped(boolean stopped) {
        return this.stopped = stopped;
    }

    public int read() {
        if (stopped)
            throw new StoppedException();
        return wrappedStream.read();
    }

    public int read(byte[] b) {
        if (stopped)
            throw new StoppedException();
        return wrappedStream.read(b);
    }

    public int read(byte[] b, int off, int len) {
        if (stopped)
            throw new StoppedException();
        return wrappedStream.read(b, off, len);
    }

    // do the same for all other methods of InputStream
}

Then initialize the parser like this:

FileInputStream fis = new FileInputStream("yourfilename.xml");
StoppableInputStream sis = new StoppableInputStream(fis);
saxParser.parse (filename, handler);

Finally, you stop the parser like this:

sis.setStopped(true);

I haven't tested the code. It might not even compile.

I recommend you go with option 2.

Upvotes: 2

Related Questions