001/**
002 * Java Web Archive Toolkit - Software to read and validate ARC, WARC
003 * and GZip files. (http://jwat.org/)
004 * Copyright 2011-2012 Netarkivet.dk (http://netarkivet.dk/)
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.jwat.common;
019
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.PushbackInputStream;
023
024/**
025 * Basic <code>PushBackInputStream</code> that also keeps track of the number
026 * of consumed bytes at any given time.
027 *
028 * @author nicl
029 */
030public class ByteCountingPushBackInputStream extends PushbackInputStream {
031
032    /** Read line initial size. */
033    public static final int READLINE_INITIAL_SIZE = 128;
034
035    /** Pushback buffer size. */
036    protected int pushback_size;
037
038    /** Offset relative to beginning of stream. */
039    protected long consumed = 0;
040
041    /** Byte counter which can also be changed. */
042    protected long counter = 0;
043
044    /**
045     * Given an <code>InputStream</code> and a push back buffer size returns
046     * a wrapped input stream with push back capabilities.
047     * @param in <code>InputStream</code> to wrap
048     * @param size push back buffer size
049     */
050    public ByteCountingPushBackInputStream(InputStream in, int size) {
051        super(in, size);
052        pushback_size = size;
053    }
054
055    /**
056     * Get the pushback buffer size.
057     * @return pushback buffer size
058     */
059    public int getPushbackSize() {
060        return pushback_size;
061    }
062
063    /**
064     * Retrieve the number of bytes consumed by this stream.
065     * @return current byte offset in this stream
066     */
067    public long getConsumed() {
068        return consumed;
069    }
070
071    /**
072     * Change the counter value.
073     * Useful for reading zero indexed relative data.
074     * @param bytes new counter value
075     */
076    public void setCounter(long bytes) {
077        counter = bytes;
078    }
079
080    /**
081     * Retrieve the current counter value.
082     * @return current counter value
083     */
084    public long getCounter() {
085        return counter;
086    }
087
088    @Override
089    public boolean markSupported() {
090        return false;
091    }
092
093    @Override
094    public synchronized void mark(int readlimit) {
095    }
096
097    @Override
098    public synchronized void reset() throws IOException {
099        throw new UnsupportedOperationException();
100    }
101
102    @Override
103    public int read() throws IOException {
104        int b = super.read();
105        if (b != -1) {
106            ++consumed;
107            ++counter;
108        }
109        return b;
110    }
111
112    /*
113     * The super method did this anyway causing a double amount of
114     * consumed bytes.
115     * @see java.io.FilterInputStream#read(byte[])
116     */
117    @Override
118    public int read(byte[] b) throws IOException {
119        return read(b, 0, b.length);
120    }
121
122    @Override
123    public int read(byte[] b, int off, int len) throws IOException {
124        int bytesRead = super.read(b, off, len);
125        if (bytesRead > 0) {
126            consumed += bytesRead;
127            counter += bytesRead;
128        }
129        return bytesRead;
130    }
131
132    @Override
133    public long skip(long n) throws IOException {
134        long bytesSkipped = super.skip(n);
135        consumed += bytesSkipped;
136        counter += bytesSkipped;
137        return bytesSkipped;
138    }
139
140    @Override
141    public void unread(int b) throws IOException {
142        super.unread(b);
143        --consumed;
144        --counter;
145    }
146
147    /*
148     * The super method did this anyway causing a double amount of
149     * un-consumed bytes.
150     * @see java.io.PushbackInputStream#unread(byte[])
151     */
152    @Override
153    public void unread(byte[] b) throws IOException {
154        unread(b, 0, b.length);
155    }
156
157    @Override
158    public void unread(byte[] b, int off, int len) throws IOException {
159        super.unread(b, off, len);
160        consumed -= len;
161        counter -= len;
162    }
163
164    /**
165     * Read a single line into a string.
166     * @return single string line
167     * @throws IOException if an I/O error occurs while reading line
168     */
169    public String readLine() throws IOException {
170        StringBuffer sb = new StringBuffer(READLINE_INITIAL_SIZE);
171        int b;
172        while (true) {
173            b = read();
174            if (b == -1) {
175                return null;    //Unexpected EOF
176            }
177            if (b == '\n'){
178                break;
179            }
180            if (b != '\r') {
181                sb.append((char) b);
182            }
183        }
184        return sb.toString();
185    }
186
187    /**
188     * Guaranteed to read the exact number of bytes that are in the array,
189     * if not, the bytes are pushed back into the stream before returning.
190     * @param buffer byte buffer to read bytes into
191     * @return the number of bytes read into array
192     * @throws IOException if an I/O error occurs while reading array
193     */
194    public int readFully(byte[] buffer) throws IOException {
195        int readOffset = 0;
196        int readRemaining = buffer.length;
197        int readLast = 0;
198        while (readRemaining > 0 && readLast != -1) {
199            readRemaining -= readLast;
200            readOffset += readLast;
201            readLast = read(buffer, readOffset, readRemaining);
202        }
203        if (readRemaining > 0) {
204            unread(buffer, 0, readOffset);
205            readOffset = 0;
206        }
207        return readOffset;
208    }
209
210    /**
211     * Peek into the stream by filling as much of the supplied array as
212     * possible before unreading the stream and returning.
213     * @param buffer byte buffer to read bytes into
214     * @return the number of bytes read into array
215     * @throws IOException if an I/O error occurs while reading array
216     */
217    public int peek(byte[] buffer) throws IOException {
218        int readOffset = 0;
219        int readRemaining = buffer.length;
220        int readLast = 0;
221        while (readRemaining > 0 && readLast != -1) {
222            readRemaining -= readLast;
223            readOffset += readLast;
224            readLast = read(buffer, readOffset, readRemaining);
225        }
226        if (readOffset > 0) {
227            unread(buffer, 0, readOffset);
228        }
229        return readOffset;
230    }
231
232}