// // Copyright (C) 2006 United States Government as represented by the // Administrator of the National Aeronautics and Space Administration // (NASA). All Rights Reserved. // // This software is distributed under the NASA Open Source Agreement // (NOSA), version 1.3. The NOSA has been approved by the Open Source // Initiative. See the file NOSA-1.3-JPF at the top of the distribution // directory tree for the complete NOSA document. // // THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY // KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT // LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO // SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR // A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT // THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT // DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. // package gov.nasa.jpf.util; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.concurrent.locks.ReentrantLock; /* Note: This class fails after 8 petabytes of data has been read. This should * never be a problem. For example, assuming a 10 Ghz clock and 1 cycle to read * 4-bytes from L1 cache, it would take 7.3 years to read that much data. */ public class SplitInputStream { static final int INITIAL_BUFFER_SIZE = 1024; // Recommended to be a power of 2. If not, it will be rounded up to the next power of 2. private final ReentrantLock m_sourceLock = new ReentrantLock(); private final ReentrantLock m_dataLock = new ReentrantLock(); private final InputStream m_source; // Protected by m_sourceGate lock private final Stream m_stream[]; // Not protected by a lock private long m_write; // Must hold m_dataGate lock to read. Must hold m_dataGate and m_sourceGate to write. private int m_available; // Must hold m_dataGate lock to read. Must hold m_dataGate and m_sourceGate to write. private int m_openStreams; // Must hold m_dataGate lock to access. private byte m_buffer[]; // Must hold m_dataGate lock to read. Must hold m_sourceGate lock to write. The written data doesn't become available until m_write is updated. public SplitInputStream(InputStream source, int streamCount) { this(source, streamCount, INITIAL_BUFFER_SIZE); } public SplitInputStream(InputStream source, int streamCount, int initialSize) { int i; if (source == null) throw new NullPointerException("source == null"); if (streamCount <= 0) throw new IllegalArgumentException("streamCount <= 0 : " + streamCount); if (initialSize <= 0) throw new IllegalArgumentException("initialSize <= 0 : " + initialSize); m_source = source; m_openStreams = streamCount; m_stream = new Stream[streamCount]; for (i = streamCount; --i >= 0; ) m_stream[i] = new Stream(i); initialSize--; // Rounds initialSize up to the next power of 2 initialSize |= initialSize >> 1; initialSize |= initialSize >> 2; initialSize |= initialSize >> 4; initialSize |= initialSize >> 8; initialSize |= initialSize >> 16; initialSize++; m_buffer = new byte[initialSize]; } public int getStreamCount() { return(m_stream.length); } public InputStream getStream(int index) { return(m_stream[index]); } private int read(int index) throws IOException { long position; int offset, result; m_dataLock.lock(); try { position = m_stream[index].getPosition(); if (position == m_write) { if (!fill(index)) return(-1); position = m_stream[index].getPosition(); } offset = getBufferOffset(position); result = m_buffer[offset] & 0x0FF; m_stream[index].setPosition(position + 1); } finally { m_dataLock.unlock(); } return(result); } private int read(int index, byte buffer[], int offset, int length) throws IOException { long position; int off; if (buffer == null) throw new NullPointerException("buffer == null"); if (offset < 0) throw new IndexOutOfBoundsException("offset < 0 : " + offset); if (length < 0) throw new IndexOutOfBoundsException("length < 0 : " + length); if (offset + length > buffer.length) throw new IndexOutOfBoundsException("offset + length > buffer.length : " + offset + " + " + length + " > " + buffer.length); if (length == 0) return(0); m_dataLock.lock(); try { position = m_stream[index].getPosition(); if (position == m_write) { if (!fill(index)) return(-1); position = m_stream[index].getPosition(); } off = getBufferOffset(position); length = (int) Math.min(length, m_write - position); length = Math.min(length, m_buffer.length - off); m_stream[index].setPosition(position + length); System.arraycopy(m_buffer, off, buffer, offset, length); } finally { m_dataLock.unlock(); } return(length); } private long skip(int index, long n) throws IOException { long position; if (n <= 0) return(0); m_dataLock.lock(); try { position = m_stream[index].getPosition(); if (position == m_write) { if (!fill(index)) return(0); position = m_stream[index].getPosition(); } n = Math.min(n, m_write - position); m_stream[index].setPosition(position + n); } finally { m_dataLock.unlock(); } return(n); } private boolean fill(int index) throws IOException { long minPosition, write; int length, offsetPosition, offsetWrite; try { if (!doLock(index)) return(true); minPosition = getMinPosition(); if (m_write - minPosition + 1 >= m_buffer.length) expand(); write = m_write; // Capture the data in local variables so the calculations can take place outside m_dataLock. length = m_buffer.length; m_available = m_source.available(); m_dataLock.unlock(); // Don't hold m_dataLock while blocked reading. That way other Streams with data left to read can do so. offsetWrite = getBufferOffset(write); offsetPosition = getBufferOffset(minPosition); // If the minPosition advances while not holding the lock, no big deal. It simply means less data will be read from m_source. length = getReadLength(offsetPosition, offsetWrite, length); do { length = m_source.read(m_buffer, offsetWrite, length); } while (length == 0); // Guarantee that at least 1 byte is read or end of file is reached. if (length < 0) return(false); m_dataLock.lock(); m_write += length; m_available = m_source.available(); } finally { m_sourceLock.unlock(); if (!m_dataLock.isHeldByCurrentThread()) // Restore the lock state when the method was called. m_dataLock.lock(); } return(true); } private boolean doLock(int index) { long position; /* m_sourceLock must be acquired before m_dataLock. Otherwise, there will * be a deadlock. But, if we can tryLock() m_sourceLock while holding * m_dataLock, this will save CPU time. */ if (m_sourceLock.tryLock()) return(true); m_dataLock.unlock(); m_sourceLock.lock(); m_dataLock.lock(); position = m_stream[index].getPosition(); return(position == m_write); // Does the thread still need to read data? } private long getMinPosition() { long result, position; int i; result = Long.MAX_VALUE; for (i = m_stream.length; --i >= 0; ) if (!m_stream[i].isClosed()) { position = m_stream[i].getPosition(); result = Math.min(result, position); } return(result); } private int getReadLength(int offsetPosition, int offsetWrite, int length) { if (offsetPosition > offsetWrite) return(offsetPosition - offsetWrite - 1); length -= offsetWrite; if (offsetPosition == 0) length--; return(length); } private void expand() { int length; byte buffer[]; buffer = m_buffer; length = buffer.length; m_buffer = Arrays.copyOf(buffer, 2 * length); // Since we are doubling the length of the array, we simply have to duplicate the contents. // This allows us to avoid figuring out which part of m_buffer is actually holding data and dealing with wrapping. System.arraycopy(buffer, 0, m_buffer, length, length); } private int available(int index) throws IOException { long result; boolean sourceLock; m_dataLock.lock(); sourceLock = m_sourceLock.tryLock(); // By putting this after locking m_dataLock, the only way tryLock() will fail is if a thread is reading from m_source. try { if (sourceLock) m_available = m_source.available(); result = m_available; result += m_write - m_stream[index].getPosition(); } finally { m_dataLock.unlock(); if (sourceLock) m_sourceLock.unlock(); } if (result > Integer.MAX_VALUE) return(Integer.MAX_VALUE); return((int) result); } private void close() throws IOException { boolean close; m_dataLock.lock(); try { m_openStreams--; close = m_openStreams == 0; } finally { m_dataLock.unlock(); } if (!close) return; m_sourceLock.lock(); try { m_source.close(); } finally { m_sourceLock.unlock(); } } private int getBufferOffset(long position) { return((int) (position & (m_buffer.length - 1))); } private class Stream extends InputStream { private long m_position; private final int m_index; private boolean m_closed; private Stream(int index) { m_index = index; } long getPosition() { return(m_position); } void setPosition(long position) { m_position = position; } synchronized boolean isClosed() { return(m_closed); } public int read() throws IOException { if (isClosed()) return(-1); return(SplitInputStream.this.read(m_index)); } public int read(byte buffer[], int offset, int length) throws IOException { if (isClosed()) return(-1); return(SplitInputStream.this.read(m_index, buffer, offset, length)); } public long skip(long n) throws IOException { if (isClosed()) return(0); return(SplitInputStream.this.skip(m_index, n)); } public int available() throws IOException { if (isClosed()) return(0); return(SplitInputStream.this.available(m_index)); } public void close() throws IOException { synchronized (this) { if (m_closed) return; m_closed = true; } SplitInputStream.this.close(); } } }