/** * VMware Continuent Tungsten Replicator * Copyright (C) 2015 VMware, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Initial developer(s): Robert Hodges * Contributor(s): */ package com.continuent.tungsten.common.cache; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; /** * Implements an input stream on a raw byte vector. The input stream reads first * from available in-memory byte buffers, then from storage. */ public class RawByteInputStream extends InputStream { // Allocator for the byte vector. private final RawByteAllocator alloc; // Overall index of next byte to read. private long nextByteIndex = 0; // Buffer position and offset. private int bufferIndex = 0; private int bufferOffset = 0; // Input stream for reading from storage. private FileInputStream fileInput = null; private BufferedInputStream input = null; RawByteInputStream(RawByteAllocator alloc) { this.alloc = alloc; } /** * {@inheritDoc} * * @see java.io.InputStream#read() */ @Override public int read() throws IOException { // Decide whether we should be reading from byte buffers or storage. if (nextByteIndex < alloc.memoryLength) { // See if we have anything to read at all. if (bufferIndex >= alloc.buffers.size()) return -1; // Adjust overall position forward so next read will be at the // correct position. nextByteIndex++; // See if the current memory offset is at the end of a buffer and // rotate to next buffer accordingly. while (bufferOffset >= alloc.buffers.get(bufferIndex).length) { bufferIndex++; bufferOffset = 0; } // Return the next byte. We AND with x'FF' to remove automatic // sign-extension. return 0xFF & alloc.buffers.get(bufferIndex)[bufferOffset++]; } else { // See if there is storage allocated. If not we are at EOF. if (alloc.cacheFile == null) return -1; // Adjust overall position forward so next read will be at the // correct position. nextByteIndex++; // Ensure we have an input stream to read data. if (fileInput == null) { fileInput = new FileInputStream(alloc.cacheFile); input = new BufferedInputStream(fileInput); } return input.read(); } } /** * Provide efficient skipping mechanism that depends on ability to determine * offset location quickly in memory buffer or by seeking to a file * position. * * @throws IOException * @see java.io.InputStream#skip(long) */ @Override public long skip(long n) throws IOException { // Test for trival case. if (n == 0) { return 0; } // Set up counters for seek operation. long target = nextByteIndex + n; long skipped; int currentLength = (int) (alloc.memoryLength + alloc.storageLength); // Test cases. if (target > currentLength) { // We are past the end of the vector. Next read should return EOF. // If there is storage allocated see to end to ensure this is the // case. skipped = currentLength - nextByteIndex; nextByteIndex = currentLength; if (fileInput != null) seek(alloc.storageLength); } else if (target < alloc.memoryLength) { // We are in the memory buffers at the head of the vector. skipped = target - nextByteIndex; long amountToSkip = skipped; // Mark off the distance to skip, updating buffer pointers // accordingly. while (amountToSkip > 0) { int currentBufferLength = alloc.buffers.get(bufferIndex).length; if (bufferOffset + amountToSkip >= alloc.buffers.get(bufferIndex).length) { // We need to skip to the next buffer, resetting the offset // to zero. amountToSkip -= (currentBufferLength - bufferOffset); bufferIndex++; bufferOffset = 0; } else { // We need to skip forward within the current buffer. bufferOffset = (int) amountToSkip; amountToSkip = 0; nextByteIndex = target; } } } else { // We are in the storage part of the vector. Seek to // target position after subtracting allocated memory. if (fileInput == null) { fileInput = new FileInputStream(alloc.cacheFile); input = new BufferedInputStream(fileInput); } seek(target - alloc.memoryLength); skipped = target - nextByteIndex; nextByteIndex = target; } // Return number of bytes actually skipped. return skipped; } /** * {@inheritDoc} * * @see java.io.InputStream#close() */ public void close() throws IOException { // Close file input if we were using it. if (fileInput != null) fileInput.close(); if(input != null) input.close(); } // Seek to a particular position in storage. private void seek(long offset) throws IOException { fileInput.getChannel().position(offset); } }