/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.ignite.igfs.mapreduce;
import java.io.EOFException;
import java.io.IOException;
import org.apache.ignite.igfs.IgfsInputStream;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.jetbrains.annotations.NotNull;
/**
* Decorator for regular {@link org.apache.ignite.igfs.IgfsInputStream} which streams only data within the given range.
* This stream is used for {@link IgfsInputStreamJobAdapter} convenience adapter to create
* jobs which will be working only with the assigned range. You can also use it explicitly when
* working with {@link IgfsJob} directly.
*/
public final class IgfsRangeInputStream extends IgfsInputStream {
/** Base input stream. */
private final IgfsInputStream is;
/** Start position. */
private final long start;
/** Maximum stream length. */
private final long maxLen;
/** Current position within the stream. */
private long pos;
/**
* Constructor.
*
* @param is Base input stream.
* @param start Start position.
* @param maxLen Maximum stream length.
* @throws IOException In case of exception.
*/
public IgfsRangeInputStream(IgfsInputStream is, long start, long maxLen) throws IOException {
if (is == null)
throw new IllegalArgumentException("Input stream cannot be null.");
if (start < 0)
throw new IllegalArgumentException("Start position cannot be negative.");
if (start >= is.length())
throw new IllegalArgumentException("Start position cannot be greater that file length.");
if (maxLen < 0)
throw new IllegalArgumentException("Length cannot be negative.");
if (start + maxLen > is.length())
throw new IllegalArgumentException("Sum of start position and length cannot be greater than file length.");
this.is = is;
this.start = start;
this.maxLen = maxLen;
is.seek(start);
}
/** {@inheritDoc} */
@Override public long length() {
return is.length();
}
/**
* Constructor.
*
* @param is Base input stream.
* @param range File range.
* @throws IOException In case of exception.
*/
public IgfsRangeInputStream(IgfsInputStream is, IgfsFileRange range) throws IOException {
this(is, range.start(), range.length());
}
/** {@inheritDoc} */
@Override public int read() throws IOException {
if (pos < maxLen) {
int res = is.read();
if (res != -1)
pos++;
return res;
}
else
return -1;
}
/** {@inheritDoc} */
@Override public int read(@NotNull byte[] b, int off, int len) throws IOException {
if (pos < maxLen) {
len = (int)Math.min(len, maxLen - pos);
int res = is.read(b, off, len);
if (res != -1)
pos += res;
return res;
}
else
return -1;
}
/** {@inheritDoc} */
@Override public int read(long pos, byte[] buf, int off, int len) throws IOException {
seek(pos);
return read(buf, off, len);
}
/** {@inheritDoc} */
@Override public void readFully(long pos, byte[] buf) throws IOException {
readFully(pos, buf, 0, buf.length);
}
/** {@inheritDoc} */
@Override public void readFully(long pos, byte[] buf, int off, int len) throws IOException {
seek(pos);
for (int readBytes = 0; readBytes < len;) {
int read = read(buf, off + readBytes, len - readBytes);
if (read == -1)
throw new EOFException("Failed to read stream fully (stream ends unexpectedly) [pos=" + pos +
", buf.length=" + buf.length + ", off=" + off + ", len=" + len + ']');
readBytes += read;
}
}
/** {@inheritDoc} */
@Override public void seek(long pos) throws IOException {
if (pos < 0)
throw new IOException("Seek position cannot be negative: " + pos);
is.seek(start + pos);
this.pos = pos;
}
/** {@inheritDoc} */
@Override public long position() {
return pos;
}
/**
* Since range input stream represents a part of larger file stream, there is an offset at which this
* range input stream starts in original input stream. This method returns start offset of this input
* stream relative to original input stream.
*
* @return Start offset in original input stream.
*/
public long startOffset() {
return start;
}
/** {@inheritDoc} */
@Override public int available() {
long l = maxLen - pos;
if (l < 0)
return 0;
if (l > Integer.MAX_VALUE)
return Integer.MAX_VALUE;
return (int)l;
}
/** {@inheritDoc} */
@Override public void close() throws IOException {
is.close();
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(IgfsRangeInputStream.class, this);
}
}