001    /**
002     * Copyright (c) 2000-2013 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 com.liferay.portal.kernel.util.CharPool;
018    import com.liferay.portal.kernel.util.StringBundler;
019    
020    import java.io.IOException;
021    import java.io.Reader;
022    
023    /**
024     * <p>
025     * See http://issues.liferay.com/browse/LPS-6648.
026     * </p>
027     *
028     * @author Shuyang Zhou
029     */
030    public class UnsyncBufferedReader extends Reader {
031    
032            public UnsyncBufferedReader(Reader reader) {
033                    this(reader, _DEFAULT_BUFFER_SIZE);
034            }
035    
036            public UnsyncBufferedReader(Reader reader, int size) {
037                    if (size <= 0) {
038                            throw new IllegalArgumentException("Size is less than 0");
039                    }
040    
041                    this.reader = reader;
042    
043                    buffer = new char[size];
044            }
045    
046            @Override
047            public void close() throws IOException {
048                    if (reader != null) {
049                            reader.close();
050    
051                            reader = null;
052                            buffer = null;
053                    }
054            }
055    
056            @Override
057            public void mark(int markLimit) throws IOException {
058                    if (markLimit < 0) {
059                            throw new IllegalArgumentException("Mark limit is less than 0");
060                    }
061    
062                    if (reader == null) {
063                            throw new IOException("Reader is null");
064                    }
065    
066                    if (markLimit == 0) {
067                            return;
068                    }
069    
070                    markLimitIndex = markLimit;
071    
072                    if (index == 0) {
073                            return;
074                    }
075    
076                    int available = firstInvalidIndex - index;
077    
078                    if (available > 0) {
079    
080                            // Shuffle mark beginning to buffer beginning
081    
082                            System.arraycopy(buffer, index, buffer, 0, available);
083    
084                            index = 0;
085    
086                            firstInvalidIndex = available;
087                    }
088                    else {
089    
090                            // Reset buffer states
091    
092                            index = firstInvalidIndex = 0;
093                    }
094            }
095    
096            @Override
097            public boolean markSupported() {
098                    return true;
099            }
100    
101            @Override
102            public int read() throws IOException {
103                    if (reader == null) {
104                            throw new IOException("Reader is null");
105                    }
106    
107                    if (index >= firstInvalidIndex) {
108                            fillInBuffer();
109    
110                            if (index >= firstInvalidIndex) {
111                                    return -1;
112                            }
113                    }
114    
115                    return buffer[index++];
116            }
117    
118            @Override
119            public int read(char[] chars) throws IOException {
120                    return read(chars, 0, chars.length);
121            }
122    
123            @Override
124            public int read(char[] chars, int offset, int length) throws IOException {
125                    if (reader == null) {
126                            throw new IOException("Reader is null");
127                    }
128    
129                    if (length <= 0) {
130                            return 0;
131                    }
132    
133                    int read = 0;
134    
135                    while (true) {
136    
137                            // Try to at least read some data
138    
139                            int currentRead = readOnce(chars, offset + read, length - read);
140    
141                            if (currentRead <= 0) {
142                                    if (read == 0) {
143                                            read = currentRead;
144                                    }
145    
146                                    break;
147                            }
148    
149                            read += currentRead;
150    
151                            if (!reader.ready() || (read >= length)) {
152    
153                                    // Read enough or further reading may be blocked, stop reading
154    
155                                    break;
156                            }
157                    }
158    
159                    return read;
160            }
161    
162            public String readLine() throws IOException {
163                    if (reader == null) {
164                            throw new IOException("Reader is null");
165                    }
166    
167                    StringBundler sb = null;
168    
169                    while (true) {
170                            if (index >= firstInvalidIndex) {
171                                    fillInBuffer();
172                            }
173    
174                            if (index >= firstInvalidIndex) {
175                                    if ((sb != null) && (sb.index() > 0)) {
176                                            return sb.toString();
177                                    }
178                                    else {
179                                            return null;
180                                    }
181                            }
182    
183                            boolean hasLineBreak = false;
184                            char lineEndChar = 0;
185    
186                            int x = index;
187                            int y = index;
188    
189                            while (y < firstInvalidIndex) {
190                                    lineEndChar = buffer[y];
191    
192                                    if ((lineEndChar == CharPool.NEW_LINE) ||
193                                            (lineEndChar == CharPool.RETURN)) {
194    
195                                            hasLineBreak = true;
196    
197                                            break;
198                                    }
199    
200                                    y++;
201                            }
202    
203                            String line = new String(buffer, x, y - x);
204    
205                            index = y;
206    
207                            if (hasLineBreak) {
208                                    index++;
209    
210                                    if (lineEndChar == CharPool.RETURN) {
211                                            if ((index < buffer.length) &&
212                                                    (buffer[index] == CharPool.NEW_LINE)) {
213    
214                                                    index++;
215                                            }
216                                    }
217    
218                                    if (sb == null) {
219                                            return line;
220                                    }
221    
222                                    sb.append(line);
223    
224                                    return sb.toString();
225                            }
226    
227                            if (sb == null) {
228                                    sb = new StringBundler();
229                            }
230    
231                            sb.append(line);
232                    }
233            }
234    
235            @Override
236            public boolean ready() throws IOException {
237                    if (reader == null) {
238                            throw new IOException("Reader is null");
239                    }
240    
241                    return (index < firstInvalidIndex) || reader.ready();
242            }
243    
244            @Override
245            public void reset() throws IOException {
246                    if (reader == null) {
247                            throw new IOException("Reader is null");
248                    }
249    
250                    if (markLimitIndex < 0) {
251                            throw new IOException("Resetting to invalid mark");
252                    }
253    
254                    index = 0;
255            }
256    
257            @Override
258            public long skip(long skip) throws IOException {
259                    if (skip < 0) {
260                            throw new IllegalArgumentException("Skip is less than 0");
261                    }
262    
263                    if (reader == null) {
264                            throw new IOException("Reader is null");
265                    }
266    
267                    if (skip == 0) {
268                            return 0;
269                    }
270    
271                    long available = firstInvalidIndex - index;
272    
273                    if (available <= 0) {
274                            if (markLimitIndex < 0) {
275    
276                                    // No mark required, skip the underlying input stream
277    
278                                    return reader.skip(skip);
279                            }
280                            else {
281    
282                                    // Mark required, save the skipped data
283    
284                                    fillInBuffer();
285    
286                                    available = firstInvalidIndex - index;
287    
288                                    if (available <= 0) {
289                                            return 0;
290                                    }
291                            }
292                    }
293    
294                    // Skip the data in buffer
295    
296                    if (available < skip) {
297                            skip = available;
298                    }
299    
300                    index += skip;
301    
302                    return skip;
303            }
304    
305            protected void fillInBuffer() throws IOException {
306                    if (markLimitIndex < 0) {
307    
308                            // No mark required, fill the buffer
309    
310                            index = firstInvalidIndex = 0;
311    
312                            int number = reader.read(buffer);
313    
314                            if (number > 0) {
315                                    firstInvalidIndex = number;
316                            }
317    
318                            return;
319                    }
320    
321                    // Mark required
322    
323                    if (index >= markLimitIndex) {
324    
325                            // Passed mark limit indexs, get rid of all cache data
326    
327                            markLimitIndex = -1;
328    
329                            index = firstInvalidIndex = 0;
330                    }
331                    else if (index == buffer.length) {
332    
333                            // Cannot get rid of cache data and there is no room to read in any
334                            // more data, so grow the buffer
335    
336                            int newBufferSize = buffer.length * 2;
337    
338                            if (newBufferSize > markLimitIndex) {
339                                    newBufferSize = markLimitIndex;
340                            }
341    
342                            char[] newBuffer = new char[newBufferSize];
343    
344                            System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
345    
346                            buffer = newBuffer;
347                    }
348    
349                    // Read the underlying input stream since the buffer has more space
350    
351                    firstInvalidIndex = index;
352    
353                    int number = reader.read(buffer, index, buffer.length - index);
354    
355                    if (number > 0) {
356                            firstInvalidIndex += number;
357                    }
358            }
359    
360            protected int readOnce(char[] chars, int offset, int length)
361                    throws IOException {
362    
363                    int available = firstInvalidIndex - index;
364    
365                    if (available <= 0) {
366    
367                            // Buffer is empty, read from underlying reader
368    
369                            if ((markLimitIndex < 0) && (length >= buffer.length)) {
370    
371                                    // No mark required, left read block is no less than buffer,
372                                    // read through buffer is inefficient, so directly read from
373                                    // underlying reader
374    
375                                    return reader.read(chars, offset, length);
376                            }
377                            else {
378    
379                                    // Mark is required, has to read through the buffer to remember
380                                    // data
381    
382                                    fillInBuffer();
383    
384                                    available = firstInvalidIndex - index;
385    
386                                    if (available <= 0) {
387                                            return -1;
388                                    }
389                            }
390                    }
391    
392                    if (length > available) {
393                            length = available;
394                    }
395    
396                    System.arraycopy(buffer, index, chars, offset, length);
397    
398                    index += length;
399    
400                    return length;
401            }
402    
403            protected char[] buffer;
404            protected int firstInvalidIndex;
405            protected int index;
406            protected int markLimitIndex = -1;
407            protected Reader reader;
408    
409            private static final int _DEFAULT_BUFFER_SIZE = 8192;
410    
411    }