001/*
002 *  Copyright 2013 Chris Pheby
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.jadira.lang.io.io2nio;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.nio.BufferUnderflowException;
021import java.nio.ByteBuffer;
022
023/**
024 * An InputStream that reads from a ByteBuffer. This can be used to adapt the NIO type to standard Java IO.
025 */
026public class ByteBufferBackedInputStream extends InputStream {
027
028    private boolean isClosed = false;
029
030    private ByteBuffer buf;
031
032    private int mark = -1;
033
034    private int readlimit = -1;
035
036    /**
037     * Creates a new instance for the given ByteBuffer.
038     * @param buf The ByteBuffer to use
039     */
040    public ByteBufferBackedInputStream(ByteBuffer buf) {
041        this.buf = buf;
042    }
043
044    @Override
045    public int read() throws IOException {
046
047        if (isClosed) {
048            throw new IOException("ByteBufferInputStream was already closed");
049        }
050
051        if (!buf.hasRemaining()) {
052            return -1;
053        }
054        try {
055            final int res = buf.get() & 0xFF;
056            invalidateMark();
057
058            return res;
059        } catch (BufferUnderflowException e) {
060            throw new IOException("Unexpected Error reading from ByteBuffer: " + e.getMessage(), e);
061        }
062    }
063
064    private void invalidateMark() {
065        if (mark >= 0 && readlimit >= 0) {
066            if (buf.position() > (mark + readlimit)) {
067                mark = -1;
068                readlimit = -1;
069            }
070        }
071    }
072
073    @Override
074    public int read(byte[] bytes, int off, int len) throws IOException {
075
076        if (isClosed) {
077            throw new IOException("ByteBufferInputStream was already closed");
078        }
079
080        if (!buf.hasRemaining()) {
081            return -1;
082        }
083
084        len = Math.min(len, buf.remaining());
085
086        try {
087            buf.get(bytes, off, len);
088        } catch (BufferUnderflowException e) {
089            throw new IOException("Unexpected Error reading from ByteBuffer: " + e.getMessage(), e);
090        } catch (IndexOutOfBoundsException e) {
091            throw new IOException("Unexpected Error reading from ByteBuffer: " + e.getMessage(), e);
092        }
093        invalidateMark();
094        return len;
095    }
096
097    @Override
098    public long skip(long n) throws IOException {
099
100        if (isClosed) {
101            throw new IOException("ByteBufferInputStream was already closed");
102        }
103
104        long bytesSkipped = 0;
105
106        int position = buf.position();
107        int remaining = buf.limit() - position;
108
109        int len = (int) (Math.min(n, (long) remaining));
110
111        buf.position(position + len);
112
113        invalidateMark();
114        return bytesSkipped;
115    }
116
117    public int available() throws IOException {
118
119        if (isClosed) {
120            throw new IOException("available() but ByteBufferInputStream was already closed");
121        }
122
123        return buf.limit() - buf.position();
124    };
125
126    public void close() throws IOException {
127        isClosed = true;
128    }
129
130    public void mark(int readlimit) {
131
132        mark = buf.position();
133        this.readlimit = readlimit;
134    }
135
136    public void rewind() throws IOException {
137        position(0);
138    }
139
140    public void position(int position) throws IOException {
141
142        if (isClosed) {
143            throw new IOException("ByteBufferInputStream was already closed");
144        }
145
146        buf.rewind();
147        skip(position);
148        invalidateMark();
149    }
150
151    public void reset() throws IOException {
152
153        if (isClosed) {
154            throw new IOException("ByteBufferInputStream was already closed");
155        }
156
157        if (mark < 0) {
158            throw new IOException("Attempted to call reset() but mark was not valid");
159        }
160        buf.position(mark);
161    }
162
163    public boolean markSupported() {
164        return true;
165    }
166}