All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.javanut.pronghorn.pipe.stream.RingInputStream Maven / Gradle / Ivy

Go to download

Ring buffer based queuing utility for applications that require high performance and/or a small footprint. Well suited for embedded and stream based processing.

There is a newer version: 1.1.27
Show newest version
package com.javanut.pronghorn.pipe.stream;

import static com.javanut.pronghorn.pipe.Pipe.byteBackingArray;
import static com.javanut.pronghorn.pipe.Pipe.bytePosition;
import static com.javanut.pronghorn.pipe.Pipe.headPosition;
import static com.javanut.pronghorn.pipe.Pipe.tailPosition;
import static com.javanut.pronghorn.pipe.Pipe.takeRingByteLen;
import static com.javanut.pronghorn.pipe.Pipe.takeRingByteMetaData;

import java.io.IOException;
import java.io.InputStream;

import com.javanut.pronghorn.pipe.Pipe;
import com.javanut.pronghorn.pipe.RawDataSchema;

public class RingInputStream extends InputStream implements AutoCloseable {

	private final Pipe pipe;
	private final int sourceByteMask;
	private final int recordSize = RawDataSchema.FROM.fragDataSize[RawDataSchema.MSG_CHUNKEDSTREAM_1];
	
	private int remainingSourceLength = -1;
	private int remainingSourceMeta;
	private int remainingSourceOffset;
	private byte[] oneByte = new byte[1]; 
	
	/**
	 * By definition an input stream is blocking so this adds a blocking API for the pipe;
	 * @param pipe
	 */
	public RingInputStream(Pipe pipe) {
		this.pipe = pipe;
		this.sourceByteMask = pipe.blobMask;
		if (Pipe.from(pipe) != RawDataSchema.FROM) {
			throw new UnsupportedOperationException("This class can only be used with the very simple RAW_BYTES catalog of messages.");
		}
	}
	
	@Override
    public int available() throws IOException {
        return 0;
    }
    
	@Override
	public int read() {
		//this array does not escape the scope of this method so it will
		//probably be removed by the runtime compiler and directly use stack space
		
		if (remainingSourceLength <= 0) {
			return blockForNewContent(oneByte, 0, 1) <  0 ? -1 : 0xFF&oneByte[0];
		} else {		
			if (sendRemainingContent(oneByte, 0, 1)!=1) {
				throw new UnsupportedOperationException();
			}
			return 0xFF&oneByte[0];
		}
	}

	@Override
	public int read(byte[] b) {
		//favor true as the most frequent branch and keep the happy path first
		//this helps branch prediction and pre-fetch
		int result;
		if (remainingSourceLength <= 0) {
			result = blockForNewContent(b, 0, b.length);
		} else {		
			result = sendRemainingContent(b, 0, b.length);
		}
		if (0==result) {
			new Exception("BAD ZERO RETURN").printStackTrace();
		}
		return result;
	}

	@Override
	public int read(byte[] targetData, int targetOffset, int targetLength) {
		//favor true as the most frequent branch and keep the happy path first
		//this helps branch prediction and pre-fetch
		int result;
		if (remainingSourceLength <= 0) {
			result = blockForNewContent(targetData, targetOffset, targetLength); 
		} else {		
			result = sendRemainingContent(targetData, targetOffset, targetLength);
		}
		if (0==result) {
			new Exception("BAD ZERO RETURN2").printStackTrace();
		}
		return result;
	}

//	boolean closed = false;// TODO: C, clean this up to make simpler
	private int blockForNewContent(byte[] targetData, int targetOffset, int targetLength) {
//	    if (closed) {
//	        return -1;
//	    }
		int returnLength = 0;
		//only need to look for 1 value then step forward by steps this lets us pick up the EOM message without hanging.
		long target = 1+tailPosition(pipe);
		long headPosCache = headPosition(pipe);
				
		do {
			//block until we have something to read
		    long lastCheckedValue = headPosCache;
			while ( lastCheckedValue < target) {
				Pipe.spinWork(pipe);//TODO: WARNING this may hang when using a single thread scheduler
			    lastCheckedValue = Pipe.headPosition(pipe);
			}
			headPosCache = lastCheckedValue;
		    target+=recordSize;		    
		    returnLength = sendNewContent(targetData, targetOffset, targetLength);		    
		    
		} while (returnLength==0); //Must block until at least 1 byte was read or -1 EOF detected
		return returnLength;
	}
	
