View Javadoc
1   /*
2    * Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 2.1 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this program; if not, write to the Free Software
16   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17   *
18   * Additionally licensed with:
19   *
20   * Licensed under the Apache License, Version 2.0 (the "License");
21   * you may not use this file except in compliance with the License.
22   * You may obtain a copy of the License at
23   *
24   *      http://www.apache.org/licenses/LICENSE-2.0
25   *
26   * Unless required by applicable law or agreed to in writing, software
27   * distributed under the License is distributed on an "AS IS" BASIS,
28   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29   * See the License for the specific language governing permissions and
30   * limitations under the License.
31   */
32  package org.spf4j.text;
33  //CHECKSTYLE:OFF
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   * An AttributedString holds text and related attribute information. It
55   * may be used as the actual data storage in some cases where a text
56   * reader wants to access attributed text through the AttributedCharacterIterator
57   * interface.
58   *
59   * <p>
60   * An attribute is a key/value pair, identified by the key.  No two
61   * attributes on a given character can have the same key.
62   *
63   * <p>The values for an attribute are immutable, or must not be mutated
64   * by clients or storage.  They are always passed by reference, and not
65   * cloned.
66   *
67   * @see AttributedCharacterIterator
68   * @see Annotation
69   * @since 1.2
70   */
71  
72  @SuppressFBWarnings({ "PL_PARALLEL_LISTS" }) // uses Vector specific setSize().
73  public final class AttributedString {
74  
75      // since there are no vectors of int, we have to use arrays.
76      // We allocate them in chunks of 10 elements so we don't have to allocate all the time.
77      private static final int ARRAY_SIZE_INCREMENT = 10;
78  
79      // field holding the text
80      private String text;
81  
82      // fields holding run attribute information
83      // run attributes are organized by run
84      private int runArraySize;               // current size of the arrays
85      private int runCount;                   // actual number of runs, <= runArraySize
86      private int runStarts[];                // start index for each run
87      private ArrayList<Attribute> runAttributes[];         // vector of attribute keys for each run
88      private ArrayList<Object> runAttributeValues[];    // parallel vector of attribute values for each run
89  
90      /**
91       * Constructs an AttributedString instance with the given
92       * AttributedCharacterIterators.
93       *
94       * @param iterators AttributedCharacterIterators to construct
95       * AttributedString from.
96       * @throws NullPointerException if iterators is null
97       */
98      AttributedString(@Nonnull AttributedCharacterIterator[] iterators) {
99          if (iterators.length == 0) {
100             text = "";
101         }
102         else {
103             // Build the String contents
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                 // Determine the runs, creating a new run when the attributes
113                 // differ.
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      * Constructs an AttributedString instance with the given text.
142      * @param text The text for this attributed string.
143      * @exception NullPointerException if <code>text</code> is null.
144      */
145     public AttributedString(@Nonnull String text) {
146         this.text = text;
147     }
148 
149     /**
150      * Constructs an AttributedString instance with the given text and attributes.
151      * @param text The text for this attributed string.
152      * @param attributes The attributes that apply to the entire string.
153      * @exception NullPointerException if <code>text</code> or
154      *            <code>attributes</code> is null.
155      * @exception IllegalArgumentException if the text has length 0
156      * and the attributes parameter is not an empty Map (attributes
157      * cannot be applied to a 0-length range).
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      * Constructs an AttributedString instance with the given attributed
189      * text represented by AttributedCharacterIterator.
190      * @param text The text for this attributed string.
191      * @exception NullPointerException if <code>text</code> is null.
192      */
193     public AttributedString(AttributedCharacterIterator text) {
194         // If performance is critical, this constructor should be
195         // implemented here rather than invoking the constructor for a
196         // subrange. We can avoid some range checking in the loops.
197         this(text, text.getBeginIndex(), text.getEndIndex(), null);
198     }
199 
200     /**
201      * Constructs an AttributedString instance with the subrange of
202      * the given attributed text represented by
203      * AttributedCharacterIterator. If the given range produces an
204      * empty text, all attributes will be discarded.  Note that any
205      * attributes wrapped by an Annotation object are discarded for a
206      * subrange of the original attribute range.
207      *
208      * @param text The text for this attributed string.
209      * @param beginIndex Index of the first character of the range.
210      * @param endIndex Index of the character following the last character
211      * of the range.
212      * @exception NullPointerException if <code>text</code> is null.
213      * @exception IllegalArgumentException if the subrange given by
214      * beginIndex and endIndex is out of the text range.
215      * @see java.text.Annotation
216      */
217     public AttributedString(AttributedCharacterIterator text,
218                             int beginIndex,
219                             int endIndex) {
220         this(text, beginIndex, endIndex, null);
221     }
222 
223     /**
224      * Constructs an AttributedString instance with the subrange of
225      * the given attributed text represented by
226      * AttributedCharacterIterator.  Only attributes that match the
227      * given attributes will be incorporated into the instance. If the
228      * given range produces an empty text, all attributes will be
229      * discarded. Note that any attributes wrapped by an Annotation
230      * object are discarded for a subrange of the original attribute
231      * range.
232      *
233      * @param text The text for this attributed string.
234      * @param beginIndex Index of the first character of the range.
235      * @param endIndex Index of the character following the last character
236      * of the range.
237      * @param attributes Specifies attributes to be extracted
238      * from the text. If null is specified, all available attributes will
239      * be used.
240      * @exception NullPointerException if <code>text</code> is null.
241      * @exception IllegalArgumentException if the subrange given by
242      * beginIndex and endIndex is out of the text range.
243      * @see java.text.Annotation
244      */
245     @SuppressFBWarnings("STT_TOSTRING_STORED_IN_FIELD")
246     public AttributedString(@Nonnull AttributedCharacterIterator text,
247                             int beginIndex,
248                             int endIndex,
249                             Attribute[] attributes) {
250         // Validate the given subrange
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         // Copy the given string
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         // Select attribute keys to be taken care of
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         // Get and set attribute runs for each attribute name. Need to
284         // scan from the top of the text so that we can discard any
285         // Annotation that is no longer applied to a subset text segment.
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                         // if the run is beyond the given (subset) range, we
305                         // don't need to process further.
306                         if (start >= endIndex)
307                             break;
308                         if (limit > beginIndex) {
309                             // attribute is applied to any subrange
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      * Adds an attribute to the entire string.
327      * @param attribute the attribute key
328      * @param value the value of the attribute; may be null
329      * @exception NullPointerException if <code>attribute</code> is null.
330      * @exception IllegalArgumentException if the AttributedString has length 0
331      * (attributes cannot be applied to a 0-length range).
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      * Adds an attribute to a subrange of the string.
345      * @param attribute the attribute key
346      * @param value The value of the attribute. May be null.
347      * @param beginIndex Index of the first character of the range.
348      * @param endIndex Index of the character following the last character of the range.
349      * @exception NullPointerException if <code>attribute</code> is null.
350      * @exception IllegalArgumentException if beginIndex is less then 0, endIndex is
351      * greater than the length of the string, or beginIndex and endIndex together don't
352      * define a non-empty subrange of the string.
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      * Adds a set of attributes to a subrange of the string.
366      * @param attributes The attributes to be added to the string.
367      * @param beginIndex Index of the first character of the range.
368      * @param endIndex Index of the character following the last
369      * character of the range.
370      * @exception NullPointerException if <code>attributes</code> is null.
371      * @exception IllegalArgumentException if beginIndex is less then
372      * 0, endIndex is greater than the length of the string, or
373      * beginIndex and endIndex together don't define a non-empty
374      * subrange of the string and the attributes parameter is not an
375      * empty Map.
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         // make sure we have run attribute data vectors
390         if (runCount == 0) {
391             createRunAttributeDataVectors();
392         }
393 
394         // break up runs if necessary
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         // make sure we have run attribute data vectors
410         if (runCount == 0) {
411             createRunAttributeDataVectors();
412         }
413 
414         // break up runs if necessary
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         // use temporary variables so things remain consistent in case of an exception
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; // assume initial run starting at index 0
436     }
437 
438     // ensure there's a run break at offset, return the index of the run
439     private final int ensureRunBreak(int offset) {
440         return ensureRunBreak(offset, true);
441     }
442 
443     /**
444      * Ensures there is a run break at offset, returning the index of
445      * the run. If this results in splitting a run, two things can happen:
446      * <ul>
447      * <li>If copyAttrs is true, the attributes from the existing run
448      *     will be placed in both of the newly created runs.
449      * <li>If copyAttrs is false, the attributes from the existing run
450      * will NOT be copied to the run to the right (>= offset) of the break,
451      * but will exist on the run to the left (< offset).
452      * </ul>
453      */
454     private final int ensureRunBreak(int offset, boolean copyAttrs) {
455         if (offset == length()) {
456             return runCount;
457         }
458 
459         // search for the run index where this offset should be
460         int runIndex = 0;
461         while (runIndex < runCount && runStarts[runIndex] < offset) {
462             runIndex++;
463         }
464 
465         // if the offset is at a run start already, we're done
466         if (runIndex < runCount && runStarts[runIndex] == offset) {
467             return runIndex;
468         }
469 
470         // we'll have to break up a run
471         // first, make sure we have enough space in our arrays
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         // make copies of the attribute information of the old run that the new one used to be part of
494         // use temporary variables so things remain consistent in case of an exception
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         // now actually break up the run
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     // add the attribute attribute/value to all runs where beginRunIndex <= runIndex < endRunIndex
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; // index of key and value in our vectors; assume we don't have an entry yet
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                 // check whether we have an entry already
537                 keyValueIndex = runAttribute.indexOf(attribute);
538             }
539 
540             if (keyValueIndex == -1) {
541                 // create new entry
542                 runAttribute.add(attribute);
543                 runAttributeValue.add(value);
544             } else {
545                 // update existing entry
546                 runAttributeValue.set(keyValueIndex, value);
547             }
548         }
549     }
550 
551     /**
552      * Creates an AttributedCharacterIterator instance that provides access to the entire contents of
553      * this string.
554      *
555      * @return An iterator providing access to the text and its attributes.
556      */
557     public AttributedCharacterIterator getIterator() {
558       return new AttributedStringIterator(0, length());
559     }
560 
561     // all (with the exception of length) reading operations are private,
562     // since AttributedString instances are accessed through iterators.
563 
564     // length is package private so that CharacterIteratorFieldDelegate can
565     // access it without creating an AttributedCharacterIterator.
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     // gets an attribute value, but returns an annotation only
591     // if it's range does not extend outside the range beginIndex..endIndex
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             // need to check whether the annotation's range extends outside the iterator's range
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                     // annotation's range starts before iterator's range
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                     // annotation's range ends after iterator's range
621                     return null;
622                 }
623             }
624             // annotation's range is subrange of iterator's range,
625             // so we can return the value
626         }
627         return value;
628     }
629 
630     // returns whether all specified attributes have equal values in the runs with the given indices
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     // returns whether the two objects are either both null or equal
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      * Appends the contents of the CharacterIterator iterator into the
653      * StringBuffer buf.
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      * Sets the attributes for the range from offset to the next run break
668      * (typically the end of the text) to the ones specified in attrs.
669      * This is only meant to be called from the constructor!
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      * Returns true if the attributes specified in last and attrs differ.
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     // the iterator class associated with this string class
707 
708     final private class AttributedStringIterator implements AttributedCharacterIterator {
709 
710         // note on synchronization:
711         // we don't synchronize on the iterator, assuming that an iterator is only used in one thread.
712         // we do synchronize access to the AttributedString however,
713         // since it's more likely to be shared between threads.
714 
715         // start and end index for our iteration
716         private int beginIndex;
717         private int endIndex;
718 
719         // the current index for our iteration
720         // invariant: beginIndex <= currentIndex <= endIndex
721         private int currentIndex;
722 
723         // information about the run that includes currentIndex
724         private int currentRunIndex;
725         private int currentRunStart;
726         private int currentRunLimit;
727 
728         // constructor
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         // Object methods. See documentation in that class.
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         // CharacterIterator methods. See documentation in that interface.
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         // AttributedCharacterIterator methods. See documentation in that interface.
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") // looks like a FP
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             // ??? This should screen out attribute keys that aren't relevant to the client
923             if (runAttributes == null) {
924                 // ??? would be nice to return null, but current spec doesn't allow it
925                 // returning HashSet saves us from dealing with emptiness
926                 return new HashSet<>();
927             }
928             synchronized (AttributedString.this) {
929                 // ??? should try to create this only once, then update if necessary,
930                 // and give callers read-only view
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         // internally used methods
959 
960         private AttributedString getString() {
961             return AttributedString.this;
962         }
963 
964         // set the current index, update information about the current run if necessary,
965         // return the character at the current index
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         // update the information about the current run
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     // the map class associated with this string class, giving access to the attributes of one run
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 }