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