/*
* Copyright 2006-2017 ICEsoft Technologies Canada Corp.
*
* 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.
*/
package org.icepdf.core.io;
import java.io.IOException;
import java.io.InputStream;
/**
* @author Mark Collette
* @since 2.0
*/
public class SeekableInputConstrainedWrapper extends InputStream {
private SeekableInput streamDataInput;
private long filePositionOfStreamData;
private long lengthOfStreamData;
private long filePositionBeforeUse;
private boolean usedYet;
public SeekableInputConstrainedWrapper(
SeekableInput in, long offset, long length) {
streamDataInput = in;
filePositionOfStreamData = offset;
lengthOfStreamData = length;
filePositionBeforeUse = 0L;
usedYet = false;
}
private void ensureReadyOnFirstUse() throws IOException {
if (usedYet)
return;
usedYet = true;
filePositionBeforeUse = streamDataInput.getAbsolutePosition();
streamDataInput.seekAbsolute(filePositionOfStreamData);
}
private long getBytesRemaining() throws IOException {
long absPos = streamDataInput.getAbsolutePosition();
if (absPos < filePositionOfStreamData)
return -1;
long end = filePositionOfStreamData + lengthOfStreamData;
return end - absPos;
}
//
// Methods from InputStream
// Since Java does not have multiple inheritance, we have to
// explicitly expose InputStream's methods as part of our interface
//
public int read() throws IOException {
ensureReadyOnFirstUse();
long remain = getBytesRemaining();
if (remain <= 0)
return -1;
return streamDataInput.read();
}
public int read(byte[] buffer) throws IOException {
return read(buffer, 0, buffer.length);
}
public int read(byte[] buffer, int offset, int length) throws IOException {
ensureReadyOnFirstUse();
long remain = getBytesRemaining();
if (remain <= 0)
return -1;
length = (int) Math.min(Math.min(remain, (long) length), (long) Integer.MAX_VALUE);
return streamDataInput.read(buffer, offset, length);
}
public int available() {
return 0;
}
public void mark(int readLimit) {
}
public boolean markSupported() {
return false;
}
public void reset() throws IOException {
}
public long skip(long n) throws IOException {
ensureReadyOnFirstUse();
long remain = getBytesRemaining();
if (remain <= 0)
return -1;
n = (int) Math.min(Math.min(remain, n), (long) Integer.MAX_VALUE);
return streamDataInput.skip(n);
}
//
// Special methods that make this truly seekable
//
public void seekAbsolute(long absolutePosition) throws IOException {
ensureReadyOnFirstUse();
// The wrapper exists in a different coordinate system,
// where its beginning is location 0
if (absolutePosition < 0L)
throw new IOException("Attempt to absolutely seek to negative location: " + absolutePosition);
// It's alright to seek beyond the end, it's just that read operations will fail
absolutePosition += filePositionOfStreamData;
streamDataInput.seekAbsolute(absolutePosition);
}
public void seekRelative(long relativeOffset) throws IOException {
ensureReadyOnFirstUse();
long pos = streamDataInput.getAbsolutePosition();
pos += relativeOffset;
if (pos < filePositionOfStreamData)
pos = filePositionOfStreamData;
// It's alright to seek beyond the end, it's just that read operations will fail
streamDataInput.seekAbsolute(pos);
}
public void seekEnd() throws IOException {
ensureReadyOnFirstUse();
streamDataInput.seekAbsolute(filePositionOfStreamData + lengthOfStreamData);
}
public long getAbsolutePosition() throws IOException {
ensureReadyOnFirstUse();
long absolutePosition = getAbsolutePosition();
absolutePosition -= filePositionOfStreamData;
return absolutePosition;
}
public long getLength() {
return lengthOfStreamData;
}
// To access InputStream methods, call this instead of casting
// This InputStream has to support mark(), reset(), and obviously markSupported()
public InputStream getInputStream() {
return this;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
sb.append(" ( ");
sb.append("pos=").append(filePositionOfStreamData).append(", ");
sb.append("len=").append(lengthOfStreamData).append(", ");
sb.append("posToRestore=").append(filePositionBeforeUse).append(", ");
sb.append(" ) ");
sb.append(": ");
if (streamDataInput == null)
sb.append("null ");
else
sb.append(streamDataInput.toString());
return sb.toString();
}
}