Coverage Report - org.apache.myfaces.shared.util.StreamCharBuffer
 
Classes in this File Line Coverage Branch Coverage Complexity
StreamCharBuffer
31%
92/294
15%
23/144
2.181
StreamCharBuffer$1
N/A
N/A
2.181
StreamCharBuffer$AbstractChunk
50%
4/8
50%
1/2
2.181
StreamCharBuffer$AbstractChunkReader
0%
0/22
0%
0/14
2.181
StreamCharBuffer$AllocatedBuffer
48%
18/37
33%
2/6
2.181
StreamCharBuffer$AllocatedBufferReader
0%
0/17
0%
0/10
2.181
StreamCharBuffer$CharBufferChunk
0%
0/13
0%
0/4
2.181
StreamCharBuffer$CharBufferChunkReader
0%
0/9
N/A
2.181
StreamCharBuffer$ChunkReader
0%
0/1
N/A
2.181
StreamCharBuffer$ConnectedWriter
0%
0/15
0%
0/8
2.181
StreamCharBuffer$FixedCharArrayWriter
47%
8/17
N/A
2.181
StreamCharBuffer$LazyInitializingWriter
N/A
N/A
2.181
StreamCharBuffer$MultiOutputWriter
0%
0/21
0%
0/8
2.181
StreamCharBuffer$SingleOutputWriter
0%
0/13
N/A
2.181
StreamCharBuffer$StreamCharBufferKey
50%
1/2
N/A
2.181
StreamCharBuffer$StreamCharBufferReader
0%
0/81
0%
0/70
2.181
StreamCharBuffer$StreamCharBufferSubChunk
0%
0/23
0%
0/10
2.181
StreamCharBuffer$StreamCharBufferSubChunkReader
0%
0/7
N/A
2.181
StreamCharBuffer$StreamCharBufferWriter
25%
31/120
10%
9/82
2.181
StreamCharBuffer$StringChunk
72%
8/11
50%
2/4
2.181
StreamCharBuffer$StringChunkReader
0%
0/9
N/A
2.181
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  *   http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License.
 18  
  */
 19  
 package org.apache.myfaces.shared.util;
 20  
 
 21  
 import java.io.EOFException;
 22  
 import java.io.Externalizable;
 23  
 import java.io.IOException;
 24  
 import java.io.ObjectInput;
 25  
 import java.io.ObjectOutput;
 26  
 import java.io.Reader;
 27  
 import java.io.Writer;
 28  
 import java.lang.ref.SoftReference;
 29  
 import java.util.ArrayList;
 30  
 import java.util.HashMap;
 31  
 import java.util.HashSet;
 32  
 import java.util.Iterator;
 33  
 import java.util.List;
 34  
 import java.util.Map;
 35  
 import java.util.Set;
 36  
 
 37  
 /**
 38  
  * <p>
 39  
  * StreamCharBuffer is a multipurpose in-memory buffer that can replace JDK
 40  
  * in-memory buffers (StringBuffer, StringBuilder, StringWriter).
 41  
  * </p>
 42  
  *
 43  
  * <p>
 44  
  * Grails GSP rendering uses this class as a buffer that is optimized for performance.
 45  
  * </p>
 46  
  *
 47  
  * <p>
 48  
  * StreamCharBuffer keeps the buffer in a linked list of "chunks". The main
 49  
  * difference compared to JDK in-memory buffers (StringBuffer, StringBuilder &
 50  
  * StringWriter) is that the buffer can be held in several smaller buffers
 51  
  * ("chunks" here). In JDK in-memory buffers, the buffer has to be expanded
 52  
  * whenever it gets filled up. The old buffer's data is copied to the new one
 53  
  * and the old one is discarded. In StreamCharBuffer, there are several ways to
 54  
  * prevent unnecessary allocation & copy operations. The StreamCharBuffer
 55  
  * contains a linked list of different type of chunks: char arrays,
 56  
  * java.lang.String chunks and other StreamCharBuffers as sub chunks. A
 57  
  * StringChunk is appended to the linked list whenever a java.lang.String of a
 58  
  * length that exceeds the "stringChunkMinSize" value is written to the buffer.
 59  
  * </p>
 60  
  *
 61  
  * <p>
 62  
  * Grails tag libraries also use a StreamCharBuffer to "capture" the output of
 63  
  * the taglib and return it to the caller. The buffer can be appended to it's
 64  
  * parent buffer directly without extra object generation (like converting to
 65  
  * java.lang.String in between).
 66  
  *
 67  
  * for example this line of code in a taglib would just append the buffer
 68  
  * returned from the body closure evaluation to the buffer of the taglib:<br>
 69  
  * <code>
 70  
  * out << body()
 71  
  * </code><br>
 72  
  * other example:<br>
 73  
  * <code>
 74  
  * out << g.render(template: '/some/template', model:[somebean: somebean])
 75  
  * </code><br>
 76  
  * There's no extra java.lang.String generation overhead.
 77  
  *
 78  
  * </p>
 79  
  *
 80  
  * <p>
 81  
  * There's a java.io.Writer interface for appending character data to the buffer
 82  
  * and a java.io.Reader interface for reading data.
 83  
  * </p>
 84  
  *
 85  
  * <p>
 86  
  * Each {@link #getReader()} call will create a new reader instance that keeps
 87  
  * it own state.<br>
 88  
  * There is a alternative method {@link #getReader(boolean)} for creating the
 89  
  * reader. When reader is created by calling getReader(true), the reader will
 90  
  * remove already read characters from the buffer. In this mode only a single
 91  
  * reader instance is supported.
 92  
  * </p>
 93  
  *
 94  
  * <p>
 95  
  * There's also several other options for reading data:<br>
 96  
  * {@link #readAsCharArray()} reads the buffer to a char[] array<br>
 97  
  * {@link #readAsString()} reads the buffer and wraps the char[] data as a
 98  
  * String<br>
 99  
  * {@link #writeTo(Writer)} writes the buffer to a java.io.Writer<br>
 100  
  * {@link #toCharArray()} returns the buffer as a char[] array, caches the
 101  
  * return value internally so that this method can be called several times.<br>
 102  
  * {@link #toString()} returns the buffer as a String, caches the return value
 103  
  * internally<br>
 104  
  * </p>
 105  
  *
 106  
  * <p>
 107  
  * By using the "connectTo" method, one can connect the buffer directly to a
 108  
  * target java.io.Writer. The internal buffer gets flushed automaticly to the
 109  
  * target whenever the buffer gets filled up. See connectTo(Writer).
 110  
  * </p>
 111  
  *
 112  
  * <p>
 113  
  * <b>This class is not thread-safe.</b> Object instances of this class are
 114  
  * intended to be used by a single Thread. The Reader and Writer interfaces can
 115  
  * be open simultaneous and the same Thread can write and read several times.
 116  
  * </p>
 117  
  *
 118  
  * <p>
 119  
  * Main operation principle:<br>
 120  
  * </p>
 121  
  * <p>
 122  
  * StreamCharBuffer keeps the buffer in a linked link of "chunks".<br>
 123  
  * The main difference compared to JDK in-memory buffers (StringBuffer,
 124  
  * StringBuilder & StringWriter) is that the buffer can be held in several
 125  
  * smaller buffers ("chunks" here).<br>
 126  
  * In JDK in-memory buffers, the buffer has to be expanded whenever it gets
 127  
  * filled up. The old buffer's data is copied to the new one and the old one is
 128  
  * discarded.<br>
 129  
  * In StreamCharBuffer, there are several ways to prevent unnecessary allocation
 130  
  * & copy operations.
 131  
  * </p>
 132  
  * <p>
 133  
  * There can be several different type of chunks: char arrays (
 134  
  * {@code CharBufferChunk}), String chunks ({@code StringChunk}) and other
 135  
  * StreamCharBuffers as sub chunks ({@code StreamCharBufferSubChunk}).
 136  
  * </p>
 137  
  * <p>
 138  
  * Child StreamCharBuffers can be changed after adding to parent buffer. The
 139  
  * flush() method must be called on the child buffer's Writer to notify the
 140  
  * parent that the child buffer's content has been changed (used for calculating
 141  
  * total size).
 142  
  * </p>
 143  
  * <p>
 144  
  * A StringChunk is appended to the linked list whenever a java.lang.String of a
 145  
  * length that exceeds the "stringChunkMinSize" value is written to the buffer.
 146  
  * </p>
 147  
  * <p>
 148  
  * If the buffer is in "connectTo" mode, any String or char[] that's length is
 149  
  * over writeDirectlyToConnectedMinSize gets written directly to the target. The
 150  
  * buffer content will get fully flushed to the target before writing the String
 151  
  * or char[].
 152  
  * </p>
 153  
  * <p>
 154  
  * There can be several targets "listening" the buffer in "connectTo" mode. The
 155  
  * same content will be written to all targets.
 156  
  * <p>
 157  
  * <p>
 158  
  * Growable chunksize: By default, a newly allocated chunk's size will grow
 159  
  * based on the total size of all written chunks.<br>
 160  
  * The default growProcent value is 100. If the total size is currently 1024,
 161  
  * the newly created chunk will have a internal buffer that's size is 1024.<br>
 162  
  * Growable chunksize can be turned off by setting the growProcent to 0.<br>
 163  
  * There's a default maximum chunksize of 1MB by default. The minimum size is
 164  
  * the initial chunksize size.<br>
 165  
  * </p>
 166  
  *
 167  
  * <p>
 168  
  * System properties to change default configuration parameters:<br>
 169  
  * <table>
 170  
  * <tr>
 171  
  * <th>System Property name</th>
 172  
  * <th>Description</th>
 173  
  * <th>Default value</th>
 174  
  * </tr>
 175  
  * <tr>
 176  
  * <td>streamcharbuffer.chunksize</td>
 177  
  * <td>default chunk size - the size the first allocated buffer</td>
 178  
  * <td>512</td>
 179  
  * </tr>
 180  
  * <tr>
 181  
  * <td>streamcharbuffer.maxchunksize</td>
 182  
  * <td>maximum chunk size - the maximum size of the allocated buffer</td>
 183  
  * <td>1048576</td>
 184  
  * </tr>
 185  
  * <tr>
 186  
  * <td>streamcharbuffer.growprocent</td>
 187  
  * <td>growing buffer percentage - the newly allocated buffer is defined by
 188  
  * total_size * (growpercent/100)</td>
 189  
  * <td>100</td>
 190  
  * </tr>
 191  
  * <tr>
 192  
  * <td>streamcharbuffer.subbufferchunkminsize</td>
 193  
  * <td>minimum size of child StreamCharBuffer chunk - if the size is smaller,
 194  
  * the content is copied to the parent buffer</td>
 195  
  * <td>512</td>
 196  
  * </tr>
 197  
  * <tr>
 198  
  * <td>streamcharbuffer.substringchunkminsize</td>
 199  
  * <td>minimum size of String chunks - if the size is smaller, the content is
 200  
  * copied to the buffer</td>
 201  
  * <td>512</td>
 202  
  * </tr>
 203  
  * <tr>
 204  
  * <td>streamcharbuffer.chunkminsize</td>
 205  
  * <td>minimum size of chunk that gets written directly to the target in
 206  
  * connected mode.</td>
 207  
  * <td>256</td>
 208  
  * </tr>
 209  
  * </table>
 210  
  *
 211  
  * Configuration values can also be changed for each instance of
 212  
  * StreamCharBuffer individually. Default values are defined with System
 213  
  * Properties.
 214  
  *
 215  
  * </p>
 216  
  *
 217  
  * @author Lari Hotari, Sagire Software Oy
 218  
  * @see org.codehaus.groovy.grails.web.util.StreamCharBuffer
 219  
  *      file licensed under ASL v2.0 
 220  
  *      Copyright 2009 the original author or authors.
 221  
  */
 222  26
 public class StreamCharBuffer implements /*Writable,*/CharSequence,
 223  
         Externalizable
 224  
 {
 225  
     static final long serialVersionUID = 5486972234419632945L;
 226  
     //private static final Log log=LogFactory.getLog(StreamCharBuffer.class);
 227  
 
 228  1
     private static final int DEFAULT_CHUNK_SIZE = Integer.getInteger(
 229  
             "oam.streamcharbuffer.chunksize", 512);
 230  1
     private static final int DEFAULT_MAX_CHUNK_SIZE = Integer.getInteger(
 231  
             "oam.streamcharbuffer.maxchunksize", 1024 * 1024);
 232  1
     private static final int DEFAULT_CHUNK_SIZE_GROW_PROCENT = Integer
 233  
             .getInteger("oam.streamcharbuffer.growprocent", 100);
 234  1
     private static final int SUB_BUFFERCHUNK_MIN_SIZE = Integer.getInteger(
 235  
             "oam.streamcharbuffer.subbufferchunkminsize", 512);
 236  1
     private static final int SUB_STRINGCHUNK_MIN_SIZE = Integer.getInteger(
 237  
             "oam.streamcharbuffer.substringchunkminsize", 512);
 238  1
     private static final int WRITE_DIRECT_MIN_SIZE = Integer.getInteger(
 239  
             "oam.streamcharbuffer.writedirectminsize", 1024);
 240  1
     private static final int CHUNK_MIN_SIZE = Integer.getInteger(
 241  
             "oam.streamcharbuffer.chunkminsize", 256);
 242  
 
 243  
     private final int firstChunkSize;
 244  
     private final int growProcent;
 245  
     private final int maxChunkSize;
 246  7
     private int subStringChunkMinSize = SUB_STRINGCHUNK_MIN_SIZE;
 247  7
     private int subBufferChunkMinSize = SUB_BUFFERCHUNK_MIN_SIZE;
 248  7
     private int writeDirectlyToConnectedMinSize = WRITE_DIRECT_MIN_SIZE;
 249  7
     private int chunkMinSize = CHUNK_MIN_SIZE;
 250  
 
 251  
     private int chunkSize;
 252  
     private int totalChunkSize;
 253  
 
 254  
     private final StreamCharBufferWriter writer;
 255  
     private List<ConnectedWriter> connectedWriters;
 256  
     private Writer connectedWritersWriter;
 257  
 
 258  7
     boolean preferSubChunkWhenWritingToOtherBuffer = false;
 259  
 
 260  
     private AllocatedBuffer allocBuffer;
 261  
     private AbstractChunk firstChunk;
 262  
     private AbstractChunk lastChunk;
 263  
     private int totalCharsInList;
 264  
     private int totalCharsInDynamicChunks;
 265  
     private int sizeAtLeast;
 266  7
     private StreamCharBufferKey bufferKey = new StreamCharBufferKey();
 267  
     private Map<StreamCharBufferKey, StreamCharBufferSubChunk> dynamicChunkMap;
 268  
 
 269  
     private Set<SoftReference<StreamCharBufferKey>> parentBuffers;
 270  7
     int allocatedBufferIdSequence = 0;
 271  7
     int readerCount = 0;
 272  7
     boolean hasReaders = false;
 273  
 
 274  
     public StreamCharBuffer()
 275  
     {
 276  0
         this(DEFAULT_CHUNK_SIZE, DEFAULT_CHUNK_SIZE_GROW_PROCENT,
 277  
                 DEFAULT_MAX_CHUNK_SIZE);
 278  0
     }
 279  
 
 280  
     public StreamCharBuffer(int chunkSize)
 281  
     {
 282  0
         this(chunkSize, DEFAULT_CHUNK_SIZE_GROW_PROCENT, DEFAULT_MAX_CHUNK_SIZE);
 283  0
     }
 284  
 
 285  
     public StreamCharBuffer(int chunkSize, int growProcent)
 286  
     {
 287  7
         this(chunkSize, growProcent, DEFAULT_MAX_CHUNK_SIZE);
 288  7
     }
 289  
 
 290  
     public StreamCharBuffer(int chunkSize, int growProcent, int maxChunkSize)
 291  7
     {
 292  7
         this.firstChunkSize = chunkSize;
 293  7
         this.growProcent = growProcent;
 294  7
         this.maxChunkSize = maxChunkSize;
 295  7
         writer = new StreamCharBufferWriter();
 296  7
         reset(true);
 297  7
     }
 298  
 
 299  14
     private class StreamCharBufferKey
 300  
     {
 301  
         StreamCharBuffer getBuffer()
 302  
         {
 303  0
             return StreamCharBuffer.this;
 304  
         }
 305  
     }
 306  
 
 307  
     public boolean isPreferSubChunkWhenWritingToOtherBuffer()
 308  
     {
 309  0
         return preferSubChunkWhenWritingToOtherBuffer;
 310  
     }
 311  
 
 312  
     public void setPreferSubChunkWhenWritingToOtherBuffer(
 313  
             boolean preferSubChunkWhenWritingToOtherBuffer)
 314  
     {
 315  0
         this.preferSubChunkWhenWritingToOtherBuffer = preferSubChunkWhenWritingToOtherBuffer;
 316  0
     }
 317  
 
 318  
     public final void reset()
 319  
     {
 320  7
         reset(true);
 321  7
     }
 322  
 
 323  
     /**
 324  
      * resets the state of this buffer (empties it)
 325  
      *
 326  
      * @param resetChunkSize
 327  
      */
 328  
     public final void reset(boolean resetChunkSize)
 329  
     {
 330  14
         firstChunk = null;
 331  14
         lastChunk = null;
 332  14
         totalCharsInList = 0;
 333  14
         totalCharsInDynamicChunks = -1;
 334  14
         sizeAtLeast = -1;
 335  14
         if (resetChunkSize)
 336  
         {
 337  14
             chunkSize = firstChunkSize;
 338  14
             totalChunkSize = 0;
 339  
         }
 340  14
         allocBuffer = new AllocatedBuffer(chunkSize);
 341  14
         dynamicChunkMap = new HashMap<StreamCharBufferKey, StreamCharBufferSubChunk>();
 342  14
     }
 343  
 
 344  
     /**
 345  
      * Clears the buffer and notifies the parents of this buffer of the change
 346  
      * 
 347  
      */
 348  
     public final void clear()
 349  
     {
 350  0
         reset();
 351  0
         notifyBufferChange();
 352  0
     }
 353  
 
 354  
     /**
 355  
      * Connect this buffer to a target Writer.
 356  
      *
 357  
      * When the buffer (a chunk) get filled up, it will automaticly write it's content to the Writer
 358  
      *
 359  
      * @param w
 360  
      */
 361  
     public final void connectTo(Writer w)
 362  
     {
 363  0
         connectTo(w, true);
 364  0
     }
 365  
 
 366  
     public final void connectTo(Writer w, boolean autoFlush)
 367  
     {
 368  0
         initConnected();
 369  0
         connectedWriters.add(new ConnectedWriter(w, autoFlush));
 370  0
         initConnectedWritersWriter();
 371  0
     }
 372  
 
 373  
     private void initConnectedWritersWriter()
 374  
     {
 375  0
         if (connectedWriters.size() > 1)
 376  
         {
 377  0
             connectedWritersWriter = new MultiOutputWriter(connectedWriters);
 378  
         }
 379  
         else
 380  
         {
 381  0
             connectedWritersWriter = new SingleOutputWriter(
 382  
                     connectedWriters.get(0));
 383  
         }
 384  0
     }
 385  
 
 386  
     public final void connectTo(LazyInitializingWriter w)
 387  
     {
 388  0
         connectTo(w, true);
 389  0
     }
 390  
 
 391  
     public final void connectTo(LazyInitializingWriter w, boolean autoFlush)
 392  
     {
 393  0
         initConnected();
 394  0
         connectedWriters.add(new ConnectedWriter(w, autoFlush));
 395  0
         initConnectedWritersWriter();
 396  0
     }
 397  
 
 398  
     public final void removeConnections()
 399  
     {
 400  0
         if (connectedWriters != null)
 401  
         {
 402  0
             connectedWriters.clear();
 403  0
             connectedWritersWriter = null;
 404  
         }
 405  0
     }
 406  
 
 407  
     private void initConnected()
 408  
     {
 409  0
         if (connectedWriters == null)
 410  
         {
 411  0
             connectedWriters = new ArrayList<ConnectedWriter>(2);
 412  
         }
 413  0
     }
 414  
 
 415  
     public int getSubStringChunkMinSize()
 416  
     {
 417  0
         return subStringChunkMinSize;
 418  
     }
 419  
 
 420  
     /**
 421  
      * Minimum size for a String to be added as a StringChunk instead of copying content to 
 422  
      * the char[] buffer of the current StreamCharBufferChunk
 423  
      *
 424  
      * @param stringChunkMinSize
 425  
      */
 426  
     public void setSubStringChunkMinSize(int stringChunkMinSize)
 427  
     {
 428  0
         this.subStringChunkMinSize = stringChunkMinSize;
 429  0
     }
 430  
 
 431  
     public int getSubBufferChunkMinSize()
 432  
     {
 433  0
         return subBufferChunkMinSize;
 434  
     }
 435  
 
 436  
     public void setSubBufferChunkMinSize(int subBufferChunkMinSize)
 437  
     {
 438  0
         this.subBufferChunkMinSize = subBufferChunkMinSize;
 439  0
     }
 440  
 
 441  
     public int getWriteDirectlyToConnectedMinSize()
 442  
     {
 443  0
         return writeDirectlyToConnectedMinSize;
 444  
     }
 445  
 
 446  
     /**
 447  
      * Minimum size for a String or char[] to get written directly to connected writer (in "connectTo" mode).
 448  
      *
 449  
      * @param writeDirectlyToConnectedMinSize
 450  
      */
 451  
     public void setWriteDirectlyToConnectedMinSize(
 452  
             int writeDirectlyToConnectedMinSize)
 453  
     {
 454  0
         this.writeDirectlyToConnectedMinSize = writeDirectlyToConnectedMinSize;
 455  0
     }
 456  
 
 457  
     public int getChunkMinSize()
 458  
     {
 459  0
         return chunkMinSize;
 460  
     }
 461  
 
 462  
     public void setChunkMinSize(int chunkMinSize)
 463  
     {
 464  0
         this.chunkMinSize = chunkMinSize;
 465  0
     }
 466  
 
 467  
     /**
 468  
      * Writer interface for adding/writing data to the buffer.
 469  
      *
 470  
      * @return the Writer
 471  
      */
 472  
     public Writer getWriter()
 473  
     {
 474  14
         return writer;
 475  
     }
 476  
 
 477  
     /**
 478  
      * Creates a new Reader instance for reading/consuming data from the buffer.
 479  
      * Each call creates a new instance that will keep it's reading state. There can be several readers on
 480  
      * the buffer. (single thread only supported)
 481  
      *
 482  
      * @return the Reader
 483  
      */
 484  
     public Reader getReader()
 485  
     {
 486  0
         return getReader(false);
 487  
     }
 488  
 
 489  
     /**
 490  
      * Like getReader(), but when removeAfterReading is true, the read data will be removed from the buffer.
 491  
      *
 492  
      * @param removeAfterReading
 493  
      * @return the Reader
 494  
      */
 495  
     public Reader getReader(boolean removeAfterReading)
 496  
     {
 497  0
         readerCount++;
 498  0
         hasReaders = true;
 499  0
         return new StreamCharBufferReader(removeAfterReading);
 500  
     }
 501  
 
 502  
     /**
 503  
      * Writes the buffer content to a target java.io.Writer
 504  
      *
 505  
      * @param target
 506  
      * @throws IOException
 507  
      */
 508  
     public Writer writeTo(Writer target) throws IOException
 509  
     {
 510  7
         writeTo(target, false, false);
 511  7
         return getWriter();
 512  
     }
 513  
 
 514  
     /**
 515  
      * Writes the buffer content to a target java.io.Writer
 516  
      *
 517  
      * @param target Writer
 518  
      * @param flushTarget calls target.flush() before finishing
 519  
      * @param emptyAfter empties the buffer if true
 520  
      * @throws IOException
 521  
      */
 522  
     public void writeTo(Writer target, boolean flushTarget, boolean emptyAfter)
 523  
             throws IOException
 524  
     {
 525  
         //if (target instanceof GrailsWrappedWriter) {
 526  
         //    target = ((GrailsWrappedWriter)target).unwrap();
 527  
         //}
 528  7
         if (target instanceof StreamCharBufferWriter)
 529  
         {
 530  0
             if (target == writer)
 531  
             {
 532  0
                 throw new IllegalArgumentException(
 533  
                         "Cannot write buffer to itself.");
 534  
             }
 535  0
             ((StreamCharBufferWriter) target).write(this);
 536  0
             return;
 537  
         }
 538  7
         writeToImpl(target, flushTarget, emptyAfter);
 539  7
     }
 540  
 
 541  
     private void writeToImpl(Writer target, boolean flushTarget,
 542  
             boolean emptyAfter) throws IOException
 543  
     {
 544  7
         AbstractChunk current = firstChunk;
 545  7
         while (current != null)
 546  
         {
 547  0
             current.writeTo(target);
 548  0
             current = current.next;
 549  
         }
 550  7
         if (emptyAfter)
 551  
         {
 552  0
             firstChunk = null;
 553  0
             lastChunk = null;
 554  0
             totalCharsInList = 0;
 555  0
             totalCharsInDynamicChunks = -1;
 556  0
             sizeAtLeast = -1;
 557  0
             dynamicChunkMap.clear();
 558  
         }
 559  7
         allocBuffer.writeTo(target);
 560  7
         if (emptyAfter)
 561  
         {
 562  0
             allocBuffer.reuseBuffer();
 563  
         }
 564  7
         if (flushTarget)
 565  
         {
 566  0
             target.flush();
 567  
         }
 568  7
     }
 569  
 
 570  
     /**
 571  
      * Reads the buffer to a char[].
 572  
      *
 573  
      * @return the chars
 574  
      */
 575  
     public char[] readAsCharArray()
 576  
     {
 577  7
         int currentSize = size();
 578  7
         if (currentSize == 0)
 579  
         {
 580  0
             return new char[0];
 581  
         }
 582  
 
 583  7
         FixedCharArrayWriter target = new FixedCharArrayWriter(currentSize);
 584  
         try
 585  
         {
 586  7
             writeTo(target);
 587  
         }
 588  0
         catch (IOException e)
 589  
         {
 590  0
             throw new RuntimeException("Unexpected IOException", e);
 591  7
         }
 592  7
         return target.getCharArray();
 593  
     }
 594  
 
 595  
     /**
 596  
      * Reads the buffer to a String.
 597  
      *
 598  
      * @return the String
 599  
      */
 600  
     public String readAsString()
 601  
     {
 602  7
         char[] buf = readAsCharArray();
 603  7
         if (buf.length > 0)
 604  
         {
 605  7
             return StringCharArrayAccessor.createString(buf);
 606  
         }
 607  
 
 608  0
         return "";
 609  
     }
 610  
 
 611  
     /**
 612  
      * {@inheritDoc}
 613  
      *
 614  
      * Reads (and empties) the buffer to a String, but caches the return value for subsequent calls.
 615  
      * If more content has been added between 2 calls, the returned value will be joined from the previously 
 616  
      * cached value and the data read from the buffer.
 617  
      *
 618  
      * @see java.lang.Object#toString()
 619  
      */
 620  
     @Override
 621  
     public String toString()
 622  
     {
 623  10
         if (firstChunk == lastChunk && firstChunk instanceof StringChunk
 624  
                 && allocBuffer.charsUsed() == 0
 625  
                 && ((StringChunk) firstChunk).isSingleBuffer())
 626  
         {
 627  3
             return ((StringChunk) firstChunk).str;
 628  
         }
 629  
 
 630  7
         int initialReaderCount = readerCount;
 631  7
         String str = readAsString();
 632  7
         if (initialReaderCount == 0)
 633  
         {
 634  
             // if there are no readers, the result can be cached
 635  7
             reset();
 636  7
             if (str.length() > 0)
 637  
             {
 638  7
                 addChunk(new StringChunk(str, 0, str.length()));
 639  
             }
 640  
         }
 641  7
         return str;
 642  
     }
 643  
 
 644  
     /**
 645  
      * {@inheritDoc}
 646  
      *
 647  
      * Uses String's hashCode to support compatibility with String instances in maps, sets, etc.
 648  
      *
 649  
      * @see java.lang.Object#hashCode()
 650  
      */
 651  
     @Override
 652  
     public int hashCode()
 653  
     {
 654  0
         return toString().hashCode();
 655  
     }
 656  
 
 657  
     /**
 658  
      * equals uses String.equals to check for equality to support compatibility with String instances 
 659  
      * in maps, sets, etc.
 660  
      *
 661  
      * @see java.lang.Object#equals(java.lang.Object)
 662  
      */
 663  
     @Override
 664  
     public boolean equals(Object o)
 665  
     {
 666  0
         if (o == this)
 667  
         {
 668  0
             return true;
 669  
         }
 670  
 
 671  0
         if (!(o instanceof CharSequence))
 672  
         {
 673  0
             return false;
 674  
         }
 675  
 
 676  0
         CharSequence other = (CharSequence) o;
 677  
 
 678  0
         return toString().equals(other.toString());
 679  
     }
 680  
 
 681  
     public String plus(String value)
 682  
     {
 683  0
         return toString() + value;
 684  
     }
 685  
 
 686  
     public String plus(Object value)
 687  
     {
 688  0
         return toString() + String.valueOf(value);
 689  
     }
 690  
 
 691  
     /**
 692  
      * Reads the buffer to a char[].
 693  
      *
 694  
      * Caches the result if there aren't any readers.
 695  
      *
 696  
      * @return the chars
 697  
      */
 698  
     public char[] toCharArray()
 699  
     {
 700  
         // check if there is a cached single charbuffer
 701  0
         if (firstChunk == lastChunk && firstChunk instanceof CharBufferChunk
 702  
                 && allocBuffer.charsUsed() == 0
 703  
                 && ((CharBufferChunk) firstChunk).isSingleBuffer())
 704  
         {
 705  0
             return ((CharBufferChunk) firstChunk).buffer;
 706  
         }
 707  
 
 708  0
         int initialReaderCount = readerCount;
 709  0
         char[] buf = readAsCharArray();
 710  0
         if (initialReaderCount == 0)
 711  
         {
 712  
             // if there are no readers, the result can be cached
 713  0
             reset();
 714  0
             if (buf.length > 0)
 715  
             {
 716  0
                 addChunk(new CharBufferChunk(-1, buf, 0, buf.length));
 717  
             }
 718  
         }
 719  0
         return buf;
 720  
     }
 721  
 
 722  
     public int size()
 723  
     {
 724  7
         int total = totalCharsInList;
 725  7
         if (totalCharsInDynamicChunks == -1)
 726  
         {
 727  7
             totalCharsInDynamicChunks = 0;
 728  7
             for (StreamCharBufferSubChunk chunk : dynamicChunkMap.values())
 729  
             {
 730  0
                 totalCharsInDynamicChunks += chunk.size();
 731  0
             }
 732  
         }
 733  7
         total += totalCharsInDynamicChunks;
 734  7
         total += allocBuffer.charsUsed();
 735  7
         sizeAtLeast = total;
 736  7
         return total;
 737  
     }
 738  
 
 739  
     public boolean isEmpty()
 740  
     {
 741  0
         return !isNotEmpty();
 742  
     }
 743  
 
 744  
     boolean isNotEmpty()
 745  
     {
 746  0
         if (totalCharsInList > 0)
 747  
         {
 748  0
             return true;
 749  
         }
 750  0
         if (totalCharsInDynamicChunks > 0)
 751  
         {
 752  0
             return true;
 753  
         }
 754  0
         if (allocBuffer.charsUsed() > 0)
 755  
         {
 756  0
             return true;
 757  
         }
 758  0
         if (totalCharsInDynamicChunks == -1)
 759  
         {
 760  0
             for (StreamCharBufferSubChunk chunk : dynamicChunkMap.values())
 761  
             {
 762  0
                 if (chunk.getSubBuffer().isNotEmpty())
 763  
                 {
 764  0
                     return true;
 765  
                 }
 766  0
             }
 767  
         }
 768  0
         return false;
 769  
     }
 770  
 
 771  
     boolean isSizeLarger(int minSize)
 772  
     {
 773  0
         if (minSize <= sizeAtLeast)
 774  
         {
 775  0
             return true;
 776  
         }
 777  
 
 778  0
         boolean retval = calculateIsSizeLarger(minSize);
 779  0
         if (retval && minSize > sizeAtLeast)
 780  
         {
 781  0
             sizeAtLeast = minSize;
 782  
         }
 783  0
         return retval;
 784  
     }
 785  
 
 786  
     private boolean calculateIsSizeLarger(int minSize)
 787  
     {
 788  0
         int total = totalCharsInList;
 789  0
         total += allocBuffer.charsUsed();
 790  0
         if (total > minSize)
 791  
         {
 792  0
             return true;
 793  
         }
 794  0
         if (totalCharsInDynamicChunks != -1)
 795  
         {
 796  0
             total += totalCharsInDynamicChunks;
 797  0
             if (total > minSize)
 798  
             {
 799  0
                 return true;
 800  
             }
 801  
         }
 802  
         else
 803  
         {
 804  0
             for (StreamCharBufferSubChunk chunk : dynamicChunkMap.values())
 805  
             {
 806  0
                 if (!chunk.hasCachedSize()
 807  
                         && chunk.getSubBuffer().isSizeLarger(minSize - total))
 808  
                 {
 809  0
                     return true;
 810  
                 }
 811  0
                 total += chunk.size();
 812  0
                 if (total > minSize)
 813  
                 {
 814  0
                     return true;
 815  
                 }
 816  0
             }
 817  
         }
 818  0
         return false;
 819  
     }
 820  
 
 821  
     int allocateSpace() throws IOException
 822  
     {
 823  16
         int spaceLeft = allocBuffer.spaceLeft();
 824  16
         if (spaceLeft == 0)
 825  
         {
 826  0
             spaceLeft = appendCharBufferChunk(true);
 827  
         }
 828  16
         return spaceLeft;
 829  
     }
 830  
 
 831  
     private int appendCharBufferChunk(boolean flushInConnected)
 832  
             throws IOException
 833  
     {
 834  0
         int spaceLeft = 0;
 835  0
         if (flushInConnected && isConnectedMode())
 836  
         {
 837  0
             flushToConnected();
 838  0
             if (!isChunkSizeResizeable())
 839  
             {
 840  0
                 allocBuffer.reuseBuffer();
 841  0
                 spaceLeft = allocBuffer.spaceLeft();
 842  
             }
 843  
             else
 844  
             {
 845  0
                 spaceLeft = 0;
 846  
             }
 847  
         }
 848  
         else
 849  
         {
 850  0
             if (allocBuffer.hasChunk())
 851  
             {
 852  0
                 addChunk(allocBuffer.createChunk());
 853  
             }
 854  0
             spaceLeft = allocBuffer.spaceLeft();
 855  
         }
 856  0
         if (spaceLeft == 0)
 857  
         {
 858  0
             totalChunkSize += allocBuffer.chunkSize();
 859  0
             resizeChunkSizeAsProcentageOfTotalSize();
 860  0
             allocBuffer = new AllocatedBuffer(chunkSize);
 861  0
             spaceLeft = allocBuffer.spaceLeft();
 862  
         }
 863  0
         return spaceLeft;
 864  
     }
 865  
 
 866  
     void appendStringChunk(String str, int off, int len) throws IOException
 867  
     {
 868  0
         appendCharBufferChunk(false);
 869  0
         addChunk(new StringChunk(str, off, len));
 870  0
     }
 871  
 
 872  
     public void appendStreamCharBufferChunk(StreamCharBuffer subBuffer)
 873  
             throws IOException
 874  
     {
 875  0
         appendCharBufferChunk(false);
 876  0
         addChunk(new StreamCharBufferSubChunk(subBuffer));
 877  0
     }
 878  
 
 879  
     void addChunk(AbstractChunk newChunk)
 880  
     {
 881  7
         if (lastChunk != null)
 882  
         {
 883  0
             lastChunk.next = newChunk;
 884  0
             if (hasReaders)
 885  
             {
 886  
                 // double link only if there are active readers since backwards iterating is only required 
 887  
                 //for simultaneous writer & reader
 888  0
                 newChunk.prev = lastChunk;
 889  
             }
 890  
         }
 891  7
         lastChunk = newChunk;
 892  7
         if (firstChunk == null)
 893  
         {
 894  7
             firstChunk = newChunk;
 895  
         }
 896  7
         if (newChunk instanceof StreamCharBufferSubChunk)
 897  
         {
 898  0
             StreamCharBufferSubChunk bufSubChunk = (StreamCharBufferSubChunk) newChunk;
 899  0
             dynamicChunkMap.put(bufSubChunk.streamCharBuffer.bufferKey,
 900  
                     bufSubChunk);
 901  0
         }
 902  
         else
 903  
         {
 904  7
             totalCharsInList += newChunk.size();
 905  
         }
 906  7
     }
 907  
 
 908  
     public boolean isConnectedMode()
 909  
     {
 910  10
         return connectedWriters != null && !connectedWriters.isEmpty();
 911  
     }
 912  
 
 913  
     private void flushToConnected() throws IOException
 914  
     {
 915  0
         writeTo(connectedWritersWriter, true, true);
 916  0
     }
 917  
 
 918  
     protected boolean isChunkSizeResizeable()
 919  
     {
 920  0
         return (growProcent > 0);
 921  
     }
 922  
 
 923  
     protected void resizeChunkSizeAsProcentageOfTotalSize()
 924  
     {
 925  0
         if (growProcent == 0)
 926  
         {
 927  0
             return;
 928  
         }
 929  
 
 930  0
         if (growProcent == 100)
 931  
         {
 932  0
             chunkSize = Math.min(totalChunkSize, maxChunkSize);
 933  
         }
 934  0
         else if (growProcent == 200)
 935  
         {
 936  0
             chunkSize = Math.min(totalChunkSize << 1, maxChunkSize);
 937  
         }
 938  0
         else if (growProcent > 0)
 939  
         {
 940  0
             chunkSize = Math.max(Math.min((totalChunkSize * growProcent) / 100,
 941  
                     maxChunkSize), firstChunkSize);
 942  
         }
 943  0
     }
 944  
 
 945  
     protected static final void arrayCopy(char[] src, int srcPos, char[] dest,
 946  
             int destPos, int length)
 947  
     {
 948  7
         if (length == 1)
 949  
         {
 950  0
             dest[destPos] = src[srcPos];
 951  
         }
 952  
         else
 953  
         {
 954  7
             System.arraycopy(src, srcPos, dest, destPos, length);
 955  
         }
 956  7
     }
 957  
 
 958  
     /**
 959  
      * This is the java.io.Writer implementation for StreamCharBuffer
 960  
      *
 961  
      * @author Lari Hotari, Sagire Software Oy
 962  
      */
 963  7
     public final class StreamCharBufferWriter extends Writer
 964  
     {
 965  7
         boolean closed = false;
 966  7
         int writerUsedCounter = 0;
 967  7
         boolean increaseCounter = true;
 968  
 
 969  
         @Override
 970  
         public final void write(final char[] b, final int off, final int len)
 971  
                 throws IOException
 972  
         {
 973  0
             if (b == null)
 974  
             {
 975  0
                 throw new NullPointerException();
 976  
             }
 977  
 
 978  0
             if ((off < 0) || (off > b.length) || (len < 0)
 979  
                     || ((off + len) > b.length) || ((off + len) < 0))
 980  
             {
 981  0
                 throw new IndexOutOfBoundsException();
 982  
             }
 983  
 
 984  0
             if (len == 0)
 985  
             {
 986  0
                 return;
 987  
             }
 988  
 
 989  0
             markUsed();
 990  0
             if (shouldWriteDirectly(len))
 991  
             {
 992  0
                 appendCharBufferChunk(true);
 993  0
                 connectedWritersWriter.write(b, off, len);
 994  
             }
 995  
             else
 996  
             {
 997  0
                 int charsLeft = len;
 998  0
                 int currentOffset = off;
 999  0
                 while (charsLeft > 0)
 1000  
                 {
 1001  0
                     int spaceLeft = allocateSpace();
 1002  0
                     int writeChars = Math.min(spaceLeft, charsLeft);
 1003  0
                     allocBuffer.write(b, currentOffset, writeChars);
 1004  0
                     charsLeft -= writeChars;
 1005  0
                     currentOffset += writeChars;
 1006  0
                 }
 1007  
             }
 1008  0
         }
 1009  
 
 1010  
         private final boolean shouldWriteDirectly(final int len)
 1011  
         {
 1012  10
             if (!isConnectedMode())
 1013  
             {
 1014  10
                 return false;
 1015  
             }
 1016  
 
 1017  0
             if (!(writeDirectlyToConnectedMinSize >= 0 && len >= writeDirectlyToConnectedMinSize))
 1018  
             {
 1019  0
                 return false;
 1020  
             }
 1021  
 
 1022  0
             return isNextChunkBigEnough(len);
 1023  
         }
 1024  
 
 1025  
         private final boolean isNextChunkBigEnough(final int len)
 1026  
         {
 1027  0
             return (len > getNewChunkMinSize());
 1028  
         }
 1029  
 
 1030  
         private final int getDirectChunkMinSize()
 1031  
         {
 1032  0
             if (!isConnectedMode())
 1033  
             {
 1034  0
                 return -1;
 1035  
             }
 1036  0
             if (writeDirectlyToConnectedMinSize >= 0)
 1037  
             {
 1038  0
                 return writeDirectlyToConnectedMinSize;
 1039  
             }
 1040  
 
 1041  0
             return getNewChunkMinSize();
 1042  
         }
 1043  
 
 1044  
         private final int getNewChunkMinSize()
 1045  
         {
 1046  0
             if (chunkMinSize <= 0 || allocBuffer.charsUsed() == 0
 1047  
                     || allocBuffer.charsUsed() >= chunkMinSize)
 1048  
             {
 1049  0
                 return 0;
 1050  
             }
 1051  0
             return allocBuffer.spaceLeft();
 1052  
         }
 1053  
 
 1054  
         @Override
 1055  
         public final void write(final String str) throws IOException
 1056  
         {
 1057  10
             write(str, 0, str.length());
 1058  10
         }
 1059  
 
 1060  
         @Override
 1061  
         public final void write(final String str, final int off, final int len)
 1062  
                 throws IOException
 1063  
         {
 1064  10
             if (len == 0)
 1065  
             {
 1066  0
                 return;
 1067  
             }
 1068  10
             markUsed();
 1069  10
             if (shouldWriteDirectly(len))
 1070  
             {
 1071  0
                 appendCharBufferChunk(true);
 1072  0
                 connectedWritersWriter.write(str, off, len);
 1073  
             }
 1074  10
             else if (len >= subStringChunkMinSize && isNextChunkBigEnough(len))
 1075  
             {
 1076  0
                 appendStringChunk(str, off, len);
 1077  
             }
 1078  
             else
 1079  
             {
 1080  10
                 int charsLeft = len;
 1081  10
                 int currentOffset = off;
 1082  20
                 while (charsLeft > 0)
 1083  
                 {
 1084  10
                     int spaceLeft = allocateSpace();
 1085  10
                     int writeChars = Math.min(spaceLeft, charsLeft);
 1086  10
                     allocBuffer.writeString(str, currentOffset, writeChars);
 1087  10
                     charsLeft -= writeChars;
 1088  10
                     currentOffset += writeChars;
 1089  10
                 }
 1090  
             }
 1091  10
         }
 1092  
 
 1093  
         public final void write(StreamCharBuffer subBuffer) throws IOException
 1094  
         {
 1095  0
             markUsed();
 1096  0
             int directChunkMinSize = getDirectChunkMinSize();
 1097  0
             if (directChunkMinSize != -1
 1098  
                     && subBuffer.isSizeLarger(directChunkMinSize))
 1099  
             {
 1100  0
                 appendCharBufferChunk(true);
 1101  0
                 subBuffer.writeToImpl(connectedWritersWriter, false, false);
 1102  
             }
 1103  0
             else if (subBuffer.preferSubChunkWhenWritingToOtherBuffer
 1104  
                     || subBuffer.isSizeLarger(Math.max(subBufferChunkMinSize,
 1105  
                             getNewChunkMinSize())))
 1106  
             {
 1107  0
                 if (subBuffer.preferSubChunkWhenWritingToOtherBuffer)
 1108  
                 {
 1109  0
                     StreamCharBuffer.this.preferSubChunkWhenWritingToOtherBuffer = true;
 1110  
                 }
 1111  0
                 appendStreamCharBufferChunk(subBuffer);
 1112  0
                 subBuffer.addParentBuffer(StreamCharBuffer.this);
 1113  
             }
 1114  
             else
 1115  
             {
 1116  0
                 subBuffer.writeToImpl(this, false, false);
 1117  
             }
 1118  0
         }
 1119  
 
 1120  
         @Override
 1121  
         public final Writer append(final CharSequence csq, final int start,
 1122  
                 final int end) throws IOException
 1123  
         {
 1124  0
             markUsed();
 1125  0
             if (csq == null)
 1126  
             {
 1127  0
                 write("null");
 1128  
             }
 1129  
             else
 1130  
             {
 1131  0
                 if (csq instanceof String || csq instanceof StringBuffer
 1132  
                         || csq instanceof StringBuilder)
 1133  
                 {
 1134  0
                     int len = end - start;
 1135  0
                     int charsLeft = len;
 1136  0
                     int currentOffset = start;
 1137  0
                     while (charsLeft > 0)
 1138  
                     {
 1139  0
                         int spaceLeft = allocateSpace();
 1140  0
                         int writeChars = Math.min(spaceLeft, charsLeft);
 1141  0
                         if (csq instanceof String)
 1142  
                         {
 1143  0
                             allocBuffer.writeString((String) csq,
 1144  
                                     currentOffset, writeChars);
 1145  
                         }
 1146  0
                         else if (csq instanceof StringBuffer)
 1147  
                         {
 1148  0
                             allocBuffer.writeStringBuffer((StringBuffer) csq,
 1149  
                                     currentOffset, writeChars);
 1150  
                         }
 1151  0
                         else if (csq instanceof StringBuilder)
 1152  
                         {
 1153  0
                             allocBuffer.writeStringBuilder((StringBuilder) csq,
 1154  
                                     currentOffset, writeChars);
 1155  
                         }
 1156  0
                         charsLeft -= writeChars;
 1157  0
                         currentOffset += writeChars;
 1158  0
                     }
 1159  0
                 }
 1160  
                 else
 1161  
                 {
 1162  0
                     write(csq.subSequence(start, end).toString());
 1163  
                 }
 1164  
             }
 1165  0
             return this;
 1166  
         }
 1167  
 
 1168  
         @Override
 1169  
         public final Writer append(final CharSequence csq) throws IOException
 1170  
         {
 1171  0
             markUsed();
 1172  0
             if (csq == null)
 1173  
             {
 1174  0
                 write("null");
 1175  
             }
 1176  
             else
 1177  
             {
 1178  0
                 append(csq, 0, csq.length());
 1179  
 
 1180  
             }
 1181  0
             return this;
 1182  
         }
 1183  
 
 1184  
         @Override
 1185  
         public void close() throws IOException
 1186  
         {
 1187  0
             closed = true;
 1188  0
             flush();
 1189  0
         }
 1190  
 
 1191  
         public boolean isClosed()
 1192  
         {
 1193  0
             return closed;
 1194  
         }
 1195  
 
 1196  
         public boolean isUsed()
 1197  
         {
 1198  0
             return writerUsedCounter > 0;
 1199  
         }
 1200  
 
 1201  
         public final void markUsed()
 1202  
         {
 1203  16
             if (increaseCounter)
 1204  
             {
 1205  7
                 writerUsedCounter++;
 1206  7
                 if (!hasReaders)
 1207  
                 {
 1208  7
                     increaseCounter = false;
 1209  
                 }
 1210  
             }
 1211  16
         }
 1212  
 
 1213  
         public int resetUsed()
 1214  
         {
 1215  0
             int prevUsed = writerUsedCounter;
 1216  0
             writerUsedCounter = 0;
 1217  0
             increaseCounter = true;
 1218  0
             return prevUsed;
 1219  
         }
 1220  
 
 1221  
         @Override
 1222  
         public void write(final int b) throws IOException
 1223  
         {
 1224  6
             markUsed();
 1225  6
             allocateSpace();
 1226  6
             allocBuffer.write((char) b);
 1227  6
         }
 1228  
 
 1229  
         @Override
 1230  
         public void flush() throws IOException
 1231  
         {
 1232  0
             if (isConnectedMode())
 1233  
             {
 1234  0
                 flushToConnected();
 1235  
             }
 1236  0
             notifyBufferChange();
 1237  0
         }
 1238  
 
 1239  
         public final StreamCharBuffer getBuffer()
 1240  
         {
 1241  0
             return StreamCharBuffer.this;
 1242  
         }
 1243  
     }
 1244  
 
 1245  
     /**
 1246  
      * This is the java.io.Reader implementation for StreamCharBuffer
 1247  
      *
 1248  
      * @author Lari Hotari, Sagire Software Oy
 1249  
      */
 1250  
 
 1251  
     final public class StreamCharBufferReader extends Reader
 1252  
     {
 1253  0
         boolean eofException = false;
 1254  0
         int eofReachedCounter = 0;
 1255  
         ChunkReader chunkReader;
 1256  
         ChunkReader lastChunkReader;
 1257  
         boolean removeAfterReading;
 1258  
 
 1259  
         public StreamCharBufferReader(boolean removeAfterReading)
 1260  0
         {
 1261  0
             this.removeAfterReading = removeAfterReading;
 1262  0
         }
 1263  
 
 1264  
         private int prepareRead(int len)
 1265  
         {
 1266  0
             if (hasReaders && eofReachedCounter != 0)
 1267  
             {
 1268  0
                 if (eofReachedCounter != writer.writerUsedCounter)
 1269  
                 {
 1270  0
                     eofReachedCounter = 0;
 1271  0
                     eofException = false;
 1272  0
                     repositionChunkReader();
 1273  
                 }
 1274  
             }
 1275  0
             if (chunkReader == null && eofReachedCounter == 0)
 1276  
             {
 1277  0
                 if (firstChunk != null)
 1278  
                 {
 1279  0
                     chunkReader = firstChunk.getChunkReader(removeAfterReading);
 1280  0
                     if (removeAfterReading)
 1281  
                     {
 1282  0
                         firstChunk.subtractFromTotalCount();
 1283  
                     }
 1284  
                 }
 1285  
                 else
 1286  
                 {
 1287  0
                     chunkReader = new AllocatedBufferReader(allocBuffer,
 1288  
                             removeAfterReading);
 1289  
                 }
 1290  
             }
 1291  0
             int available = 0;
 1292  0
             if (chunkReader != null)
 1293  
             {
 1294  0
                 available = chunkReader.getReadLenLimit(len);
 1295  0
                 while (available == 0 && chunkReader != null)
 1296  
                 {
 1297  0
                     chunkReader = chunkReader.next();
 1298  0
                     if (chunkReader != null)
 1299  
                     {
 1300  0
                         available = chunkReader.getReadLenLimit(len);
 1301  
                     }
 1302  
                     else
 1303  
                     {
 1304  0
                         available = 0;
 1305  
                     }
 1306  
                 }
 1307  
             }
 1308  0
             if (chunkReader == null)
 1309  
             {
 1310  0
                 if (hasReaders)
 1311  
                 {
 1312  0
                     eofReachedCounter = writer.writerUsedCounter;
 1313  
                 }
 1314  
                 else
 1315  
                 {
 1316  0
                     eofReachedCounter = 1;
 1317  
                 }
 1318  
             }
 1319  0
             else if (hasReaders)
 1320  
             {
 1321  0
                 lastChunkReader = chunkReader;
 1322  
             }
 1323  0
             return available;
 1324  
         }
 1325  
 
 1326  
         /* adds support for reading and writing simultaneously in the same thread */
 1327  
         private void repositionChunkReader()
 1328  
         {
 1329  0
             if (lastChunkReader instanceof AllocatedBufferReader)
 1330  
             {
 1331  0
                 if (lastChunkReader.isValid())
 1332  
                 {
 1333  0
                     chunkReader = lastChunkReader;
 1334  
                 }
 1335  
                 else
 1336  
                 {
 1337  0
                     AllocatedBufferReader allocBufferReader = (AllocatedBufferReader) lastChunkReader;
 1338  
                     // find out what is the CharBufferChunk that was read by the AllocatedBufferReader already
 1339  0
                     int currentPosition = allocBufferReader.position;
 1340  0
                     AbstractChunk chunk = lastChunk;
 1341  0
                     while (chunk != null
 1342  
                             && chunk.writerUsedCounter >= lastChunkReader
 1343  
                                     .getWriterUsedCounter())
 1344  
                     {
 1345  0
                         if (chunk instanceof CharBufferChunk)
 1346  
                         {
 1347  0
                             CharBufferChunk charBufChunk = (CharBufferChunk) chunk;
 1348  0
                             if (charBufChunk.allocatedBufferId == allocBufferReader.parent.id)
 1349  
                             {
 1350  0
                                 if (currentPosition >= charBufChunk.offset
 1351  
                                         && currentPosition <= charBufChunk.lastposition)
 1352  
                                 {
 1353  0
                                     CharBufferChunkReader charBufChunkReader = (CharBufferChunkReader) charBufChunk
 1354  
                                             .getChunkReader(removeAfterReading);
 1355  0
                                     int oldpointer = charBufChunkReader.pointer;
 1356  
                                     // skip the already chars
 1357  0
                                     charBufChunkReader.pointer = currentPosition;
 1358  0
                                     if (removeAfterReading)
 1359  
                                     {
 1360  0
                                         int diff = charBufChunkReader.pointer
 1361  
                                                 - oldpointer;
 1362  0
                                         totalCharsInList -= diff;
 1363  0
                                         charBufChunk.subtractFromTotalCount();
 1364  
                                     }
 1365  0
                                     chunkReader = charBufChunkReader;
 1366  0
                                     break;
 1367  
                                 }
 1368  
                             }
 1369  
                         }
 1370  0
                         chunk = chunk.prev;
 1371  
                     }
 1372  
                 }
 1373  
             }
 1374  0
         }
 1375  
 
 1376  
         @Override
 1377  
         public boolean ready() throws IOException
 1378  
         {
 1379  0
             return true;
 1380  
         }
 1381  
 
 1382  
         @Override
 1383  
         public final int read(final char[] b, final int off, final int len)
 1384  
                 throws IOException
 1385  
         {
 1386  0
             return readImpl(b, off, len);
 1387  
         }
 1388  
 
 1389  
         final int readImpl(final char[] b, final int off, final int len)
 1390  
                 throws IOException
 1391  
         {
 1392  0
             if (b == null)
 1393  
             {
 1394  0
                 throw new NullPointerException();
 1395  
             }
 1396  
 
 1397  0
             if ((off < 0) || (off > b.length) || (len < 0)
 1398  
                     || ((off + len) > b.length) || ((off + len) < 0))
 1399  
             {
 1400  0
                 throw new IndexOutOfBoundsException();
 1401  
             }
 1402  
 
 1403  0
             if (len == 0)
 1404  
             {
 1405  0
                 return 0;
 1406  
             }
 1407  
 
 1408  0
             int charsLeft = len;
 1409  0
             int currentOffset = off;
 1410  0
             int readChars = prepareRead(charsLeft);
 1411  0
             if (eofException)
 1412  
             {
 1413  0
                 throw new EOFException();
 1414  
             }
 1415  
 
 1416  0
             int totalCharsRead = 0;
 1417  0
             while (charsLeft > 0 && readChars > 0)
 1418  
             {
 1419  0
                 chunkReader.read(b, currentOffset, readChars);
 1420  0
                 charsLeft -= readChars;
 1421  0
                 currentOffset += readChars;
 1422  0
                 totalCharsRead += readChars;
 1423  0
                 if (charsLeft > 0)
 1424  
                 {
 1425  0
                     readChars = prepareRead(charsLeft);
 1426  
                 }
 1427  
             }
 1428  
 
 1429  0
             if (totalCharsRead > 0)
 1430  
             {
 1431  0
                 return totalCharsRead;
 1432  
             }
 1433  
 
 1434  0
             eofException = true;
 1435  0
             return -1;
 1436  
         }
 1437  
 
 1438  
         @Override
 1439  
         public void close() throws IOException
 1440  
         {
 1441  
             // do nothing
 1442  0
         }
 1443  
 
 1444  
         public final StreamCharBuffer getBuffer()
 1445  
         {
 1446  0
             return StreamCharBuffer.this;
 1447  
         }
 1448  
 
 1449  
         public int getReadLenLimit(int askedAmount)
 1450  
         {
 1451  0
             return prepareRead(askedAmount);
 1452  
         }
 1453  
     }
 1454  
 
 1455  
     abstract class AbstractChunk
 1456  
     {
 1457  
         AbstractChunk next;
 1458  
         AbstractChunk prev;
 1459  
         int writerUsedCounter;
 1460  
 
 1461  
         public AbstractChunk()
 1462  7
         {
 1463  7
             if (hasReaders)
 1464  
             {
 1465  0
                 writerUsedCounter = writer.writerUsedCounter;
 1466  
             }
 1467  
             else
 1468  
             {
 1469  7
                 writerUsedCounter = 1;
 1470  
             }
 1471  7
         }
 1472  
 
 1473  
         public abstract void writeTo(Writer target) throws IOException;
 1474  
 
 1475  
         public abstract ChunkReader getChunkReader(boolean removeAfterReading);
 1476  
 
 1477  
         public abstract int size();
 1478  
 
 1479  
         public int getWriterUsedCounter()
 1480  
         {
 1481  0
             return writerUsedCounter;
 1482  
         }
 1483  
 
 1484  
         public void subtractFromTotalCount()
 1485  
         {
 1486  0
             totalCharsInList -= size();
 1487  0
         }
 1488  
     }
 1489  
 
 1490  
     // keep read state in this class
 1491  0
     static abstract class ChunkReader
 1492  
     {
 1493  
         public abstract int read(char[] ch, int off, int len)
 1494  
                 throws IOException;
 1495  
 
 1496  
         public abstract int getReadLenLimit(int askedAmount);
 1497  
 
 1498  
         public abstract ChunkReader next();
 1499  
 
 1500  
         public abstract int getWriterUsedCounter();
 1501  
 
 1502  
         public abstract boolean isValid();
 1503  
     }
 1504  
 
 1505  0
     final class AllocatedBuffer
 1506  
     {
 1507  14
         private int id = allocatedBufferIdSequence++;
 1508  
         private int size;
 1509  
         private char[] buffer;
 1510  14
         private int used = 0;
 1511  14
         private int chunkStart = 0;
 1512  
 
 1513  
         public AllocatedBuffer(int size)
 1514  14
         {
 1515  14
             this.size = size;
 1516  14
             buffer = new char[size];
 1517  14
         }
 1518  
 
 1519  
         public int charsUsed()
 1520  
         {
 1521  10
             return used - chunkStart;
 1522  
         }
 1523  
 
 1524  
         public void writeTo(Writer target) throws IOException
 1525  
         {
 1526  7
             if (used - chunkStart > 0)
 1527  
             {
 1528  7
                 target.write(buffer, chunkStart, used - chunkStart);
 1529  
             }
 1530  7
         }
 1531  
 
 1532  
         public void reuseBuffer()
 1533  
         {
 1534  0
             used = 0;
 1535  0
             chunkStart = 0;
 1536  0
         }
 1537  
 
 1538  
         public int chunkSize()
 1539  
         {
 1540  0
             return buffer.length;
 1541  
         }
 1542  
 
 1543  
         public int spaceLeft()
 1544  
         {
 1545  16
             return size - used;
 1546  
         }
 1547  
 
 1548  
         public boolean write(final char ch)
 1549  
         {
 1550  6
             if (used < size)
 1551  
             {
 1552  6
                 buffer[used++] = ch;
 1553  6
                 return true;
 1554  
             }
 1555  
 
 1556  0
             return false;
 1557  
         }
 1558  
 
 1559  
         public final void write(final char[] ch, final int off, final int len)
 1560  
         {
 1561  0
             arrayCopy(ch, off, buffer, used, len);
 1562  0
             used += len;
 1563  0
         }
 1564  
 
 1565  
         public final void writeString(final String str, final int off,
 1566  
                 final int len)
 1567  
         {
 1568  10
             str.getChars(off, off + len, buffer, used);
 1569  10
             used += len;
 1570  10
         }
 1571  
 
 1572  
         public final void writeStringBuilder(final StringBuilder stringBuilder,
 1573  
                 final int off, final int len)
 1574  
         {
 1575  0
             stringBuilder.getChars(off, off + len, buffer, used);
 1576  0
             used += len;
 1577  0
         }
 1578  
 
 1579  
         public final void writeStringBuffer(final StringBuffer stringBuffer,
 1580  
                 final int off, final int len)
 1581  
         {
 1582  0
             stringBuffer.getChars(off, off + len, buffer, used);
 1583  0
             used += len;
 1584  0
         }
 1585  
 
 1586  
         /**
 1587  
          * Creates a new chunk from the content written to the buffer 
 1588  
          * (used before adding StringChunk or StreamCharBufferChunk).
 1589  
          *
 1590  
          * @return the chunk
 1591  
          */
 1592  
         public CharBufferChunk createChunk()
 1593  
         {
 1594  0
             CharBufferChunk chunk = new CharBufferChunk(id, buffer, chunkStart,
 1595  
                     used - chunkStart);
 1596  0
             chunkStart = used;
 1597  0
             return chunk;
 1598  
         }
 1599  
 
 1600  
         public boolean hasChunk()
 1601  
         {
 1602  0
             return (used > chunkStart);
 1603  
         }
 1604  
 
 1605  
     }
 1606  
 
 1607  
     /**
 1608  
      * The data in the buffer is stored in a linked list of StreamCharBufferChunks.
 1609  
      *
 1610  
      * This class contains data & read/write state for the "chunk level".
 1611  
      * It contains methods for reading & writing to the chunk level.
 1612  
      *
 1613  
      * Underneath the chunk is one more level, the StringChunkGroup + StringChunk.
 1614  
      * StringChunk makes it possible to directly store the java.lang.String objects.
 1615  
      *
 1616  
      * @author Lari Hotari
 1617  
      *
 1618  
      */
 1619  
     final class CharBufferChunk extends AbstractChunk
 1620  
     {
 1621  
         int allocatedBufferId;
 1622  
         char[] buffer;
 1623  
         int offset;
 1624  
         int lastposition;
 1625  
         int length;
 1626  
 
 1627  
         public CharBufferChunk(int allocatedBufferId, char[] buffer,
 1628  
                 int offset, int len)
 1629  0
         {
 1630  0
             super();
 1631  0
             this.allocatedBufferId = allocatedBufferId;
 1632  0
             this.buffer = buffer;
 1633  0
             this.offset = offset;
 1634  0
             this.lastposition = offset + len;
 1635  0
             this.length = len;
 1636  0
         }
 1637  
 
 1638  
         @Override
 1639  
         public void writeTo(final Writer target) throws IOException
 1640  
         {
 1641  0
             target.write(buffer, offset, length);
 1642  0
         }
 1643  
 
 1644  
         @Override
 1645  
         public ChunkReader getChunkReader(boolean removeAfterReading)
 1646  
         {
 1647  0
             return new CharBufferChunkReader(this, removeAfterReading);
 1648  
         }
 1649  
 
 1650  
         @Override
 1651  
         public int size()
 1652  
         {
 1653  0
             return length;
 1654  
         }
 1655  
 
 1656  
         public boolean isSingleBuffer()
 1657  
         {
 1658  0
             return offset == 0 && length == buffer.length;
 1659  
         }
 1660  
     }
 1661  
 
 1662  
     abstract class AbstractChunkReader extends ChunkReader
 1663  
     {
 1664  
         private AbstractChunk parentChunk;
 1665  
         private boolean removeAfterReading;
 1666  
 
 1667  
         public AbstractChunkReader(AbstractChunk parentChunk,
 1668  
                 boolean removeAfterReading)
 1669  0
         {
 1670  0
             this.parentChunk = parentChunk;
 1671  0
             this.removeAfterReading = removeAfterReading;
 1672  0
         }
 1673  
 
 1674  
         @Override
 1675  
         public boolean isValid()
 1676  
         {
 1677  0
             return true;
 1678  
         }
 1679  
 
 1680  
         @Override
 1681  
         public ChunkReader next()
 1682  
         {
 1683  0
             if (removeAfterReading)
 1684  
             {
 1685  0
                 if (firstChunk == parentChunk)
 1686  
                 {
 1687  0
                     firstChunk = null;
 1688  
                 }
 1689  0
                 if (lastChunk == parentChunk)
 1690  
                 {
 1691  0
                     lastChunk = null;
 1692  
                 }
 1693  
             }
 1694  0
             AbstractChunk nextChunk = parentChunk.next;
 1695  0
             if (nextChunk != null)
 1696  
             {
 1697  0
                 if (removeAfterReading)
 1698  
                 {
 1699  0
                     if (firstChunk == null)
 1700  
                     {
 1701  0
                         firstChunk = nextChunk;
 1702  
                     }
 1703  0
                     if (lastChunk == null)
 1704  
                     {
 1705  0
                         lastChunk = nextChunk;
 1706  
                     }
 1707  0
                     nextChunk.prev = null;
 1708  0
                     nextChunk.subtractFromTotalCount();
 1709  
                 }
 1710  0
                 return nextChunk.getChunkReader(removeAfterReading);
 1711  
             }
 1712  
 
 1713  0
             return new AllocatedBufferReader(allocBuffer, removeAfterReading);
 1714  
         }
 1715  
 
 1716  
         @Override
 1717  
         public int getWriterUsedCounter()
 1718  
         {
 1719  0
             return parentChunk.getWriterUsedCounter();
 1720  
         }
 1721  
     }
 1722  
 
 1723  
     final class CharBufferChunkReader extends AbstractChunkReader
 1724  
     {
 1725  
         CharBufferChunk parent;
 1726  
         int pointer;
 1727  
 
 1728  
         public CharBufferChunkReader(CharBufferChunk parent,
 1729  
                 boolean removeAfterReading)
 1730  0
         {
 1731  0
             super(parent, removeAfterReading);
 1732  0
             this.parent = parent;
 1733  0
             this.pointer = parent.offset;
 1734  0
         }
 1735  
 
 1736  
         @Override
 1737  
         public int read(final char[] ch, final int off, final int len)
 1738  
                 throws IOException
 1739  
         {
 1740  0
             arrayCopy(parent.buffer, pointer, ch, off, len);
 1741  0
             pointer += len;
 1742  0
             return len;
 1743  
         }
 1744  
 
 1745  
         @Override
 1746  
         public int getReadLenLimit(int askedAmount)
 1747  
         {
 1748  0
             return Math.min(parent.lastposition - pointer, askedAmount);
 1749  
         }
 1750  
     }
 1751  
 
 1752  
     /**
 1753  
      * StringChunk is a wrapper for java.lang.String.
 1754  
      *
 1755  
      * It also keeps state of the read offset and the number of unread characters.
 1756  
      *
 1757  
      * There's methods that StringChunkGroup uses for reading data.
 1758  
      *
 1759  
      * @author Lari Hotari
 1760  
      *
 1761  
      */
 1762  
     final class StringChunk extends AbstractChunk
 1763  
     {
 1764  
         String str;
 1765  
         int offset;
 1766  
         int lastposition;
 1767  
         int length;
 1768  
 
 1769  
         public StringChunk(String str, int offset, int length)
 1770  7
         {
 1771  7
             this.str = str;
 1772  7
             this.offset = offset;
 1773  7
             this.length = length;
 1774  7
             this.lastposition = offset + length;
 1775  7
         }
 1776  
 
 1777  
         @Override
 1778  
         public ChunkReader getChunkReader(boolean removeAfterReading)
 1779  
         {
 1780  0
             return new StringChunkReader(this, removeAfterReading);
 1781  
         }
 1782  
 
 1783  
         @Override
 1784  
         public void writeTo(Writer target) throws IOException
 1785  
         {
 1786  0
             target.write(str, offset, length);
 1787  0
         }
 1788  
 
 1789  
         @Override
 1790  
         public int size()
 1791  
         {
 1792  7
             return length;
 1793  
         }
 1794  
 
 1795  
         public boolean isSingleBuffer()
 1796  
         {
 1797  3
             return offset == 0 && length == str.length();
 1798  
         }
 1799  
     }
 1800  
 
 1801  
     final class StringChunkReader extends AbstractChunkReader
 1802  
     {
 1803  
         StringChunk parent;
 1804  
         int position;
 1805  
 
 1806  
         public StringChunkReader(StringChunk parent, boolean removeAfterReading)
 1807  0
         {
 1808  0
             super(parent, removeAfterReading);
 1809  0
             this.parent = parent;
 1810  0
             this.position = parent.offset;
 1811  0
         }
 1812  
 
 1813  
         @Override
 1814  
         public int read(final char[] ch, final int off, final int len)
 1815  
         {
 1816  0
             parent.str.getChars(position, (position + len), ch, off);
 1817  0
             position += len;
 1818  0
             return len;
 1819  
         }
 1820  
 
 1821  
         @Override
 1822  
         public int getReadLenLimit(int askedAmount)
 1823  
         {
 1824  0
             return Math.min(parent.lastposition - position, askedAmount);
 1825  
         }
 1826  
     }
 1827  
 
 1828  
     final class StreamCharBufferSubChunk extends AbstractChunk
 1829  
     {
 1830  
         StreamCharBuffer streamCharBuffer;
 1831  
         int cachedSize;
 1832  
 
 1833  
         public StreamCharBufferSubChunk(StreamCharBuffer streamCharBuffer)
 1834  0
         {
 1835  0
             this.streamCharBuffer = streamCharBuffer;
 1836  0
             if (totalCharsInDynamicChunks != -1)
 1837  
             {
 1838  0
                 cachedSize = streamCharBuffer.size();
 1839  0
                 totalCharsInDynamicChunks += cachedSize;
 1840  
             }
 1841  
             else
 1842  
             {
 1843  0
                 cachedSize = -1;
 1844  
             }
 1845  0
         }
 1846  
 
 1847  
         @Override
 1848  
         public void writeTo(Writer target) throws IOException
 1849  
         {
 1850  0
             streamCharBuffer.writeTo(target);
 1851  0
         }
 1852  
 
 1853  
         @Override
 1854  
         public ChunkReader getChunkReader(boolean removeAfterReading)
 1855  
         {
 1856  0
             return new StreamCharBufferSubChunkReader(this, removeAfterReading);
 1857  
         }
 1858  
 
 1859  
         @Override
 1860  
         public int size()
 1861  
         {
 1862  0
             if (cachedSize == -1)
 1863  
             {
 1864  0
                 cachedSize = streamCharBuffer.size();
 1865  
             }
 1866  0
             return cachedSize;
 1867  
         }
 1868  
 
 1869  
         public boolean hasCachedSize()
 1870  
         {
 1871  0
             return (cachedSize != -1);
 1872  
         }
 1873  
 
 1874  
         public StreamCharBuffer getSubBuffer()
 1875  
         {
 1876  0
             return this.streamCharBuffer;
 1877  
         }
 1878  
 
 1879  
         public boolean resetSize()
 1880  
         {
 1881  0
             if (cachedSize != -1)
 1882  
             {
 1883  0
                 cachedSize = -1;
 1884  0
                 return true;
 1885  
             }
 1886  0
             return false;
 1887  
         }
 1888  
 
 1889  
         @Override
 1890  
         public void subtractFromTotalCount()
 1891  
         {
 1892  0
             if (totalCharsInDynamicChunks != -1)
 1893  
             {
 1894  0
                 totalCharsInDynamicChunks -= size();
 1895  
             }
 1896  0
             dynamicChunkMap.remove(streamCharBuffer.bufferKey);
 1897  0
         }
 1898  
     }
 1899  
 
 1900  
     final class StreamCharBufferSubChunkReader extends AbstractChunkReader
 1901  
     {
 1902  
         StreamCharBufferSubChunk parent;
 1903  
         private StreamCharBufferReader reader;
 1904  
 
 1905  
         public StreamCharBufferSubChunkReader(StreamCharBufferSubChunk parent,
 1906  
                 boolean removeAfterReading)
 1907  0
         {
 1908  0
             super(parent, removeAfterReading);
 1909  0
             this.parent = parent;
 1910  0
             reader = (StreamCharBufferReader) parent.streamCharBuffer
 1911  
                     .getReader();
 1912  0
         }
 1913  
 
 1914  
         @Override
 1915  
         public int getReadLenLimit(int askedAmount)
 1916  
         {
 1917  0
             return reader.getReadLenLimit(askedAmount);
 1918  
         }
 1919  
 
 1920  
         @Override
 1921  
         public int read(char[] ch, int off, int len) throws IOException
 1922  
         {
 1923  0
             return reader.read(ch, off, len);
 1924  
         }
 1925  
     }
 1926  
 
 1927  
     final class AllocatedBufferReader extends ChunkReader
 1928  
     {
 1929  
         AllocatedBuffer parent;
 1930  
         int position;
 1931  
         int writerUsedCounter;
 1932  
         boolean removeAfterReading;
 1933  
 
 1934  
         public AllocatedBufferReader(AllocatedBuffer parent,
 1935  
                 boolean removeAfterReading)
 1936  0
         {
 1937  0
             this.parent = parent;
 1938  0
             this.position = parent.chunkStart;
 1939  0
             if (hasReaders)
 1940  
             {
 1941  0
                 writerUsedCounter = writer.writerUsedCounter;
 1942  
             }
 1943  
             else
 1944  
             {
 1945  0
                 writerUsedCounter = 1;
 1946  
             }
 1947  0
             this.removeAfterReading = removeAfterReading;
 1948  0
         }
 1949  
 
 1950  
         @Override
 1951  
         public int getReadLenLimit(int askedAmount)
 1952  
         {
 1953  0
             return Math.min(parent.used - position, askedAmount);
 1954  
         }
 1955  
 
 1956  
         @Override
 1957  
         public int read(char[] ch, int off, int len) throws IOException
 1958  
         {
 1959  0
             arrayCopy(parent.buffer, position, ch, off, len);
 1960  0
             position += len;
 1961  0
             if (removeAfterReading)
 1962  
             {
 1963  0
                 parent.chunkStart = position;
 1964  
             }
 1965  0
             return len;
 1966  
         }
 1967  
 
 1968  
         @Override
 1969  
         public ChunkReader next()
 1970  
         {
 1971  0
             return null;
 1972  
         }
 1973  
 
 1974  
         @Override
 1975  
         public int getWriterUsedCounter()
 1976  
         {
 1977  0
             return writerUsedCounter;
 1978  
         }
 1979  
 
 1980  
         @Override
 1981  
         public boolean isValid()
 1982  
         {
 1983  0
             return (allocBuffer == parent && (lastChunk == null || lastChunk.writerUsedCounter < writerUsedCounter));
 1984  
         }
 1985  
     }
 1986  
 
 1987  
     /**
 1988  
      * Simplified version of a CharArrayWriter used internally in readAsCharArray method.
 1989  
      *
 1990  
      * Doesn't do any bound checks since size shouldn't change during writing in readAsCharArray.
 1991  
      */
 1992  
     private static final class FixedCharArrayWriter extends Writer
 1993  
     {
 1994  
         char buf[];
 1995  7
         int count = 0;
 1996  
 
 1997  
         public FixedCharArrayWriter(int fixedSize)
 1998  7
         {
 1999  7
             buf = new char[fixedSize];
 2000  7
         }
 2001  
 
 2002  
         @Override
 2003  
         public void write(char[] cbuf, int off, int len) throws IOException
 2004  
         {
 2005  7
             arrayCopy(cbuf, off, buf, count, len);
 2006  7
             count += len;
 2007  7
         }
 2008  
 
 2009  
         @Override
 2010  
         public void write(char[] cbuf) throws IOException
 2011  
         {
 2012  0
             write(cbuf, 0, cbuf.length);
 2013  0
         }
 2014  
 
 2015  
         @Override
 2016  
         public void write(String str, int off, int len) throws IOException
 2017  
         {
 2018  0
             str.getChars(off, off + len, buf, count);
 2019  0
             count += len;
 2020  0
         }
 2021  
 
 2022  
         @Override
 2023  
         public void write(String str) throws IOException
 2024  
         {
 2025  0
             write(str, 0, str.length());
 2026  0
         }
 2027  
 
 2028  
         @Override
 2029  
         public void close() throws IOException
 2030  
         {
 2031  
             // do nothing
 2032  0
         }
 2033  
 
 2034  
         @Override
 2035  
         public void flush() throws IOException
 2036  
         {
 2037  
             // do nothing
 2038  0
         }
 2039  
 
 2040  
         public char[] getCharArray()
 2041  
         {
 2042  7
             return buf;
 2043  
         }
 2044  
     }
 2045  
 
 2046  
     /**
 2047  
      * Interface for a Writer that gets initialized if it is used
 2048  
      * Can be used for passing in to "connectTo" method of StreamCharBuffer
 2049  
      *
 2050  
      * @author Lari Hotari
 2051  
      *
 2052  
      */
 2053  
     public static interface LazyInitializingWriter
 2054  
     {
 2055  
         public Writer getWriter() throws IOException;
 2056  
     }
 2057  
 
 2058  
     /**
 2059  
      * Simple holder class for the connected writer
 2060  
      *
 2061  
      * @author Lari Hotari
 2062  
      *
 2063  
      */
 2064  
     static final class ConnectedWriter
 2065  
     {
 2066  
         Writer writer;
 2067  
         LazyInitializingWriter lazyInitializingWriter;
 2068  
         final boolean autoFlush;
 2069  
 
 2070  
         ConnectedWriter(final Writer writer, final boolean autoFlush)
 2071  0
         {
 2072  0
             this.writer = writer;
 2073  0
             this.autoFlush = autoFlush;
 2074  0
         }
 2075  
 
 2076  
         ConnectedWriter(final LazyInitializingWriter lazyInitializingWriter,
 2077  
                 final boolean autoFlush)
 2078  0
         {
 2079  0
             this.lazyInitializingWriter = lazyInitializingWriter;
 2080  0
             this.autoFlush = autoFlush;
 2081  0
         }
 2082  
 
 2083  
         Writer getWriter() throws IOException
 2084  
         {
 2085  0
             if (writer == null && lazyInitializingWriter != null)
 2086  
             {
 2087  0
                 writer = lazyInitializingWriter.getWriter();
 2088  
             }
 2089  0
             return writer;
 2090  
         }
 2091  
 
 2092  
         public void flush() throws IOException
 2093  
         {
 2094  0
             if (writer != null && isAutoFlush())
 2095  
             {
 2096  0
                 writer.flush();
 2097  
             }
 2098  0
         }
 2099  
 
 2100  
         public boolean isAutoFlush()
 2101  
         {
 2102  0
             return autoFlush;
 2103  
         }
 2104  
     }
 2105  
 
 2106  0
     static final class SingleOutputWriter extends Writer
 2107  
     {
 2108  
         private ConnectedWriter writer;
 2109  
 
 2110  
         public SingleOutputWriter(ConnectedWriter writer)
 2111  0
         {
 2112  0
             this.writer = writer;
 2113  0
         }
 2114  
 
 2115  
         @Override
 2116  
         public void close() throws IOException
 2117  
         {
 2118  
             // do nothing
 2119  0
         }
 2120  
 
 2121  
         @Override
 2122  
         public void flush() throws IOException
 2123  
         {
 2124  0
             writer.flush();
 2125  0
         }
 2126  
 
 2127  
         @Override
 2128  
         public void write(final char[] cbuf, final int off, final int len)
 2129  
                 throws IOException
 2130  
         {
 2131  0
             writer.getWriter().write(cbuf, off, len);
 2132  0
         }
 2133  
 
 2134  
         @Override
 2135  
         public Writer append(final CharSequence csq, final int start,
 2136  
                 final int end) throws IOException
 2137  
         {
 2138  0
             writer.getWriter().append(csq, start, end);
 2139  0
             return this;
 2140  
         }
 2141  
 
 2142  
         @Override
 2143  
         public void write(String str, int off, int len) throws IOException
 2144  
         {
 2145  0
             StringCharArrayAccessor.writeStringAsCharArray(writer.getWriter(),
 2146  
                     str, off, len);
 2147  0
         }
 2148  
     }
 2149  
 
 2150  
     /**
 2151  
      * delegates to several writers, used in "connectTo" mode.
 2152  
      *
 2153  
      */
 2154  0
     static final class MultiOutputWriter extends Writer
 2155  
     {
 2156  
         final List<ConnectedWriter> writers;
 2157  
 
 2158  
         public MultiOutputWriter(final List<ConnectedWriter> writers)
 2159  0
         {
 2160  0
             this.writers = writers;
 2161  0
         }
 2162  
 
 2163  
         @Override
 2164  
         public void close() throws IOException
 2165  
         {
 2166  
             // do nothing
 2167  0
         }
 2168  
 
 2169  
         @Override
 2170  
         public void flush() throws IOException
 2171  
         {
 2172  0
             for (ConnectedWriter writer : writers)
 2173  
             {
 2174  0
                 writer.flush();
 2175  0
             }
 2176  0
         }
 2177  
 
 2178  
         @Override
 2179  
         public void write(final char[] cbuf, final int off, final int len)
 2180  
                 throws IOException
 2181  
         {
 2182  0
             for (ConnectedWriter writer : writers)
 2183  
             {
 2184  0
                 writer.getWriter().write(cbuf, off, len);
 2185  0
             }
 2186  0
         }
 2187  
 
 2188  
         @Override
 2189  
         public Writer append(final CharSequence csq, final int start,
 2190  
                 final int end) throws IOException
 2191  
         {
 2192  0
             for (ConnectedWriter writer : writers)
 2193  
             {
 2194  0
                 writer.getWriter().append(csq, start, end);
 2195  0
             }
 2196  0
             return this;
 2197  
         }
 2198  
 
 2199  
         @Override
 2200  
         public void write(String str, int off, int len) throws IOException
 2201  
         {
 2202  0
             for (ConnectedWriter writer : writers)
 2203  
             {
 2204  0
                 StringCharArrayAccessor.writeStringAsCharArray(
 2205  
                         writer.getWriter(), str, off, len);
 2206  0
             }
 2207  0
         }
 2208  
     }
 2209  
 
 2210  
     /* Compatibility methods so that StreamCharBuffer will behave more like java.lang.String in groovy code */
 2211  
 
 2212  
     public char charAt(int index)
 2213  
     {
 2214  0
         return toString().charAt(index);
 2215  
     }
 2216  
 
 2217  
     public int length()
 2218  
     {
 2219  0
         return size();
 2220  
     }
 2221  
 
 2222  
     public CharSequence subSequence(int start, int end)
 2223  
     {
 2224  0
         return toString().subSequence(start, end);
 2225  
     }
 2226  
 
 2227  
     public boolean asBoolean()
 2228  
     {
 2229  0
         return isNotEmpty();
 2230  
     }
 2231  
 
 2232  
     /* methods for notifying child (sub) StreamCharBuffer changes to the parent StreamCharBuffer */
 2233  
 
 2234  
     void addParentBuffer(StreamCharBuffer parent)
 2235  
     {
 2236  0
         if (parentBuffers == null)
 2237  
         {
 2238  0
             parentBuffers = new HashSet<SoftReference<StreamCharBufferKey>>();
 2239  
         }
 2240  0
         parentBuffers.add(new SoftReference<StreamCharBufferKey>(
 2241  
                 parent.bufferKey));
 2242  0
     }
 2243  
 
 2244  
     boolean bufferChanged(StreamCharBuffer buffer)
 2245  
     {
 2246  0
         StreamCharBufferSubChunk subChunk = dynamicChunkMap
 2247  
                 .get(buffer.bufferKey);
 2248  0
         if (subChunk == null)
 2249  
         {
 2250  
             // buffer isn't a subchunk in this buffer any more
 2251  0
             return false;
 2252  
         }
 2253  
         // reset cached size;
 2254  0
         if (subChunk.resetSize())
 2255  
         {
 2256  0
             totalCharsInDynamicChunks = -1;
 2257  0
             sizeAtLeast = -1;
 2258  
             // notify parents too
 2259  0
             notifyBufferChange();
 2260  
         }
 2261  0
         return true;
 2262  
     }
 2263  
 
 2264  
     void notifyBufferChange()
 2265  
     {
 2266  0
         if (parentBuffers == null)
 2267  
         {
 2268  0
             return;
 2269  
         }
 2270  
 
 2271  0
         for (Iterator<SoftReference<StreamCharBufferKey>> i = parentBuffers
 2272  0
                 .iterator(); i.hasNext();)
 2273  
         {
 2274  0
             SoftReference<StreamCharBufferKey> ref = i.next();
 2275  0
             final StreamCharBuffer.StreamCharBufferKey parentKey = ref.get();
 2276  0
             boolean removeIt = true;
 2277  0
             if (parentKey != null)
 2278  
             {
 2279  0
                 StreamCharBuffer parent = parentKey.getBuffer();
 2280  0
                 removeIt = !parent.bufferChanged(this);
 2281  
             }
 2282  0
             if (removeIt)
 2283  
             {
 2284  0
                 i.remove();
 2285  
             }
 2286  0
         }
 2287  0
     }
 2288  
 
 2289  
     public void readExternal(ObjectInput in) throws IOException,
 2290  
             ClassNotFoundException
 2291  
     {
 2292  0
         String str = in.readUTF();
 2293  0
         reset();
 2294  0
         if (str.length() > 0)
 2295  
         {
 2296  0
             addChunk(new StringChunk(str, 0, str.length()));
 2297  
         }
 2298  0
     }
 2299  
 
 2300  
     public void writeExternal(ObjectOutput out) throws IOException
 2301  
     {
 2302  0
         String str = toString();
 2303  0
         out.writeUTF(str);
 2304  0
     }
 2305  
 
 2306  
 }