/** * */ package nokogiri.internals; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingQueue; import nokogiri.XmlSaxPushParser; /** * A smart input stream that signals the caller when a chunk of data is consumed * from the stream. The main use of this stream is to synchronize the * {@link XmlSaxPushParser} and the {@link XmlSaxParser} which runs in a * different thread. * * @author John Shahid <jvshahid@gmail.com> */ public class NokogiriBlockingQueueInputStream extends InputStream { private final LinkedBlockingQueue<Task> queue; protected Task currentTask; protected ByteArrayInputStream currentStream; protected int position; protected boolean closed = false; public static final ByteArrayInputStream END = new ByteArrayInputStream(new byte[0]); private static class Task extends FutureTask<Void> { private final ByteArrayInputStream stream; public Task(ByteArrayInputStream stream) { super(new Callable<Void>() { @Override public Void call() throws Exception { // TODO Auto-generated method stub return null; } }); this.stream = stream; } public ByteArrayInputStream getStream() { return stream; } @Override public void run() { // don't do anything } @Override public boolean runAndReset() { // don't do anything return true; } @Override public void set(Void v) { super.set(v); } } public NokogiriBlockingQueueInputStream() { queue = new LinkedBlockingQueue<Task>(); } /** * This method shouldn't be called unless the parser has finished parsing or * threw an exception while doing so, otherwise, there'll be the protential * that the read method will block indefinitely. */ @Override public synchronized void close() { closed = true; List<Task> tasks = new LinkedList<Task>(); queue.drainTo(tasks); tasks.add(currentTask); for (Task task : tasks) { task.set(null); } } /** * Add @param stream to the end of the queue of data that will be returned by * {@link #read()} and its variants. The method will @return a future whose * {@link Future#get()} will block until the data in @param stream is read. * * Passing the special stream {@link #END} to this method, will cause * {@link #read()} to return an eof indicator (i.e. -1) to the caller, after * all the data inserted before {@link #END} is processed. * * @return */ public synchronized Future<Void> addChunk(ByteArrayInputStream stream) throws ClosedStreamException { if (closed) throw new ClosedStreamException("Cannot add a chunk to a closed stream"); Task task = new Task(stream); queue.add(task); return task; } /* * (non-Javadoc) * * @see java.io.InputStream#read() */ @Override public int read() throws IOException { if (currentTask == null || currentStream.available() == 0) if (getNextTask() == -1) return -1; return currentStream.read(); } /* * (non-Javadoc) * * @see java.io.InputStream#read(byte[], int, int) */ @Override public int read(byte[] bytes, int off, int len) { if (currentTask == null || currentStream.available() == 0) { if (getNextTask() == -1) { currentTask.set(null); return -1; } } return currentStream.read(bytes, off, len); } protected int getNextTask() { while (true) { try { if (currentTask != null) currentTask.set(null); currentTask = queue.take(); currentStream = currentTask.getStream(); return currentStream.available() == 0 ? -1 : currentStream.available(); } catch (InterruptedException ex) { // keep retrying to read } } } }