001    /**
002     * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.io.unsync;
016    
017    import java.io.IOException;
018    import java.io.InputStream;
019    
020    /**
021     * <p>
022     * See http://issues.liferay.com/browse/LPS-6648.
023     * </p>
024     *
025     * @author Shuyang Zhou
026     */
027    public class UnsyncBufferedInputStream extends UnsyncFilterInputStream {
028    
029            public UnsyncBufferedInputStream(InputStream inputStream) {
030                    this(inputStream, _DEFAULT_BUFFER_SIZE);
031            }
032    
033            public UnsyncBufferedInputStream(InputStream inputStream, int size) {
034                    super(inputStream);
035    
036                    buffer = new byte[size];
037            }
038    
039            public int available() throws IOException {
040                    if (inputStream == null) {
041                            throw new IOException("Input stream is null");
042                    }
043    
044                    return inputStream.available() + (firstInvalidIndex - index);
045            }
046    
047            public void close() throws IOException {
048                    if (inputStream != null) {
049                            inputStream.close();
050    
051                            inputStream = null;
052                    }
053    
054                    buffer = null;
055            }
056    
057            public void mark(int readLimit) {
058                    markLimit = readLimit;
059                    markIndex = index;
060            }
061    
062            public boolean markSupported() {
063                    return true;
064            }
065    
066            public int read() throws IOException {
067                    if (inputStream == null) {
068                            throw new IOException("Input stream is null");
069                    }
070    
071                    if (index >= firstInvalidIndex) {
072                            readUnderlyingInputStream();
073    
074                            if (index >= firstInvalidIndex) {
075                                    return -1;
076                            }
077                    }
078    
079                    return buffer[index++] & 0xff;
080            }
081    
082            public int read(byte[] byteArray) throws IOException {
083                    return read(byteArray, 0, byteArray.length);
084            }
085    
086            public int read(byte[] byteArray, int offset, int length)
087                    throws IOException {
088    
089                    if (inputStream == null) {
090                            throw new IOException("Input stream is null");
091                    }
092    
093                    if (length <= 0) {
094                            return 0;
095                    }
096    
097                    int read = 0;
098    
099                    while (true) {
100                            int available = firstInvalidIndex - index;
101    
102                            if ((available + read) >= length) {
103    
104                                    // Enough data, stop reading
105    
106                                    int leftSize = length - read;
107    
108                                    System.arraycopy(
109                                            buffer, index, byteArray, offset + read, leftSize);
110    
111                                    index += leftSize;
112    
113                                    return length;
114                            }
115    
116                            if (available <= 0) {
117    
118                                    // No more data in buffer, continue reading
119    
120                                    readUnderlyingInputStream();
121    
122                                    available = firstInvalidIndex - index;
123    
124                                    if (available <= 0) {
125    
126                                            // Cannot read any more, stop reading
127    
128                                            if (read == 0) {
129                                                    return -1;
130                                            }
131                                            else {
132                                                    return read;
133                                            }
134                                    }
135                            }
136                            else {
137    
138                                    // Copy all in-memory data, continue reading
139    
140                                    System.arraycopy(
141                                            buffer, index, byteArray, offset + read, available);
142    
143                                    index += available;
144                                    read += available;
145                            }
146                    }
147            }
148            public void reset() throws IOException {
149                    if (inputStream == null) {
150                            throw new IOException("Input stream is null");
151                    }
152    
153                    if (markIndex < 0) {
154                            throw new IOException("Resetting to invalid mark");
155                    }
156    
157                    index = markIndex;
158            }
159    
160            public long skip(long skip) throws IOException {
161                    if (inputStream == null) {
162                            throw new IOException("Input stream is null");
163                    }
164    
165                    if (skip <= 0) {
166                            return 0;
167                    }
168                    long available = firstInvalidIndex - index;
169    
170                    if (available > 0) {
171    
172                            // Skip the data in buffer
173    
174                            if (available < skip) {
175                                    skip = available;
176                            }
177                    }
178                    else {
179    
180                            // Skip the underlying input stream
181    
182                            if (markIndex < 0) {
183    
184                                    // No mark required, skip
185    
186                                    skip = inputStream.skip(skip);
187                            }
188                            else {
189    
190                                    // Mark required, save the skipped data
191    
192                                    readUnderlyingInputStream();
193    
194                                    available = firstInvalidIndex - index;
195    
196                                    if (available > 0) {
197    
198                                            // Skip the data in buffer
199    
200                                            if (available < skip) {
201                                                    skip = available;
202                                            }
203                                    }
204                            }
205                    }
206    
207                    index += skip;
208    
209                    return skip;
210            }
211    
212            protected void readUnderlyingInputStream() throws IOException {
213                    if (markIndex < 0) {
214    
215                            // No mark required, fill the buffer
216    
217                            index = firstInvalidIndex = 0;
218    
219                            int number = inputStream.read(buffer);
220    
221                            if (number > 0) {
222                                    firstInvalidIndex = number;
223                            }
224    
225                            return;
226                    }
227    
228                    // Mark required
229    
230                    if (index >= buffer.length) {
231    
232                            // Buffer is full, clean up or grow
233    
234                            if ((firstInvalidIndex - markIndex) > markLimit) {
235    
236                                    // Passed mark limit, get rid of all cache data
237    
238                                    markIndex = -1;
239                                    index = 0;
240                            }
241                            else if (markIndex > _MAX_MARK_WASTE_SIZE) {
242    
243                                    // There are more than _MAX_MARK_WASTE_SIZE free space at the
244                                    // beginning of buffer, clean up by shuffling the buffer
245    
246                                    int realDataSize = index - markIndex;
247    
248                                    System.arraycopy(
249                                            buffer, markIndex, buffer, 0, realDataSize);
250    
251                                    markIndex = 0;
252                                    index = realDataSize;
253                            }
254                            else {
255    
256                                    // Grow the buffer because we cannot get rid of cache data and
257                                    // it is inefficient to shuffle the buffer
258    
259                                    int newBufferSize = index << 1;
260    
261                                    if ((newBufferSize - _MAX_MARK_WASTE_SIZE) > markLimit) {
262    
263                                            // Make thew new buffer size larger than the mark limit
264    
265                                            newBufferSize = markLimit + _MAX_MARK_WASTE_SIZE;
266                                    }
267    
268                                    byte[] newBuffer = new byte[newBufferSize];
269    
270                                    System.arraycopy(buffer, 0, newBuffer, 0, index);
271    
272                                    buffer = newBuffer;
273                            }
274                    }
275    
276                    // Read underlying input stream since the buffer has more space
277    
278                    firstInvalidIndex = index;
279    
280                    int number = inputStream.read(buffer, index, buffer.length - index);
281    
282                    if (number > 0) {
283                            firstInvalidIndex += number;
284                    }
285            }
286    
287            protected byte[] buffer;
288            protected int firstInvalidIndex;
289            protected int index;
290            protected int markIndex = -1;
291            protected int markLimit;
292    
293            private static int _DEFAULT_BUFFER_SIZE = 8192;
294    
295            private static int _MAX_MARK_WASTE_SIZE = 4096;
296    
297    }