1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 package org.spf4j.text;
33
34 import com.google.common.collect.Sets;
35 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
36 import java.text.Annotation;
37 import java.text.AttributedCharacterIterator;
38 import java.text.AttributedCharacterIterator.Attribute;
39 import java.text.CharacterIterator;
40 import static java.text.CharacterIterator.DONE;
41 import java.util.AbstractMap;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Collections;
45 import java.util.HashSet;
46 import java.util.Iterator;
47 import java.util.Map;
48 import java.util.Objects;
49 import java.util.Set;
50 import javax.annotation.Nonnull;
51 import javax.annotation.Nullable;
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 @SuppressFBWarnings({ "PL_PARALLEL_LISTS" })
73 public final class AttributedString {
74
75
76
77 private static final int ARRAY_SIZE_INCREMENT = 10;
78
79
80 private String text;
81
82
83
84 private int runArraySize;
85 private int runCount;
86 private int runStarts[];
87 private ArrayList<Attribute> runAttributes[];
88 private ArrayList<Object> runAttributeValues[];
89
90
91
92
93
94
95
96
97
98 AttributedString(@Nonnull AttributedCharacterIterator[] iterators) {
99 if (iterators.length == 0) {
100 text = "";
101 }
102 else {
103
104 StringBuilder buffer = new StringBuilder();
105 for (int counter = 0; counter < iterators.length; counter++) {
106 appendContents(buffer, iterators[counter]);
107 }
108
109 text = buffer.toString();
110
111 if (text.length() > 0) {
112
113
114 int offset = 0;
115 Map<Attribute,Object> last = null;
116
117 for (int counter = 0; counter < iterators.length; counter++) {
118 AttributedCharacterIterator iterator = iterators[counter];
119 int start = iterator.getBeginIndex();
120 int end = iterator.getEndIndex();
121 int index = start;
122
123 while (index < end) {
124 iterator.setIndex(index);
125
126 Map<Attribute,Object> attrs = iterator.getAttributes();
127
128 if (mapsDiffer(last, attrs)) {
129 setAttributes(attrs, index - start + offset);
130 }
131 last = attrs;
132 index = iterator.getRunLimit();
133 }
134 offset += (end - start);
135 }
136 }
137 }
138 }
139
140
141
142
143
144
145 public AttributedString(@Nonnull String text) {
146 this.text = text;
147 }
148
149
150
151
152
153
154
155
156
157
158
159 public AttributedString(@Nonnull String text,
160 @Nonnull Map<? extends Attribute, ?> attributes)
161 {
162 this.text = text;
163
164 if (text.length() == 0) {
165 if (attributes.isEmpty())
166 return;
167 throw new IllegalArgumentException("Can't add attribute to 0-length text: " + attributes);
168 }
169
170 int attributeCount = attributes.size();
171 if (attributeCount > 0) {
172 createRunAttributeDataVectors();
173 ArrayList<Attribute> newRunAttributes = new ArrayList<>(attributeCount);
174 ArrayList<Object> newRunAttributeValues = new ArrayList<>(attributeCount);
175 runAttributes[0] = newRunAttributes;
176 runAttributeValues[0] = newRunAttributeValues;
177
178 Iterator<? extends Map.Entry<? extends Attribute, ?>> iterator = attributes.entrySet().iterator();
179 while (iterator.hasNext()) {
180 Map.Entry<? extends Attribute, ?> entry = iterator.next();
181 newRunAttributes.add(entry.getKey());
182 newRunAttributeValues.add(entry.getValue());
183 }
184 }
185 }
186
187
188
189
190
191
192
193 public AttributedString(AttributedCharacterIterator text) {
194
195
196
197 this(text, text.getBeginIndex(), text.getEndIndex(), null);
198 }
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217 public AttributedString(AttributedCharacterIterator text,
218 int beginIndex,
219 int endIndex) {
220 this(text, beginIndex, endIndex, null);
221 }
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245 @SuppressFBWarnings("STT_TOSTRING_STORED_IN_FIELD")
246 public AttributedString(@Nonnull AttributedCharacterIterator text,
247 int beginIndex,
248 int endIndex,
249 Attribute[] attributes) {
250
251 int textBeginIndex = text.getBeginIndex();
252 int textEndIndex = text.getEndIndex();
253 if (beginIndex < textBeginIndex || endIndex > textEndIndex || beginIndex > endIndex)
254 throw new IllegalArgumentException("Invalid substring range " + beginIndex + ' ' + endIndex);
255
256
257 StringBuilder textBuffer = new StringBuilder(endIndex - beginIndex);
258 text.setIndex(beginIndex);
259 for (char c = text.current(); text.getIndex() < endIndex; c = text.next())
260 textBuffer.append(c);
261 this.text = textBuffer.toString();
262
263 if (beginIndex == endIndex)
264 return;
265
266
267 HashSet<Attribute> keys;
268 Set<Attribute> allAttributeKeys = text.getAllAttributeKeys();
269 if (attributes == null) {
270 keys = new HashSet<>(allAttributeKeys);
271 } else {
272 keys = Sets.newHashSetWithExpectedSize(allAttributeKeys.size());
273 for (int i = 0, l = attributes.length; i < l; i++) {
274 Attribute attribute = attributes[i];
275 if (allAttributeKeys.contains(attribute)) {
276 keys.add(attribute);
277 }
278 }
279 }
280 if (keys.isEmpty())
281 return;
282
283
284
285
286 Iterator<Attribute> itr = keys.iterator();
287 while (itr.hasNext()) {
288 Attribute attributeKey = itr.next();
289 text.setIndex(textBeginIndex);
290 while (text.getIndex() < endIndex) {
291 int start = text.getRunStart(attributeKey);
292 int limit = text.getRunLimit(attributeKey);
293 Object value = text.getAttribute(attributeKey);
294
295 if (value != null) {
296 if (value instanceof Annotation) {
297 if (start >= beginIndex && limit <= endIndex) {
298 addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex);
299 } else {
300 if (limit > endIndex)
301 break;
302 }
303 } else {
304
305
306 if (start >= endIndex)
307 break;
308 if (limit > beginIndex) {
309
310 if (start < beginIndex)
311 start = beginIndex;
312 if (limit > endIndex)
313 limit = endIndex;
314 if (start != limit) {
315 addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex);
316 }
317 }
318 }
319 }
320 text.setIndex(limit);
321 }
322 }
323 }
324
325
326
327
328
329
330
331
332
333 public void addAttribute(@Nonnull Attribute attribute, Object value) {
334
335 int len = length();
336 if (len == 0) {
337 throw new IllegalArgumentException("Can't add attribute to 0-length text: " + attribute);
338 }
339
340 addAttributeImpl(attribute, value, 0, len);
341 }
342
343
344
345
346
347
348
349
350
351
352
353
354 public void addAttribute(@Nonnull Attribute attribute, Object value,
355 int beginIndex, int endIndex) {
356
357 if (beginIndex < 0 || endIndex > length() || beginIndex >= endIndex) {
358 throw new IllegalArgumentException("Invalid substring range " + beginIndex + ',' + endIndex);
359 }
360
361 addAttributeImpl(attribute, value, beginIndex, endIndex);
362 }
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377 public void addAttributes(@Nonnull Map<? extends Attribute, ?> attributes,
378 int beginIndex, int endIndex)
379 {
380 if (beginIndex < 0 || endIndex > length() || beginIndex > endIndex) {
381 throw new IllegalArgumentException("Invalid substring range " + beginIndex + ',' + endIndex);
382 }
383 if (beginIndex == endIndex) {
384 if (attributes.isEmpty())
385 return;
386 throw new IllegalArgumentException("Can't add attribute to 0-length text" + attributes);
387 }
388
389
390 if (runCount == 0) {
391 createRunAttributeDataVectors();
392 }
393
394
395 int beginRunIndex = ensureRunBreak(beginIndex);
396 int endRunIndex = ensureRunBreak(endIndex);
397
398 Iterator<? extends Map.Entry<? extends Attribute, ?>> iterator =
399 attributes.entrySet().iterator();
400 while (iterator.hasNext()) {
401 Map.Entry<? extends Attribute, ?> entry = iterator.next();
402 addAttributeRunData(entry.getKey(), entry.getValue(), beginRunIndex, endRunIndex);
403 }
404 }
405
406 private synchronized void addAttributeImpl(Attribute attribute, Object value,
407 int beginIndex, int endIndex) {
408
409
410 if (runCount == 0) {
411 createRunAttributeDataVectors();
412 }
413
414
415 int beginRunIndex = ensureRunBreak(beginIndex);
416 int endRunIndex = ensureRunBreak(endIndex);
417
418 addAttributeRunData(attribute, value, beginRunIndex, endRunIndex);
419 }
420
421 private final void createRunAttributeDataVectors() {
422
423 int newRunStarts[] = new int[ARRAY_SIZE_INCREMENT];
424
425 @SuppressWarnings("unchecked")
426 ArrayList<Attribute> newRunAttributes[] = (ArrayList<Attribute>[]) new ArrayList<?>[ARRAY_SIZE_INCREMENT];
427
428 @SuppressWarnings("unchecked")
429 ArrayList<Object> newRunAttributeValues[] = (ArrayList<Object>[]) new ArrayList<?>[ARRAY_SIZE_INCREMENT];
430
431 runStarts = newRunStarts;
432 runAttributes = newRunAttributes;
433 runAttributeValues = newRunAttributeValues;
434 runArraySize = ARRAY_SIZE_INCREMENT;
435 runCount = 1;
436 }
437
438
439 private final int ensureRunBreak(int offset) {
440 return ensureRunBreak(offset, true);
441 }
442
443
444
445
446
447
448
449
450
451
452
453
454 private final int ensureRunBreak(int offset, boolean copyAttrs) {
455 if (offset == length()) {
456 return runCount;
457 }
458
459
460 int runIndex = 0;
461 while (runIndex < runCount && runStarts[runIndex] < offset) {
462 runIndex++;
463 }
464
465
466 if (runIndex < runCount && runStarts[runIndex] == offset) {
467 return runIndex;
468 }
469
470
471
472 if (runCount == runArraySize) {
473 int newArraySize = runArraySize + ARRAY_SIZE_INCREMENT;
474 int newRunStarts[] = new int[newArraySize];
475
476 @SuppressWarnings("unchecked")
477 ArrayList<Attribute> newRunAttributes[] = (ArrayList<Attribute>[]) new ArrayList<?>[newArraySize];
478
479 @SuppressWarnings("unchecked")
480 ArrayList<Object> newRunAttributeValues[] = (ArrayList<Object>[]) new ArrayList<?>[newArraySize];
481
482 for (int i = 0; i < runArraySize; i++) {
483 newRunStarts[i] = runStarts[i];
484 newRunAttributes[i] = runAttributes[i];
485 newRunAttributeValues[i] = runAttributeValues[i];
486 }
487 runStarts = newRunStarts;
488 runAttributes = newRunAttributes;
489 runAttributeValues = newRunAttributeValues;
490 runArraySize = newArraySize;
491 }
492
493
494
495 ArrayList<Attribute> newRunAttributes = null;
496 ArrayList<Object> newRunAttributeValues = null;
497
498 if (copyAttrs) {
499 int rim1 = runIndex - 1;
500 ArrayList<Attribute> oldRunAttributes = runAttributes[rim1];
501 if (oldRunAttributes != null) {
502 newRunAttributes = new ArrayList<>(oldRunAttributes);
503 }
504 ArrayList<Object> oldRunAttributeValues = runAttributeValues[rim1];
505 if (oldRunAttributeValues != null) {
506 newRunAttributeValues = new ArrayList<>(oldRunAttributeValues);
507 }
508 }
509
510
511 runCount++;
512 for (int i = runCount - 1, j = i - 1; i > runIndex; i--, j--) {
513 runStarts[i] = runStarts[j];
514 runAttributes[i] = runAttributes[j];
515 runAttributeValues[i] = runAttributeValues[j];
516 }
517 runStarts[runIndex] = offset;
518 runAttributes[runIndex] = newRunAttributes;
519 runAttributeValues[runIndex] = newRunAttributeValues;
520
521 return runIndex;
522 }
523
524
525 private void addAttributeRunData(Attribute attribute, Object value,
526 int beginRunIndex, int endRunIndex) {
527
528 for (int i = beginRunIndex; i < endRunIndex; i++) {
529 int keyValueIndex = -1;
530 ArrayList<Attribute> runAttribute = runAttributes[i];
531 ArrayList<Object> runAttributeValue = runAttributeValues[i];
532 if (runAttribute == null) {
533 runAttributes[i] = runAttribute = new ArrayList<>();
534 runAttributeValues[i] = runAttributeValue = new ArrayList<>();
535 } else {
536
537 keyValueIndex = runAttribute.indexOf(attribute);
538 }
539
540 if (keyValueIndex == -1) {
541
542 runAttribute.add(attribute);
543 runAttributeValue.add(value);
544 } else {
545
546 runAttributeValue.set(keyValueIndex, value);
547 }
548 }
549 }
550
551
552
553
554
555
556
557 public AttributedCharacterIterator getIterator() {
558 return new AttributedStringIterator(0, length());
559 }
560
561
562
563
564
565
566 private int length() {
567 return text.length();
568 }
569
570 private char charAt(int index) {
571 return text.charAt(index);
572 }
573
574 @Nullable
575 private synchronized Object getAttribute(Attribute attribute, int runIndex) {
576 ArrayList<Attribute> currentRunAttributes = runAttributes[runIndex];
577 ArrayList<Object> currentRunAttributeValues = runAttributeValues[runIndex];
578 if (currentRunAttributes == null) {
579 return null;
580 }
581 int attributeIndex = currentRunAttributes.indexOf(attribute);
582 if (attributeIndex != -1) {
583 return currentRunAttributeValues.get(attributeIndex);
584 }
585 else {
586 return null;
587 }
588 }
589
590
591
592 @Nullable
593 private Object getAttributeCheckRange(Attribute attribute, int runIndex, int beginIndex, int endIndex) {
594 Object value = getAttribute(attribute, runIndex);
595 if (value instanceof Annotation) {
596
597 if (beginIndex > 0) {
598 int currIndex = runIndex;
599 int runStart = runStarts[currIndex];
600 while (runStart >= beginIndex &&
601 valuesMatch(value, getAttribute(attribute, currIndex - 1))) {
602 currIndex--;
603 runStart = runStarts[currIndex];
604 }
605 if (runStart < beginIndex) {
606
607 return null;
608 }
609 }
610 int textLength = length();
611 if (endIndex < textLength) {
612 int currIndex = runIndex;
613 int runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength;
614 while (runLimit <= endIndex &&
615 valuesMatch(value, getAttribute(attribute, currIndex + 1))) {
616 currIndex++;
617 runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength;
618 }
619 if (runLimit > endIndex) {
620
621 return null;
622 }
623 }
624
625
626 }
627 return value;
628 }
629
630
631 private boolean attributeValuesMatch(Set<? extends Attribute> attributes, int runIndex1, int runIndex2) {
632 Iterator<? extends Attribute> iterator = attributes.iterator();
633 while (iterator.hasNext()) {
634 Attribute key = iterator.next();
635 if (!valuesMatch(getAttribute(key, runIndex1), getAttribute(key, runIndex2))) {
636 return false;
637 }
638 }
639 return true;
640 }
641
642
643 private final static boolean valuesMatch(Object value1, Object value2) {
644 if (value1 == null) {
645 return value2 == null;
646 } else {
647 return value1.equals(value2);
648 }
649 }
650
651
652
653
654
655 private final void appendContents(StringBuilder buf,
656 CharacterIterator iterator) {
657 int index = iterator.getBeginIndex();
658 int end = iterator.getEndIndex();
659
660 while (index < end) {
661 iterator.setIndex(index++);
662 buf.append(iterator.current());
663 }
664 }
665
666
667
668
669
670
671 private void setAttributes(Map<Attribute, Object> attrs, int offset) {
672 if (runCount == 0) {
673 createRunAttributeDataVectors();
674 }
675
676 int index = ensureRunBreak(offset, false);
677 int size;
678
679 if (attrs != null && (size = attrs.size()) > 0) {
680 ArrayList<Attribute> runAttrs = new ArrayList<>(size);
681 ArrayList<Object> runValues = new ArrayList<>(size);
682 Iterator<Map.Entry<Attribute, Object>> iterator = attrs.entrySet().iterator();
683
684 while (iterator.hasNext()) {
685 Map.Entry<Attribute, Object> entry = iterator.next();
686
687 runAttrs.add(entry.getKey());
688 runValues.add(entry.getValue());
689 }
690 runAttributes[index] = runAttrs;
691 runAttributeValues[index] = runValues;
692 }
693 }
694
695
696
697
698 private static <K,V> boolean mapsDiffer(Map<K, V> last, Map<K, V> attrs) {
699 if (last == null) {
700 return (attrs != null && attrs.size() > 0);
701 }
702 return (!last.equals(attrs));
703 }
704
705
706
707
708 final private class AttributedStringIterator implements AttributedCharacterIterator {
709
710
711
712
713
714
715
716 private int beginIndex;
717 private int endIndex;
718
719
720
721 private int currentIndex;
722
723
724 private int currentRunIndex;
725 private int currentRunStart;
726 private int currentRunLimit;
727
728
729 AttributedStringIterator(int beginIndex, int endIndex) {
730
731 if (beginIndex < 0 || beginIndex > endIndex || endIndex > length()) {
732 throw new IllegalArgumentException("Invalid substring range " + beginIndex + ',' + endIndex);
733 }
734
735 this.beginIndex = beginIndex;
736 this.endIndex = endIndex;
737 this.currentIndex = beginIndex;
738 updateRunInfo();
739 }
740
741
742
743 public boolean equals(Object obj) {
744 if (this == obj) {
745 return true;
746 }
747 if (!(obj instanceof AttributedStringIterator)) {
748 return false;
749 }
750
751 AttributedStringIterator that = (AttributedStringIterator) obj;
752
753 if (AttributedString.this != that.getString())
754 return false;
755 return !(currentIndex != that.currentIndex || beginIndex != that.beginIndex || endIndex != that.endIndex);
756 }
757
758 public int hashCode() {
759 return text.hashCode() ^ currentIndex ^ beginIndex ^ endIndex;
760 }
761
762 public AttributedStringIterator clone() {
763 try {
764 return (AttributedStringIterator) super.clone();
765 }
766 catch (CloneNotSupportedException e) {
767 throw new InternalError(e);
768 }
769 }
770
771
772
773 public char first() {
774 return internalSetIndex(beginIndex);
775 }
776
777 public char last() {
778 if (endIndex == beginIndex) {
779 return internalSetIndex(endIndex);
780 } else {
781 return internalSetIndex(endIndex - 1);
782 }
783 }
784
785 public char current() {
786 if (currentIndex == endIndex) {
787 return DONE;
788 } else {
789 return charAt(currentIndex);
790 }
791 }
792
793 public char next() {
794 if (currentIndex < endIndex) {
795 return internalSetIndex(currentIndex + 1);
796 }
797 else {
798 return DONE;
799 }
800 }
801
802 public char previous() {
803 if (currentIndex > beginIndex) {
804 return internalSetIndex(currentIndex - 1);
805 }
806 else {
807 return DONE;
808 }
809 }
810
811 public char setIndex(int position) {
812 if (position < beginIndex || position > endIndex)
813 throw new IllegalArgumentException("Invalid index " + position);
814 return internalSetIndex(position);
815 }
816
817 public int getBeginIndex() {
818 return beginIndex;
819 }
820
821 public int getEndIndex() {
822 return endIndex;
823 }
824
825 public int getIndex() {
826 return currentIndex;
827 }
828
829
830
831 public int getRunStart() {
832 return currentRunStart;
833 }
834
835 public int getRunStart(Attribute attribute) {
836 if (currentRunStart == beginIndex || currentRunIndex == -1) {
837 return currentRunStart;
838 } else {
839 Object value = getAttribute(attribute);
840 int runStart = currentRunStart;
841 int runIndex = currentRunIndex;
842 while (runStart > beginIndex &&
843 valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex - 1))) {
844 runIndex--;
845 runStart = runStarts[runIndex];
846 }
847 if (runStart < beginIndex) {
848 runStart = beginIndex;
849 }
850 return runStart;
851 }
852 }
853
854 public int getRunStart(Set<? extends Attribute> attributes) {
855 if (currentRunStart == beginIndex || currentRunIndex == -1) {
856 return currentRunStart;
857 } else {
858 int runStart = currentRunStart;
859 int runIndex = currentRunIndex;
860 while (runStart > beginIndex &&
861 AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex - 1)) {
862 runIndex--;
863 runStart = runStarts[runIndex];
864 }
865 if (runStart < beginIndex) {
866 runStart = beginIndex;
867 }
868 return runStart;
869 }
870 }
871
872 public int getRunLimit() {
873 return currentRunLimit;
874 }
875
876 public int getRunLimit(Attribute attribute) {
877 if (currentRunLimit == endIndex || currentRunIndex == -1) {
878 return currentRunLimit;
879 } else {
880 Object value = getAttribute(attribute);
881 int runLimit = currentRunLimit;
882 int runIndex = currentRunIndex;
883 while (runLimit < endIndex &&
884 valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex + 1))) {
885 runIndex++;
886 runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex;
887 }
888 if (runLimit > endIndex) {
889 runLimit = endIndex;
890 }
891 return runLimit;
892 }
893 }
894
895 public int getRunLimit(Set<? extends Attribute> attributes) {
896 if (currentRunLimit == endIndex || currentRunIndex == -1) {
897 return currentRunLimit;
898 } else {
899 int runLimit = currentRunLimit;
900 int runIndex = currentRunIndex;
901 while (runLimit < endIndex &&
902 AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex + 1)) {
903 runIndex++;
904 runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex;
905 }
906 if (runLimit > endIndex) {
907 runLimit = endIndex;
908 }
909 return runLimit;
910 }
911 }
912
913 @SuppressFBWarnings("SEO_SUBOPTIMAL_EXPRESSION_ORDER")
914 public Map<Attribute,Object> getAttributes() {
915 if (runAttributes == null || currentRunIndex == -1 || runAttributes[currentRunIndex] == null) {
916 return Collections.EMPTY_MAP;
917 }
918 return new AttributeMap(currentRunIndex, beginIndex, endIndex);
919 }
920
921 public Set<Attribute> getAllAttributeKeys() {
922
923 if (runAttributes == null) {
924
925
926 return new HashSet<>();
927 }
928 synchronized (AttributedString.this) {
929
930
931 Set<Attribute> keys = new HashSet<>();
932 int i = 0;
933 while (i < runCount) {
934 if (runStarts[i] < endIndex && (i == runCount - 1 || runStarts[i + 1] > beginIndex)) {
935 ArrayList<Attribute> currentRunAttributes = runAttributes[i];
936 if (currentRunAttributes != null) {
937 int j = currentRunAttributes.size();
938 while (j-- > 0) {
939 keys.add(currentRunAttributes.get(j));
940 }
941 }
942 }
943 i++;
944 }
945 return keys;
946 }
947 }
948
949 @Nullable
950 public Object getAttribute(Attribute attribute) {
951 int runIndex = currentRunIndex;
952 if (runIndex < 0) {
953 return null;
954 }
955 return AttributedString.this.getAttributeCheckRange(attribute, runIndex, beginIndex, endIndex);
956 }
957
958
959
960 private AttributedString getString() {
961 return AttributedString.this;
962 }
963
964
965
966 private char internalSetIndex(int position) {
967 currentIndex = position;
968 if (position < currentRunStart || position >= currentRunLimit) {
969 updateRunInfo();
970 }
971 if (currentIndex == endIndex) {
972 return DONE;
973 } else {
974 return charAt(position);
975 }
976 }
977
978
979 private void updateRunInfo() {
980 if (currentIndex == endIndex) {
981 currentRunStart = currentRunLimit = endIndex;
982 currentRunIndex = -1;
983 } else {
984 synchronized (AttributedString.this) {
985 int runIndex = -1;
986 while (runIndex < runCount - 1 && runStarts[runIndex + 1] <= currentIndex)
987 runIndex++;
988 currentRunIndex = runIndex;
989 if (runIndex >= 0) {
990 currentRunStart = runStarts[runIndex];
991 if (currentRunStart < beginIndex)
992 currentRunStart = beginIndex;
993 }
994 else {
995 currentRunStart = beginIndex;
996 }
997 if (runIndex < runCount - 1) {
998 currentRunLimit = runStarts[runIndex + 1];
999 if (currentRunLimit > endIndex)
1000 currentRunLimit = endIndex;
1001 }
1002 else {
1003 currentRunLimit = endIndex;
1004 }
1005 }
1006 }
1007 }
1008
1009 }
1010
1011
1012
1013 final private class AttributeMap extends AbstractMap<Attribute,Object> {
1014
1015 private final int runIndex;
1016 private final int beginIndex;
1017 private final int endIndex;
1018
1019 AttributeMap(int runIndex, int beginIndex, int endIndex) {
1020 this.runIndex = runIndex;
1021 this.beginIndex = beginIndex;
1022 this.endIndex = endIndex;
1023 }
1024
1025 public Set<Map.Entry<Attribute, Object>> entrySet() {
1026 HashSet<Map.Entry<Attribute, Object>> set = new HashSet<>();
1027 synchronized (AttributedString.this) {
1028 int size = runAttributes[runIndex].size();
1029 for (int i = 0; i < size; i++) {
1030 Attribute key = runAttributes[runIndex].get(i);
1031 Object value = runAttributeValues[runIndex].get(i);
1032 if (value instanceof Annotation) {
1033 value = AttributedString.this.getAttributeCheckRange(key,
1034 runIndex, beginIndex, endIndex);
1035 if (value == null) {
1036 continue;
1037 }
1038 }
1039
1040 Map.Entry<Attribute, Object> entry = new AttributeEntry(key, value);
1041 set.add(entry);
1042 }
1043 }
1044 return set;
1045 }
1046
1047 @Nullable
1048 public Object get(Object key) {
1049 return AttributedString.this.getAttributeCheckRange((Attribute) key, runIndex, beginIndex, endIndex);
1050 }
1051 }
1052
1053 @Override
1054 public String toString() {
1055 return "AttributedString{" + "text=" + text + ", runArraySize=" + runArraySize
1056 + ", runCount=" + runCount + ", runStarts=" + Arrays.toString(runStarts) + ", runAttributes="
1057 + Arrays.toString(runAttributes) + ", runAttributeValues=" + Arrays.toString(runAttributeValues) + '}';
1058 }
1059
1060
1061
1062 }
1063
1064 final class AttributeEntry implements Map.Entry<Attribute,Object> {
1065
1066 private final Attribute key;
1067 private final Object value;
1068
1069 AttributeEntry(Attribute key, Object value) {
1070 this.key = key;
1071 this.value = value;
1072 }
1073
1074 public boolean equals(Object o) {
1075 if (!(o instanceof AttributeEntry)) {
1076 return false;
1077 }
1078 AttributeEntry other = (AttributeEntry) o;
1079 return other.key.equals(key) && Objects.equals(value, other.value);
1080 }
1081
1082 public Attribute getKey() {
1083 return key;
1084 }
1085
1086 public Object getValue() {
1087 return value;
1088 }
1089
1090 public Object setValue(Object newValue) {
1091 throw new UnsupportedOperationException();
1092 }
1093
1094 public int hashCode() {
1095 return key.hashCode() ^ (value==null ? 0 : value.hashCode());
1096 }
1097
1098 public String toString() {
1099 return key.toString() + '=' + value;
1100 }
1101 }