001    /**
002     * Copyright (c) 2000-present 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 https://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                    if ((index < firstInvalidIndex) || reader.ready()) {
242                            return true;
243                    }
244    
245                    return false;
246            }
247    
248            @Override
249            public void reset() throws IOException {
250                    if (reader == null) {
251                            throw new IOException("Reader is null");
252                    }
253    
254                    if (markLimitIndex < 0) {
255                            throw new IOException("Resetting to invalid mark");
256                    }
257    
258                    index = 0;
259            }
260    
261            @Override
262            public long skip(long skip) throws IOException {
263                    if (skip < 0) {
264                            throw new IllegalArgumentException("Skip is less than 0");
265                    }
266    
267                    if (reader == null) {
268                            throw new IOException("Reader is null");
269                    }
270    
271                    if (skip == 0) {
272                            return 0;
273                    }
274    
275                    long available = firstInvalidIndex - index;
276    
277                    if (available <= 0) {
278                            if (markLimitIndex < 0) {
279    
280                                    // No mark required, skip the underlying input stream
281    
282                                    return reader.skip(skip);
283                            }
284                            else {
285    
286                                    // Mark required, save the skipped data
287    
288                                    fillInBuffer();
289    
290                                    available = firstInvalidIndex - index;
291    
292                                    if (available <= 0) {
293                                            return 0;
294                                    }
295                            }
296                    }
297    
298                    // Skip the data in buffer
299    
300                    if (available < skip) {
301                            skip = available;
302                    }
303    
304                    index += skip;
305    
306                    return skip;
307            }
308    
309            protected void fillInBuffer() throws IOException {
310                    if (markLimitIndex < 0) {
311    
312                            // No mark required, fill the buffer
313    
314                            index = firstInvalidIndex = 0;
315    
316                            int number = reader.read(buffer);
317    
318                            if (number > 0) {
319                                    firstInvalidIndex = number;
320                            }
321    
322                            return;
323                    }
324    
325                    // Mark required
326    
327                    if (index >= markLimitIndex) {
328    
329                            // Passed mark limit indexs, get rid of all cache data
330    
331                            markLimitIndex = -1;
332    
333                            index = firstInvalidIndex = 0;
334                    }
335                    else if (index == buffer.length) {
336    
337                            // Cannot get rid of cache data and there is no room to read in any
338                            // more data, so grow the buffer
339    
340                            int newBufferSize = buffer.length * 2;
341    
342                            if (newBufferSize > markLimitIndex) {
343                                    newBufferSize = markLimitIndex;
344                            }
345    
346                            char[] newBuffer = new char[newBufferSize];
347    
348                            System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
349    
350                            buffer = newBuffer;
351                    }
352    
353                    // Read the underlying input stream since the buffer has more space
354    
355                    firstInvalidIndex = index;
356    
357                    int number = reader.read(buffer, index, buffer.length - index);
358    
359                    if (number > 0) {
360                            firstInvalidIndex += number;
361                    }
362            }
363    
364            protected int readOnce(char[] chars, int offset, int length)
365                    throws IOException {
366    
367                    int available = firstInvalidIndex - index;
368    
369                    if (available <= 0) {
370    
371                            // Buffer is empty, read from underlying reader
372    
373                            if ((markLimitIndex < 0) && (length >= buffer.length)) {
374    
375                                    // No mark required, left read block is no less than buffer,
376                                    // read through buffer is inefficient, so directly read from
377                                    // underlying reader
378    
379                                    return reader.read(chars, offset, length);
380                            }
381                            else {
382    
383                                    // Mark is required, has to read through the buffer to remember
384                                    // data
385    
386                                    fillInBuffer();
387    
388                                    available = firstInvalidIndex - index;
389    
390                                    if (available <= 0) {
391                                            return -1;
392                                    }
393                            }
394                    }
395    
396                    if (length > available) {
397                            length = available;
398                    }
399    
400                    System.arraycopy(buffer, index, chars, offset, length);
401    
402                    index += length;
403    
404                    return length;
405            }
406    
407            protected char[] buffer;
408            protected int firstInvalidIndex;
409            protected int index;
410            protected int markLimitIndex = -1;
411            protected Reader reader;
412    
413            private static final int _DEFAULT_BUFFER_SIZE = 8192;
414    
415    }