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