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 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                    this.reader = reader;
038                    buffer = new char[size];
039            }
040    
041            public void close() throws IOException {
042                    if (reader != null) {
043                            reader.close();
044    
045                            reader = null;
046                    }
047    
048                    buffer = null;
049            }
050    
051            public void mark(int markLimit) throws IOException {
052                    if (reader == null) {
053                            throw new IOException("Reader is null");
054                    }
055    
056                    this.markLimit = markLimit;
057                    markIndex = index;
058            }
059    
060            public boolean markSupported() {
061                    return true;
062            }
063    
064            public int read() throws IOException {
065                    if (reader == null) {
066                            throw new IOException("Reader is null");
067                    }
068    
069                    if (index >= firstInvalidIndex) {
070                            readUnderlyingReader();
071    
072                            if (index >= firstInvalidIndex) {
073                                    return -1;
074                            }
075                    }
076    
077                    return buffer[index++];
078            }
079    
080            public int read(char[] charArray) throws IOException {
081                    return read(charArray, 0, charArray.length);
082            }
083    
084            public int read(char[] charArray, int offset, int length)
085                    throws IOException {
086    
087                    if (reader == null) {
088                            throw new IOException("Reader is null");
089                    }
090    
091                    if (length <= 0) {
092                            return 0;
093                    }
094    
095                    int read = 0;
096    
097                    while (true) {
098                            int available = firstInvalidIndex - index;
099    
100                            if ((available + read) >= length) {
101    
102                                    // Enough data, stop reading
103    
104                                    int leftSize = length - read;
105    
106                                    System.arraycopy(buffer, index, charArray, read, leftSize);
107    
108                                    index += leftSize;
109    
110                                    return length;
111                            }
112    
113                            if (available <= 0) {
114    
115                                    // No more data in buffer, continue reading
116    
117                                    readUnderlyingReader();
118    
119                                    available = firstInvalidIndex - index;
120    
121                                    if (available <= 0) {
122    
123                                            // Cannot read any more, stop reading
124    
125                                            if (read == 0) {
126                                                    return -1;
127                                            }
128                                            else {
129                                                    return read;
130                                            }
131                                    }
132                            }
133                            else {
134    
135                                    // Copy all in-memory data, continue reading
136    
137                                    System.arraycopy(buffer, index, charArray, read, available);
138    
139                                    index += available;
140                                    read += available;
141                            }
142                    }
143            }
144    
145            public String readLine() throws IOException {
146                    if (reader == null) {
147                            throw new IOException("Reader is null");
148                    }
149    
150                    StringBundler sb = null;
151    
152                    while (true) {
153                            if (index >= firstInvalidIndex) {
154                                    readUnderlyingReader();
155                            }
156    
157                            if (index >= firstInvalidIndex) {
158                                    if ((sb != null) && (sb.index() > 0)) {
159                                            return sb.toString();
160                                    }
161                                    else {
162                                            return null;
163                                    }
164                            }
165    
166                            boolean hasLineBreak = false;
167                            char lineEndChar = 0;
168    
169                            int x = index;
170                            int y = index;
171    
172                            while (y < firstInvalidIndex) {
173                                    lineEndChar = buffer[y];
174    
175                                    if ((lineEndChar == CharPool.NEW_LINE) ||
176                                            (lineEndChar == CharPool.RETURN)) {
177    
178                                            hasLineBreak = true;
179    
180                                            break;
181                                    }
182    
183                                    y++;
184                            }
185    
186                            String line = new String(buffer, x, y - x);
187    
188                            index = y;
189    
190                            if (hasLineBreak) {
191                                    index++;
192    
193                                    if (lineEndChar == CharPool.RETURN) {
194                                            if ((index < buffer.length) &&
195                                                    (buffer[index] == CharPool.NEW_LINE)) {
196    
197                                                    index++;
198                                            }
199                                    }
200    
201                                    if (sb == null) {
202                                            return line;
203                                    }
204                                    else {
205                                            sb.append(line);
206    
207                                            return sb.toString();
208                                    }
209                            }
210    
211                            if (sb == null) {
212                                    sb = new StringBundler();
213                            }
214    
215                            sb.append(line);
216                    }
217            }
218    
219            public boolean ready() throws IOException {
220                    if (reader == null) {
221                            throw new IOException("Reader is null");
222                    }
223    
224                    return (index < firstInvalidIndex) || reader.ready();
225            }
226    
227            public void reset() throws IOException {
228                    if (reader == null) {
229                            throw new IOException("Reader is null");
230                    }
231    
232                    if (markIndex < 0) {
233                            throw new IOException("Resetting to invalid mark");
234                    }
235    
236                    index = markIndex;
237            }
238    
239            public long skip(long skip) throws IOException {
240                    if (reader == null) {
241                            throw new IOException("Reader is null");
242                    }
243    
244                    if (skip <= 0) {
245                            return 0;
246                    }
247    
248                    long available = firstInvalidIndex - index;
249    
250                    if (available > 0) {
251    
252                            // Skip the data in buffer
253    
254                            if (available < skip) {
255                                    skip = available;
256                            }
257                    }
258                    else {
259    
260                            // Skip the underlying reader
261    
262                            if (markIndex < 0) {
263    
264                                    // No mark required, skip
265    
266                                    skip = reader.skip(skip);
267                            }
268                            else {
269    
270                                    // Mark required, save the skipped data
271    
272                                    readUnderlyingReader();
273    
274                                    available = firstInvalidIndex - index;
275    
276                                    if (available > 0) {
277    
278                                            // Skip the data in buffer
279    
280                                            if (available < skip) {
281                                                    skip = available;
282                                            }
283                                    }
284                            }
285                    }
286    
287                    index += skip;
288    
289                    return skip;
290            }
291    
292            protected void readUnderlyingReader() throws IOException {
293                    if (markIndex < 0) {
294    
295                            // No mark required, fill the buffer
296    
297                            index = firstInvalidIndex = 0;
298    
299                            int number = reader.read(buffer);
300    
301                            if (number > 0) {
302                                    firstInvalidIndex = number;
303                            }
304    
305                            return;
306                    }
307    
308                    // Mark required
309    
310                    if (index >= buffer.length) {
311    
312                            // Buffer is full, clean up or grow
313    
314                            if ((firstInvalidIndex - markIndex) > markLimit) {
315    
316                                    // Passed mark limit, get rid of all cache data
317    
318                                    markIndex = -1;
319                                    index = 0;
320                            }
321                            else if (markIndex > _MAX_MARK_WASTE_SIZE) {
322    
323                                    // There are more than _MAX_MARK_WASTE_SIZE free space at the
324                                    // beginning of buffer, clean up by shuffling the buffer
325    
326                                    int realDataSize = index - markIndex;
327    
328                                    System.arraycopy(
329                                            buffer, markIndex, buffer, 0, realDataSize);
330    
331                                    markIndex = 0;
332                                    index = realDataSize;
333                            }
334                            else {
335    
336                                    // Grow the buffer because we cannot get rid of cache data and
337                                    // it is inefficient to shuffle the buffer
338    
339                                    int newBufferSize = index << 1;
340    
341                                    if ((newBufferSize - _MAX_MARK_WASTE_SIZE) > markLimit) {
342    
343                                            // Make thew new buffer size larger than the mark limit
344    
345                                            newBufferSize = markLimit + _MAX_MARK_WASTE_SIZE;
346                                    }
347    
348                                    char[] newBuffer = new char[newBufferSize];
349    
350                                    System.arraycopy(buffer, 0, newBuffer, 0, index);
351    
352                                    buffer = newBuffer;
353                            }
354                    }
355    
356                    // Read underlying reader since the buffer has more space
357    
358                    firstInvalidIndex = index;
359    
360                    int number = reader.read(buffer, index, buffer.length - index);
361    
362                    if (number > 0) {
363                            firstInvalidIndex += number;
364                    }
365            }
366    
367            protected char[] buffer;
368            protected int firstInvalidIndex;
369            protected int index;
370            protected int markIndex = -1;
371            protected int markLimit;
372            protected Reader reader;
373    
374            private static int _DEFAULT_BUFFER_SIZE = 8192;
375    
376            private static int _MAX_MARK_WASTE_SIZE = 4096;
377    
378    }