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