--- /dev/null
+/* ====================================================================
+ 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.poi.poifs.filesystem;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+
+import org.apache.poi.poifs.property.DocumentProperty;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianInput;
+
+/**
+ * This class provides methods to read a DocumentEntry managed by a
+ * {@link POIFSFileSystem} instance.
+ *
+ * @author Marc Johnson (mjohnson at apache dot org)
+ */
+public final class NDocumentInputStream extends InputStream implements LittleEndianInput {
+ /** returned by read operations if we're at end of document */
+ private static final int EOF = -1;
+
+ private static final int SIZE_SHORT = 2;
+ private static final int SIZE_INT = 4;
+ private static final int SIZE_LONG = 8;
+
+ /** current offset into the Document */
+ private int _current_offset;
+ /** current block count */
+ private int _current_block_count;
+
+ /** current marked offset into the Document (used by mark and reset) */
+ private int _marked_offset;
+ /** and the block count for it */
+ private int _marked_offset_count;
+
+ /** the Document's size */
+ private int _document_size;
+
+ /** have we been closed? */
+ private boolean _closed;
+
+ /** the actual Document */
+ private NPOIFSDocument _document;
+
+ private Iterator<ByteBuffer> _data;
+ private ByteBuffer _buffer;
+
+ /**
+ * Create an InputStream from the specified DocumentEntry
+ *
+ * @param document the DocumentEntry to be read
+ *
+ * @exception IOException if the DocumentEntry cannot be opened (like, maybe it has
+ * been deleted?)
+ */
+ public NDocumentInputStream(DocumentEntry document) throws IOException {
+ if (!(document instanceof DocumentNode)) {
+ throw new IOException("Cannot open internal document storage");
+ }
+ _current_offset = 0;
+ _current_block_count = 0;
+ _marked_offset = 0;
+ _marked_offset_count = 0;
+ _document_size = document.getSize();
+ _closed = false;
+
+ DocumentNode doc = (DocumentNode)document;
+ DocumentProperty property = (DocumentProperty)doc.getProperty();
+ _document = new NPOIFSDocument(
+ property,
+ ((DirectoryNode)doc.getParent()).getNFileSystem()
+ );
+ _data = _document.getBlockIterator();
+ }
+
+ /**
+ * Create an InputStream from the specified Document
+ *
+ * @param document the Document to be read
+ */
+ public NDocumentInputStream(NPOIFSDocument document) {
+ _current_offset = 0;
+ _current_block_count = 0;
+ _marked_offset = 0;
+ _marked_offset_count = 0;
+ _document_size = document.getSize();
+ _closed = false;
+ _document = document;
+ _data = _document.getBlockIterator();
+ }
+
+ public int available() {
+ if (_closed) {
+ throw new IllegalStateException("cannot perform requested operation on a closed stream");
+ }
+ return _document_size - _current_offset;
+ }
+
+ public void close() {
+ _closed = true;
+ }
+
+ public void mark(int ignoredReadlimit) {
+ _marked_offset = _current_offset;
+ _marked_offset_count = _current_block_count;
+ }
+
+ /**
+ * Tests if this input stream supports the mark and reset methods.
+ *
+ * @return <code>true</code> always
+ */
+ public boolean markSupported() {
+ return true;
+ }
+
+ public int read() throws IOException {
+ dieIfClosed();
+ if (atEOD()) {
+ return EOF;
+ }
+ byte[] b = new byte[1];
+ int result = read(b, 0, 1);
+ if(result >= 0) {
+ if(b[0] < 0) {
+ return b[0]+256;
+ }
+ return b[0];
+ }
+ return result;
+ }
+
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException {
+ dieIfClosed();
+ if (b == null) {
+ throw new IllegalArgumentException("buffer must not be null");
+ }
+ if (off < 0 || len < 0 || b.length < off + len) {
+ throw new IndexOutOfBoundsException("can't read past buffer boundaries");
+ }
+ if (len == 0) {
+ return 0;
+ }
+ if (atEOD()) {
+ return EOF;
+ }
+ int limit = Math.min(available(), len);
+ readFully(b, off, limit);
+ return limit;
+ }
+
+ /**
+ * Repositions this stream to the position at the time the mark() method was
+ * last called on this input stream. If mark() has not been called this
+ * method repositions the stream to its beginning.
+ */
+ public void reset() {
+ // Special case for reset to the start
+ if(_marked_offset == 0 && _marked_offset_count == 0) {
+ _current_block_count = _marked_offset_count;
+ _current_offset = _marked_offset;
+ _data = _document.getBlockIterator();
+ _buffer = null;
+ return;
+ }
+
+ // Start again, then wind on to the required block
+ _data = _document.getBlockIterator();
+ _current_offset = 0;
+ for(int i=0; i<_marked_offset_count; i++) {
+ _buffer = _data.next();
+ _current_offset += _buffer.remaining();
+ }
+
+ _current_block_count = _marked_offset_count;
+
+ // Do we need to position within it?
+ if(_current_offset != _marked_offset) {
+ // Grab the right block
+ _buffer = _data.next();
+ _current_block_count++;
+
+ // Skip to the right place in it
+ _buffer.position(_marked_offset - _current_offset);
+ }
+
+ // All done
+ _current_offset = _marked_offset;
+ }
+
+ public long skip(long n) throws IOException {
+ dieIfClosed();
+ if (n < 0) {
+ return 0;
+ }
+ int new_offset = _current_offset + (int) n;
+
+ if (new_offset < _current_offset) {
+ // wrap around in converting a VERY large long to an int
+ new_offset = _document_size;
+ } else if (new_offset > _document_size) {
+ new_offset = _document_size;
+ }
+
+ long rval = new_offset - _current_offset;
+
+ // TODO Do this better
+ byte[] skip = new byte[(int)rval];
+ readFully(skip);
+ return rval;
+ }
+
+ private void dieIfClosed() throws IOException {
+ if (_closed) {
+ throw new IOException("cannot perform requested operation on a closed stream");
+ }
+ }
+
+ private boolean atEOD() {
+ return _current_offset == _document_size;
+ }
+
+ private void checkAvaliable(int requestedSize) {
+ if (_closed) {
+ throw new IllegalStateException("cannot perform requested operation on a closed stream");
+ }
+ if (requestedSize > _document_size - _current_offset) {
+ throw new RuntimeException("Buffer underrun - requested " + requestedSize
+ + " bytes but " + (_document_size - _current_offset) + " was available");
+ }
+ }
+
+ public byte readByte() {
+ return (byte) readUByte();
+ }
+
+ public double readDouble() {
+ return Double.longBitsToDouble(readLong());
+ }
+
+ public void readFully(byte[] buf) {
+ readFully(buf, 0, buf.length);
+ }
+
+ public short readShort() {
+ return (short) readUShort();
+ }
+
+ public void readFully(byte[] buf, int off, int len) {
+ checkAvaliable(len);
+
+ int read = 0;
+ while(read < len) {
+ if(_buffer == null || _buffer.remaining() == 0) {
+ _current_block_count++;
+ _buffer = _data.next();
+ }
+
+ int limit = Math.min(len-read, _buffer.remaining());
+ _buffer.get(buf, off+read, limit);
+ _current_offset += limit;
+ read += limit;
+ }
+ }
+
+ public long readLong() {
+ checkAvaliable(SIZE_LONG);
+ byte[] data = new byte[SIZE_LONG];
+ readFully(data, 0, SIZE_LONG);
+ return LittleEndian.getLong(data, 0);
+ }
+
+ public int readInt() {
+ checkAvaliable(SIZE_INT);
+ byte[] data = new byte[SIZE_INT];
+ readFully(data, 0, SIZE_INT);
+ return LittleEndian.getInt(data);
+ }
+
+ public int readUShort() {
+ checkAvaliable(SIZE_SHORT);
+ byte[] data = new byte[SIZE_SHORT];
+ readFully(data, 0, SIZE_SHORT);
+ return LittleEndian.getShort(data);
+ }
+
+ public int readUByte() {
+ checkAvaliable(1);
+ byte[] data = new byte[1];
+ readFully(data, 0, 1);
+ if(data[0] >= 0)
+ return data[0];
+ return data[0] + 256;
+ }
+}