	private int sendNewContent(byte[] targetData, int targetOffset,	int targetLength) {
		
		int msgId = Pipe.takeMsgIdx(pipe);
		
		if (msgId>=0) { //exit EOF logic
			int meta = Pipe.takeByteArrayMetaData((Pipe) pipe);//side effect, this moves the pointer and must happen before we call for length
			int sourceLength = Pipe.takeByteArrayLength((Pipe) pipe);
			return beginNewContent(targetData, targetOffset, targetLength, meta, sourceLength);
		} else { 
		   // Pipe.confirmLowLevelRead(ring, recordSize);//wrong size?
			Pipe.releaseReadLock(pipe); //TOOD: bad idea needs more elegant solution.
		//	closed = true;
			return -1;			
		}
	}

	private int beginNewContent(byte[] targetData, int targetOffset, int targetLength, int meta, int sourceLength) {
		byte[] sourceData = byteBackingArray(meta, pipe);
		int sourceOffset = bytePosition(meta,pipe,sourceLength);        					
								
		if (sourceLength<=targetLength) {
			//the entire block can be sent
			copyData(targetData, targetOffset, sourceLength, sourceData, sourceOffset);
			Pipe.confirmLowLevelRead(pipe, recordSize);
			Pipe.releaseReadLock(pipe);
			return sourceLength;
		} else {
			//only part of the block can be sent so save some for later
			copyData(targetData, targetOffset, targetLength, sourceData, sourceOffset);
			
			//do not release read lock we are not done yet
			remainingSourceLength = sourceLength-targetLength;
			remainingSourceMeta = meta;
			remainingSourceOffset = sourceOffset+targetLength;
			
			return targetLength;
		}
	}

	private int sendRemainingContent(byte[] targetData, int targetOffset, int targetLength) {
		//send the rest of the data that we could not last time 
		//we assume that ending remaining content happens more frequently than the continuation
		if (remainingSourceLength<=targetLength) {
			return endRemainingContent(targetData, targetOffset);
		} else {
			return continueRemainingContent(targetData, targetOffset, targetLength);				
		}
	}

	private int continueRemainingContent(byte[] targetData, int targetOffset, int targetLength) {
		//only part of the block can be sent so save some for later
		
		copyData(targetData, targetOffset, targetLength, byteBackingArray(remainingSourceMeta, pipe), remainingSourceOffset);
		
		//do not release read lock we are not done yet
		remainingSourceLength = remainingSourceLength-targetLength;
		remainingSourceOffset = remainingSourceOffset+targetLength;
		
		return targetLength;
	}

	private int endRemainingContent(byte[] targetData, int targetOffset) {
		//the entire remaining part of the block can be sent
		int len = remainingSourceLength;
		copyData(targetData, targetOffset, len, byteBackingArray(remainingSourceMeta, pipe), remainingSourceOffset);
		Pipe.confirmLowLevelRead(pipe, recordSize);
		Pipe.releaseReadLock(pipe);
		remainingSourceLength = -1; //clear because we are now done with the remaining content
		return len;
	}

	private void copyData(byte[] targetData, int targetOffset, int sourceLength, byte[] sourceData, int sourceOffset) {
	    if (0==sourceLength) {
	        return; //TODO: needs to be cleaned up but we can not use the logic below when length is zero.
	    }
		if ((sourceOffset&sourceByteMask) > ((sourceOffset+sourceLength-1) & sourceByteMask)) {
			//rolled over the end of the buffer
			 int len1 = 1+sourceByteMask-(sourceOffset&sourceByteMask);
			 System.arraycopy(sourceData, sourceOffset&sourceByteMask, targetData, targetOffset, len1);
			 System.arraycopy(sourceData, 0,                           targetData, targetOffset+len1, sourceLength-len1);
		} else {						
			 //simple add bytes
			 System.arraycopy(sourceData, sourceOffset&sourceByteMask, targetData, targetOffset, sourceLength); 
		}
	}
	
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy