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