Home | History | Annotate | Download | only in text
      1 /*
      2  * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
      3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      4  *
      5  * This code is free software; you can redistribute it and/or modify it
      6  * under the terms of the GNU General Public License version 2 only, as
      7  * published by the Free Software Foundation.  Oracle designates this
      8  * particular file as subject to the "Classpath" exception as provided
      9  * by Oracle in the LICENSE file that accompanied this code.
     10  *
     11  * This code is distributed in the hope that it will be useful, but WITHOUT
     12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     14  * version 2 for more details (a copy is included in the LICENSE file that
     15  * accompanied this code).
     16  *
     17  * You should have received a copy of the GNU General Public License version
     18  * 2 along with this work; if not, write to the Free Software Foundation,
     19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     20  *
     21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     22  * or visit www.oracle.com if you need additional information or have any
     23  * questions.
     24  */
     25 
     26 package java.text;
     27 
     28 import java.util.*;
     29 import java.text.AttributedCharacterIterator.Attribute;
     30 
     31 /**
     32  * An AttributedString holds text and related attribute information. It
     33  * may be used as the actual data storage in some cases where a text
     34  * reader wants to access attributed text through the AttributedCharacterIterator
     35  * interface.
     36  *
     37  * <p>
     38  * An attribute is a key/value pair, identified by the key.  No two
     39  * attributes on a given character can have the same key.
     40  *
     41  * <p>The values for an attribute are immutable, or must not be mutated
     42  * by clients or storage.  They are always passed by reference, and not
     43  * cloned.
     44  *
     45  * @see AttributedCharacterIterator
     46  * @see Annotation
     47  * @since 1.2
     48  */
     49 
     50 public class AttributedString {
     51 
     52     // since there are no vectors of int, we have to use arrays.
     53     // We allocate them in chunks of 10 elements so we don't have to allocate all the time.
     54     private static final int ARRAY_SIZE_INCREMENT = 10;
     55 
     56     // field holding the text
     57     String text;
     58 
     59     // fields holding run attribute information
     60     // run attributes are organized by run
     61     int runArraySize;               // current size of the arrays
     62     int runCount;                   // actual number of runs, <= runArraySize
     63     int runStarts[];                // start index for each run
     64     Vector<Attribute> runAttributes[];         // vector of attribute keys for each run
     65     Vector<Object> runAttributeValues[];    // parallel vector of attribute values for each run
     66 
     67     /**
     68      * Constructs an AttributedString instance with the given
     69      * AttributedCharacterIterators.
     70      *
     71      * @param iterators AttributedCharacterIterators to construct
     72      * AttributedString from.
     73      * @throws NullPointerException if iterators is null
     74      */
     75     AttributedString(AttributedCharacterIterator[] iterators) {
     76         if (iterators == null) {
     77             throw new NullPointerException("Iterators must not be null");
     78         }
     79         if (iterators.length == 0) {
     80             text = "";
     81         }
     82         else {
     83             // Build the String contents
     84             StringBuffer buffer = new StringBuffer();
     85             for (int counter = 0; counter < iterators.length; counter++) {
     86                 appendContents(buffer, iterators[counter]);
     87             }
     88 
     89             text = buffer.toString();
     90 
     91             if (text.length() > 0) {
     92                 // Determine the runs, creating a new run when the attributes
     93                 // differ.
     94                 int offset = 0;
     95                 Map<Attribute,Object> last = null;
     96 
     97                 for (int counter = 0; counter < iterators.length; counter++) {
     98                     AttributedCharacterIterator iterator = iterators[counter];
     99                     int start = iterator.getBeginIndex();
    100                     int end = iterator.getEndIndex();
    101                     int index = start;
    102 
    103                     while (index < end) {
    104                         iterator.setIndex(index);
    105 
    106                         Map<Attribute,Object> attrs = iterator.getAttributes();
    107 
    108                         if (mapsDiffer(last, attrs)) {
    109                             setAttributes(attrs, index - start + offset);
    110                         }
    111                         last = attrs;
    112                         index = iterator.getRunLimit();
    113                     }
    114                     offset += (end - start);
    115                 }
    116             }
    117         }
    118     }
    119 
    120     /**
    121      * Constructs an AttributedString instance with the given text.
    122      * @param text The text for this attributed string.
    123      * @exception NullPointerException if <code>text</code> is null.
    124      */
    125     public AttributedString(String text) {
    126         if (text == null) {
    127             throw new NullPointerException();
    128         }
    129         this.text = text;
    130     }
    131 
    132     /**
    133      * Constructs an AttributedString instance with the given text and attributes.
    134      * @param text The text for this attributed string.
    135      * @param attributes The attributes that apply to the entire string.
    136      * @exception NullPointerException if <code>text</code> or
    137      *            <code>attributes</code> is null.
    138      * @exception IllegalArgumentException if the text has length 0
    139      * and the attributes parameter is not an empty Map (attributes
    140      * cannot be applied to a 0-length range).
    141      */
    142     public AttributedString(String text,
    143                             Map<? extends Attribute, ?> attributes)
    144     {
    145         if (text == null || attributes == null) {
    146             throw new NullPointerException();
    147         }
    148         this.text = text;
    149 
    150         if (text.length() == 0) {
    151             if (attributes.isEmpty())
    152                 return;
    153             throw new IllegalArgumentException("Can't add attribute to 0-length text");
    154         }
    155 
    156         int attributeCount = attributes.size();
    157         if (attributeCount > 0) {
    158             createRunAttributeDataVectors();
    159             Vector<Attribute> newRunAttributes = new Vector<>(attributeCount);
    160             Vector<Object> newRunAttributeValues = new Vector<>(attributeCount);
    161             runAttributes[0] = newRunAttributes;
    162             runAttributeValues[0] = newRunAttributeValues;
    163 
    164             Iterator<? extends Map.Entry<? extends Attribute, ?>> iterator = attributes.entrySet().iterator();
    165             while (iterator.hasNext()) {
    166                 Map.Entry<? extends Attribute, ?> entry = iterator.next();
    167                 newRunAttributes.addElement(entry.getKey());
    168                 newRunAttributeValues.addElement(entry.getValue());
    169             }
    170         }
    171     }
    172 
    173     /**
    174      * Constructs an AttributedString instance with the given attributed
    175      * text represented by AttributedCharacterIterator.
    176      * @param text The text for this attributed string.
    177      * @exception NullPointerException if <code>text</code> is null.
    178      */
    179     public AttributedString(AttributedCharacterIterator text) {
    180         // If performance is critical, this constructor should be
    181         // implemented here rather than invoking the constructor for a
    182         // subrange. We can avoid some range checking in the loops.
    183         this(text, text.getBeginIndex(), text.getEndIndex(), null);
    184     }
    185 
    186     /**
    187      * Constructs an AttributedString instance with the subrange of
    188      * the given attributed text represented by
    189      * AttributedCharacterIterator. If the given range produces an
    190      * empty text, all attributes will be discarded.  Note that any
    191      * attributes wrapped by an Annotation object are discarded for a
    192      * subrange of the original attribute range.
    193      *
    194      * @param text The text for this attributed string.
    195      * @param beginIndex Index of the first character of the range.
    196      * @param endIndex Index of the character following the last character
    197      * of the range.
    198      * @exception NullPointerException if <code>text</code> is null.
    199      * @exception IllegalArgumentException if the subrange given by
    200      * beginIndex and endIndex is out of the text range.
    201      * @see java.text.Annotation
    202      */
    203     public AttributedString(AttributedCharacterIterator text,
    204                             int beginIndex,
    205                             int endIndex) {
    206         this(text, beginIndex, endIndex, null);
    207     }
    208 
    209     /**
    210      * Constructs an AttributedString instance with the subrange of
    211      * the given attributed text represented by
    212      * AttributedCharacterIterator.  Only attributes that match the
    213      * given attributes will be incorporated into the instance. If the
    214      * given range produces an empty text, all attributes will be
    215      * discarded. Note that any attributes wrapped by an Annotation
    216      * object are discarded for a subrange of the original attribute
    217      * range.
    218      *
    219      * @param text The text for this attributed string.
    220      * @param beginIndex Index of the first character of the range.
    221      * @param endIndex Index of the character following the last character
    222      * of the range.
    223      * @param attributes Specifies attributes to be extracted
    224      * from the text. If null is specified, all available attributes will
    225      * be used.
    226      * @exception NullPointerException if <code>text</code> is null.
    227      * @exception IllegalArgumentException if the subrange given by
    228      * beginIndex and endIndex is out of the text range.
    229      * @see java.text.Annotation
    230      */
    231     public AttributedString(AttributedCharacterIterator text,
    232                             int beginIndex,
    233                             int endIndex,
    234                             Attribute[] attributes) {
    235         if (text == null) {
    236             throw new NullPointerException();
    237         }
    238 
    239         // Validate the given subrange
    240         int textBeginIndex = text.getBeginIndex();
    241         int textEndIndex = text.getEndIndex();
    242         if (beginIndex < textBeginIndex || endIndex > textEndIndex || beginIndex > endIndex)
    243             throw new IllegalArgumentException("Invalid substring range");
    244 
    245         // Copy the given string
    246         StringBuffer textBuffer = new StringBuffer();
    247         text.setIndex(beginIndex);
    248         for (char c = text.current(); text.getIndex() < endIndex; c = text.next())
    249             textBuffer.append(c);
    250         this.text = textBuffer.toString();
    251 
    252         if (beginIndex == endIndex)
    253             return;
    254 
    255         // Select attribute keys to be taken care of
    256         HashSet<Attribute> keys = new HashSet<>();
    257         if (attributes == null) {
    258             keys.addAll(text.getAllAttributeKeys());
    259         } else {
    260             for (int i = 0; i < attributes.length; i++)
    261                 keys.add(attributes[i]);
    262             keys.retainAll(text.getAllAttributeKeys());
    263         }
    264         if (keys.isEmpty())
    265             return;
    266 
    267         // Get and set attribute runs for each attribute name. Need to
    268         // scan from the top of the text so that we can discard any
    269         // Annotation that is no longer applied to a subset text segment.
    270         Iterator<Attribute> itr = keys.iterator();
    271         while (itr.hasNext()) {
    272             Attribute attributeKey = itr.next();
    273             text.setIndex(textBeginIndex);
    274             while (text.getIndex() < endIndex) {
    275                 int start = text.getRunStart(attributeKey);
    276                 int limit = text.getRunLimit(attributeKey);
    277                 Object value = text.getAttribute(attributeKey);
    278 
    279                 if (value != null) {
    280                     if (value instanceof Annotation) {
    281                         if (start >= beginIndex && limit <= endIndex) {
    282                             addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex);
    283                         } else {
    284                             if (limit > endIndex)
    285                                 break;
    286                         }
    287                     } else {
    288                         // if the run is beyond the given (subset) range, we
    289                         // don't need to process further.
    290                         if (start >= endIndex)
    291                             break;
    292                         if (limit > beginIndex) {
    293                             // attribute is applied to any subrange
    294                             if (start < beginIndex)
    295                                 start = beginIndex;
    296                             if (limit > endIndex)
    297                                 limit = endIndex;
    298                             if (start != limit) {
    299                                 addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex);
    300                             }
    301                         }
    302                     }
    303                 }
    304                 text.setIndex(limit);
    305             }
    306         }
    307     }
    308 
    309     /**
    310      * Adds an attribute to the entire string.
    311      * @param attribute the attribute key
    312      * @param value the value of the attribute; may be null
    313      * @exception NullPointerException if <code>attribute</code> is null.
    314      * @exception IllegalArgumentException if the AttributedString has length 0
    315      * (attributes cannot be applied to a 0-length range).
    316      */
    317     public void addAttribute(Attribute attribute, Object value) {
    318 
    319         if (attribute == null) {
    320             throw new NullPointerException();
    321         }
    322 
    323         int len = length();
    324         if (len == 0) {
    325             throw new IllegalArgumentException("Can't add attribute to 0-length text");
    326         }
    327 
    328         addAttributeImpl(attribute, value, 0, len);
    329     }
    330 
    331     /**
    332      * Adds an attribute to a subrange of the string.
    333      * @param attribute the attribute key
    334      * @param value The value of the attribute. May be null.
    335      * @param beginIndex Index of the first character of the range.
    336      * @param endIndex Index of the character following the last character of the range.
    337      * @exception NullPointerException if <code>attribute</code> is null.
    338      * @exception IllegalArgumentException if beginIndex is less then 0, endIndex is
    339      * greater than the length of the string, or beginIndex and endIndex together don't
    340      * define a non-empty subrange of the string.
    341      */
    342     public void addAttribute(Attribute attribute, Object value,
    343             int beginIndex, int endIndex) {
    344 
    345         if (attribute == null) {
    346             throw new NullPointerException();
    347         }
    348 
    349         if (beginIndex < 0 || endIndex > length() || beginIndex >= endIndex) {
    350             throw new IllegalArgumentException("Invalid substring range");
    351         }
    352 
    353         addAttributeImpl(attribute, value, beginIndex, endIndex);
    354     }
    355 
    356     /**
    357      * Adds a set of attributes to a subrange of the string.
    358      * @param attributes The attributes to be added to the string.
    359      * @param beginIndex Index of the first character of the range.
    360      * @param endIndex Index of the character following the last
    361      * character of the range.
    362      * @exception NullPointerException if <code>attributes</code> is null.
    363      * @exception IllegalArgumentException if beginIndex is less then
    364      * 0, endIndex is greater than the length of the string, or
    365      * beginIndex and endIndex together don't define a non-empty
    366      * subrange of the string and the attributes parameter is not an
    367      * empty Map.
    368      */
    369     public void addAttributes(Map<? extends Attribute, ?> attributes,
    370                               int beginIndex, int endIndex)
    371     {
    372         if (attributes == null) {
    373             throw new NullPointerException();
    374         }
    375 
    376         if (beginIndex < 0 || endIndex > length() || beginIndex > endIndex) {
    377             throw new IllegalArgumentException("Invalid substring range");
    378         }
    379         if (beginIndex == endIndex) {
    380             if (attributes.isEmpty())
    381                 return;
    382             throw new IllegalArgumentException("Can't add attribute to 0-length text");
    383         }
    384 
    385         // make sure we have run attribute data vectors
    386         if (runCount == 0) {
    387             createRunAttributeDataVectors();
    388         }
    389 
    390         // break up runs if necessary
    391         int beginRunIndex = ensureRunBreak(beginIndex);
    392         int endRunIndex = ensureRunBreak(endIndex);
    393 
    394         Iterator<? extends Map.Entry<? extends Attribute, ?>> iterator =
    395             attributes.entrySet().iterator();
    396         while (iterator.hasNext()) {
    397             Map.Entry<? extends Attribute, ?> entry = iterator.next();
    398             addAttributeRunData(entry.getKey(), entry.getValue(), beginRunIndex, endRunIndex);
    399         }
    400     }
    401 
    402     private synchronized void addAttributeImpl(Attribute attribute, Object value,
    403             int beginIndex, int endIndex) {
    404 
    405         // make sure we have run attribute data vectors
    406         if (runCount == 0) {
    407             createRunAttributeDataVectors();
    408         }
    409 
    410         // break up runs if necessary
    411         int beginRunIndex = ensureRunBreak(beginIndex);
    412         int endRunIndex = ensureRunBreak(endIndex);
    413 
    414         addAttributeRunData(attribute, value, beginRunIndex, endRunIndex);
    415     }
    416 
    417     private final void createRunAttributeDataVectors() {
    418         // use temporary variables so things remain consistent in case of an exception
    419         int newRunStarts[] = new int[ARRAY_SIZE_INCREMENT];
    420 
    421         @SuppressWarnings("unchecked")
    422         Vector<Attribute> newRunAttributes[] = (Vector<Attribute>[]) new Vector<?>[ARRAY_SIZE_INCREMENT];
    423 
    424         @SuppressWarnings("unchecked")
    425         Vector<Object> newRunAttributeValues[] = (Vector<Object>[]) new Vector<?>[ARRAY_SIZE_INCREMENT];
    426 
    427         runStarts = newRunStarts;
    428         runAttributes = newRunAttributes;
    429         runAttributeValues = newRunAttributeValues;
    430         runArraySize = ARRAY_SIZE_INCREMENT;
    431         runCount = 1; // assume initial run starting at index 0
    432     }
    433 
    434     // ensure there's a run break at offset, return the index of the run
    435     private final int ensureRunBreak(int offset) {
    436         return ensureRunBreak(offset, true);
    437     }
    438 
    439     /**
    440      * Ensures there is a run break at offset, returning the index of
    441      * the run. If this results in splitting a run, two things can happen:
    442      * <ul>
    443      * <li>If copyAttrs is true, the attributes from the existing run
    444      *     will be placed in both of the newly created runs.
    445      * <li>If copyAttrs is false, the attributes from the existing run
    446      * will NOT be copied to the run to the right (>= offset) of the break,
    447      * but will exist on the run to the left (< offset).
    448      * </ul>
    449      */
    450     private final int ensureRunBreak(int offset, boolean copyAttrs) {
    451         if (offset == length()) {
    452             return runCount;
    453         }
    454 
    455         // search for the run index where this offset should be
    456         int runIndex = 0;
    457         while (runIndex < runCount && runStarts[runIndex] < offset) {
    458             runIndex++;
    459         }
    460 
    461         // if the offset is at a run start already, we're done
    462         if (runIndex < runCount && runStarts[runIndex] == offset) {
    463             return runIndex;
    464         }
    465 
    466         // we'll have to break up a run
    467         // first, make sure we have enough space in our arrays
    468         if (runCount == runArraySize) {
    469             int newArraySize = runArraySize + ARRAY_SIZE_INCREMENT;
    470             int newRunStarts[] = new int[newArraySize];
    471 
    472             @SuppressWarnings("unchecked")
    473             Vector<Attribute> newRunAttributes[] = (Vector<Attribute>[]) new Vector<?>[newArraySize];
    474 
    475             @SuppressWarnings("unchecked")
    476             Vector<Object> newRunAttributeValues[] = (Vector<Object>[]) new Vector<?>[newArraySize];
    477 
    478             for (int i = 0; i < runArraySize; i++) {
    479                 newRunStarts[i] = runStarts[i];
    480                 newRunAttributes[i] = runAttributes[i];
    481                 newRunAttributeValues[i] = runAttributeValues[i];
    482             }
    483             runStarts = newRunStarts;
    484             runAttributes = newRunAttributes;
    485             runAttributeValues = newRunAttributeValues;
    486             runArraySize = newArraySize;
    487         }
    488 
    489         // make copies of the attribute information of the old run that the new one used to be part of
    490         // use temporary variables so things remain consistent in case of an exception
    491         Vector<Attribute> newRunAttributes = null;
    492         Vector<Object> newRunAttributeValues = null;
    493 
    494         if (copyAttrs) {
    495             Vector<Attribute> oldRunAttributes = runAttributes[runIndex - 1];
    496             Vector<Object> oldRunAttributeValues = runAttributeValues[runIndex - 1];
    497             if (oldRunAttributes != null) {
    498                 newRunAttributes = new Vector<>(oldRunAttributes);
    499             }
    500             if (oldRunAttributeValues != null) {
    501                 newRunAttributeValues =  new Vector<>(oldRunAttributeValues);
    502             }
    503         }
    504 
    505         // now actually break up the run
    506         runCount++;
    507         for (int i = runCount - 1; i > runIndex; i--) {
    508             runStarts[i] = runStarts[i - 1];
    509             runAttributes[i] = runAttributes[i - 1];
    510             runAttributeValues[i] = runAttributeValues[i - 1];
    511         }
    512         runStarts[runIndex] = offset;
    513         runAttributes[runIndex] = newRunAttributes;
    514         runAttributeValues[runIndex] = newRunAttributeValues;
    515 
    516         return runIndex;
    517     }
    518 
    519     // add the attribute attribute/value to all runs where beginRunIndex <= runIndex < endRunIndex
    520     private void addAttributeRunData(Attribute attribute, Object value,
    521             int beginRunIndex, int endRunIndex) {
    522 
    523         for (int i = beginRunIndex; i < endRunIndex; i++) {
    524             int keyValueIndex = -1; // index of key and value in our vectors; assume we don't have an entry yet
    525             if (runAttributes[i] == null) {
    526                 Vector<Attribute> newRunAttributes = new Vector<>();
    527                 Vector<Object> newRunAttributeValues = new Vector<>();
    528                 runAttributes[i] = newRunAttributes;
    529                 runAttributeValues[i] = newRunAttributeValues;
    530             } else {
    531                 // check whether we have an entry already
    532                 keyValueIndex = runAttributes[i].indexOf(attribute);
    533             }
    534 
    535             if (keyValueIndex == -1) {
    536                 // create new entry
    537                 int oldSize = runAttributes[i].size();
    538                 runAttributes[i].addElement(attribute);
    539                 try {
    540                     runAttributeValues[i].addElement(value);
    541                 }
    542                 catch (Exception e) {
    543                     runAttributes[i].setSize(oldSize);
    544                     runAttributeValues[i].setSize(oldSize);
    545                 }
    546             } else {
    547                 // update existing entry
    548                 runAttributeValues[i].set(keyValueIndex, value);
    549             }
    550         }
    551     }
    552 
    553     /**
    554      * Creates an AttributedCharacterIterator instance that provides access to the entire contents of
    555      * this string.
    556      *
    557      * @return An iterator providing access to the text and its attributes.
    558      */
    559     public AttributedCharacterIterator getIterator() {
    560         return getIterator(null, 0, length());
    561     }
    562 
    563     /**
    564      * Creates an AttributedCharacterIterator instance that provides access to
    565      * selected contents of this string.
    566      * Information about attributes not listed in attributes that the
    567      * implementor may have need not be made accessible through the iterator.
    568      * If the list is null, all available attribute information should be made
    569      * accessible.
    570      *
    571      * @param attributes a list of attributes that the client is interested in
    572      * @return an iterator providing access to the entire text and its selected attributes
    573      */
    574     public AttributedCharacterIterator getIterator(Attribute[] attributes) {
    575         return getIterator(attributes, 0, length());
    576     }
    577 
    578     /**
    579      * Creates an AttributedCharacterIterator instance that provides access to
    580      * selected contents of this string.
    581      * Information about attributes not listed in attributes that the
    582      * implementor may have need not be made accessible through the iterator.
    583      * If the list is null, all available attribute information should be made
    584      * accessible.
    585      *
    586      * @param attributes a list of attributes that the client is interested in
    587      * @param beginIndex the index of the first character
    588      * @param endIndex the index of the character following the last character
    589      * @return an iterator providing access to the text and its attributes
    590      * @exception IllegalArgumentException if beginIndex is less then 0,
    591      * endIndex is greater than the length of the string, or beginIndex is
    592      * greater than endIndex.
    593      */
    594     public AttributedCharacterIterator getIterator(Attribute[] attributes, int beginIndex, int endIndex) {
    595         return new AttributedStringIterator(attributes, beginIndex, endIndex);
    596     }
    597 
    598     // all (with the exception of length) reading operations are private,
    599     // since AttributedString instances are accessed through iterators.
    600 
    601     // length is package private so that CharacterIteratorFieldDelegate can
    602     // access it without creating an AttributedCharacterIterator.
    603     int length() {
    604         return text.length();
    605     }
    606 
    607     private char charAt(int index) {
    608         return text.charAt(index);
    609     }
    610 
    611     private synchronized Object getAttribute(Attribute attribute, int runIndex) {
    612         Vector<Attribute> currentRunAttributes = runAttributes[runIndex];
    613         Vector<Object> currentRunAttributeValues = runAttributeValues[runIndex];
    614         if (currentRunAttributes == null) {
    615             return null;
    616         }
    617         int attributeIndex = currentRunAttributes.indexOf(attribute);
    618         if (attributeIndex != -1) {
    619             return currentRunAttributeValues.elementAt(attributeIndex);
    620         }
    621         else {
    622             return null;
    623         }
    624     }
    625 
    626     // gets an attribute value, but returns an annotation only if it's range does not extend outside the range beginIndex..endIndex
    627     private Object getAttributeCheckRange(Attribute attribute, int runIndex, int beginIndex, int endIndex) {
    628         Object value = getAttribute(attribute, runIndex);
    629         if (value instanceof Annotation) {
    630             // need to check whether the annotation's range extends outside the iterator's range
    631             if (beginIndex > 0) {
    632                 int currIndex = runIndex;
    633                 int runStart = runStarts[currIndex];
    634                 while (runStart >= beginIndex &&
    635                         valuesMatch(value, getAttribute(attribute, currIndex - 1))) {
    636                     currIndex--;
    637                     runStart = runStarts[currIndex];
    638                 }
    639                 if (runStart < beginIndex) {
    640                     // annotation's range starts before iterator's range
    641                     return null;
    642                 }
    643             }
    644             int textLength = length();
    645             if (endIndex < textLength) {
    646                 int currIndex = runIndex;
    647                 int runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength;
    648                 while (runLimit <= endIndex &&
    649                         valuesMatch(value, getAttribute(attribute, currIndex + 1))) {
    650                     currIndex++;
    651                     runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength;
    652                 }
    653                 if (runLimit > endIndex) {
    654                     // annotation's range ends after iterator's range
    655                     return null;
    656                 }
    657             }
    658             // annotation's range is subrange of iterator's range,
    659             // so we can return the value
    660         }
    661         return value;
    662     }
    663 
    664     // returns whether all specified attributes have equal values in the runs with the given indices
    665     private boolean attributeValuesMatch(Set<? extends Attribute> attributes, int runIndex1, int runIndex2) {
    666         Iterator<? extends Attribute> iterator = attributes.iterator();
    667         while (iterator.hasNext()) {
    668             Attribute key = iterator.next();
    669            if (!valuesMatch(getAttribute(key, runIndex1), getAttribute(key, runIndex2))) {
    670                 return false;
    671             }
    672         }
    673         return true;
    674     }
    675 
    676     // returns whether the two objects are either both null or equal
    677     private final static boolean valuesMatch(Object value1, Object value2) {
    678         if (value1 == null) {
    679             return value2 == null;
    680         } else {
    681             return value1.equals(value2);
    682         }
    683     }
    684 
    685     /**
    686      * Appends the contents of the CharacterIterator iterator into the
    687      * StringBuffer buf.
    688      */
    689     private final void appendContents(StringBuffer buf,
    690                                       CharacterIterator iterator) {
    691         int index = iterator.getBeginIndex();
    692         int end = iterator.getEndIndex();
    693 
    694         while (index < end) {
    695             iterator.setIndex(index++);
    696             buf.append(iterator.current());
    697         }
    698     }
    699 
    700     /**
    701      * Sets the attributes for the range from offset to the next run break
    702      * (typically the end of the text) to the ones specified in attrs.
    703      * This is only meant to be called from the constructor!
    704      */
    705     private void setAttributes(Map<Attribute, Object> attrs, int offset) {
    706         if (runCount == 0) {
    707             createRunAttributeDataVectors();
    708         }
    709 
    710         int index = ensureRunBreak(offset, false);
    711         int size;
    712 
    713         if (attrs != null && (size = attrs.size()) > 0) {
    714             Vector<Attribute> runAttrs = new Vector<>(size);
    715             Vector<Object> runValues = new Vector<>(size);
    716             Iterator<Map.Entry<Attribute, Object>> iterator = attrs.entrySet().iterator();
    717 
    718             while (iterator.hasNext()) {
    719                 Map.Entry<Attribute, Object> entry = iterator.next();
    720 
    721                 runAttrs.add(entry.getKey());
    722                 runValues.add(entry.getValue());
    723             }
    724             runAttributes[index] = runAttrs;
    725             runAttributeValues[index] = runValues;
    726         }
    727     }
    728 
    729     /**
    730      * Returns true if the attributes specified in last and attrs differ.
    731      */
    732     private static <K,V> boolean mapsDiffer(Map<K, V> last, Map<K, V> attrs) {
    733         if (last == null) {
    734             return (attrs != null && attrs.size() > 0);
    735         }
    736         return (!last.equals(attrs));
    737     }
    738 
    739 
    740     // the iterator class associated with this string class
    741 
    742     final private class AttributedStringIterator implements AttributedCharacterIterator {
    743 
    744         // note on synchronization:
    745         // we don't synchronize on the iterator, assuming that an iterator is only used in one thread.
    746         // we do synchronize access to the AttributedString however, since it's more likely to be shared between threads.
    747 
    748         // start and end index for our iteration
    749         private int beginIndex;
    750         private int endIndex;
    751 
    752         // attributes that our client is interested in
    753         private Attribute[] relevantAttributes;
    754 
    755         // the current index for our iteration
    756         // invariant: beginIndex <= currentIndex <= endIndex
    757         private int currentIndex;
    758 
    759         // information about the run that includes currentIndex
    760         private int currentRunIndex;
    761         private int currentRunStart;
    762         private int currentRunLimit;
    763 
    764         // constructor
    765         AttributedStringIterator(Attribute[] attributes, int beginIndex, int endIndex) {
    766 
    767             if (beginIndex < 0 || beginIndex > endIndex || endIndex > length()) {
    768                 throw new IllegalArgumentException("Invalid substring range");
    769             }
    770 
    771             this.beginIndex = beginIndex;
    772             this.endIndex = endIndex;
    773             this.currentIndex = beginIndex;
    774             updateRunInfo();
    775             if (attributes != null) {
    776                 relevantAttributes = attributes.clone();
    777             }
    778         }
    779 
    780         // Object methods. See documentation in that class.
    781 
    782         public boolean equals(Object obj) {
    783             if (this == obj) {
    784                 return true;
    785             }
    786             if (!(obj instanceof AttributedStringIterator)) {
    787                 return false;
    788             }
    789 
    790             AttributedStringIterator that = (AttributedStringIterator) obj;
    791 
    792             if (AttributedString.this != that.getString())
    793                 return false;
    794             if (currentIndex != that.currentIndex || beginIndex != that.beginIndex || endIndex != that.endIndex)
    795                 return false;
    796             return true;
    797         }
    798 
    799         public int hashCode() {
    800             return text.hashCode() ^ currentIndex ^ beginIndex ^ endIndex;
    801         }
    802 
    803         public Object clone() {
    804             try {
    805                 AttributedStringIterator other = (AttributedStringIterator) super.clone();
    806                 return other;
    807             }
    808             catch (CloneNotSupportedException e) {
    809                 throw new InternalError(e);
    810             }
    811         }
    812 
    813         // CharacterIterator methods. See documentation in that interface.
    814 
    815         public char first() {
    816             return internalSetIndex(beginIndex);
    817         }
    818 
    819         public char last() {
    820             if (endIndex == beginIndex) {
    821                 return internalSetIndex(endIndex);
    822             } else {
    823                 return internalSetIndex(endIndex - 1);
    824             }
    825         }
    826 
    827         public char current() {
    828             if (currentIndex == endIndex) {
    829                 return DONE;
    830             } else {
    831                 return charAt(currentIndex);
    832             }
    833         }
    834 
    835         public char next() {
    836             if (currentIndex < endIndex) {
    837                 return internalSetIndex(currentIndex + 1);
    838             }
    839             else {
    840                 return DONE;
    841             }
    842         }
    843 
    844         public char previous() {
    845             if (currentIndex > beginIndex) {
    846                 return internalSetIndex(currentIndex - 1);
    847             }
    848             else {
    849                 return DONE;
    850             }
    851         }
    852 
    853         public char setIndex(int position) {
    854             if (position < beginIndex || position > endIndex)
    855                 throw new IllegalArgumentException("Invalid index");
    856             return internalSetIndex(position);
    857         }
    858 
    859         public int getBeginIndex() {
    860             return beginIndex;
    861         }
    862 
    863         public int getEndIndex() {
    864             return endIndex;
    865         }
    866 
    867         public int getIndex() {
    868             return currentIndex;
    869         }
    870 
    871         // AttributedCharacterIterator methods. See documentation in that interface.
    872 
    873         public int getRunStart() {
    874             return currentRunStart;
    875         }
    876 
    877         public int getRunStart(Attribute attribute) {
    878             if (currentRunStart == beginIndex || currentRunIndex == -1) {
    879                 return currentRunStart;
    880             } else {
    881                 Object value = getAttribute(attribute);
    882                 int runStart = currentRunStart;
    883                 int runIndex = currentRunIndex;
    884                 while (runStart > beginIndex &&
    885                         valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex - 1))) {
    886                     runIndex--;
    887                     runStart = runStarts[runIndex];
    888                 }
    889                 if (runStart < beginIndex) {
    890                     runStart = beginIndex;
    891                 }
    892                 return runStart;
    893             }
    894         }
    895 
    896         public int getRunStart(Set<? extends Attribute> attributes) {
    897             if (currentRunStart == beginIndex || currentRunIndex == -1) {
    898                 return currentRunStart;
    899             } else {
    900                 int runStart = currentRunStart;
    901                 int runIndex = currentRunIndex;
    902                 while (runStart > beginIndex &&
    903                         AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex - 1)) {
    904                     runIndex--;
    905                     runStart = runStarts[runIndex];
    906                 }
    907                 if (runStart < beginIndex) {
    908                     runStart = beginIndex;
    909                 }
    910                 return runStart;
    911             }
    912         }
    913 
    914         public int getRunLimit() {
    915             return currentRunLimit;
    916         }
    917 
    918         public int getRunLimit(Attribute attribute) {
    919             if (currentRunLimit == endIndex || currentRunIndex == -1) {
    920                 return currentRunLimit;
    921             } else {
    922                 Object value = getAttribute(attribute);
    923                 int runLimit = currentRunLimit;
    924                 int runIndex = currentRunIndex;
    925                 while (runLimit < endIndex &&
    926                         valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex + 1))) {
    927                     runIndex++;
    928                     runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex;
    929                 }
    930                 if (runLimit > endIndex) {
    931                     runLimit = endIndex;
    932                 }
    933                 return runLimit;
    934             }
    935         }
    936 
    937         public int getRunLimit(Set<? extends Attribute> attributes) {
    938             if (currentRunLimit == endIndex || currentRunIndex == -1) {
    939                 return currentRunLimit;
    940             } else {
    941                 int runLimit = currentRunLimit;
    942                 int runIndex = currentRunIndex;
    943                 while (runLimit < endIndex &&
    944                         AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex + 1)) {
    945                     runIndex++;
    946                     runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex;
    947                 }
    948                 if (runLimit > endIndex) {
    949                     runLimit = endIndex;
    950                 }
    951                 return runLimit;
    952             }
    953         }
    954 
    955         public Map<Attribute,Object> getAttributes() {
    956             if (runAttributes == null || currentRunIndex == -1 || runAttributes[currentRunIndex] == null) {
    957                 // ??? would be nice to return null, but current spec doesn't allow it
    958                 // returning Hashtable saves AttributeMap from dealing with emptiness
    959                 return new Hashtable<>();
    960             }
    961             return new AttributeMap(currentRunIndex, beginIndex, endIndex);
    962         }
    963 
    964         public Set<Attribute> getAllAttributeKeys() {
    965             // ??? This should screen out attribute keys that aren't relevant to the client
    966             if (runAttributes == null) {
    967                 // ??? would be nice to return null, but current spec doesn't allow it
    968                 // returning HashSet saves us from dealing with emptiness
    969                 return new HashSet<>();
    970             }
    971             synchronized (AttributedString.this) {
    972                 // ??? should try to create this only once, then update if necessary,
    973                 // and give callers read-only view
    974                 Set<Attribute> keys = new HashSet<>();
    975                 int i = 0;
    976                 while (i < runCount) {
    977                     if (runStarts[i] < endIndex && (i == runCount - 1 || runStarts[i + 1] > beginIndex)) {
    978                         Vector<Attribute> currentRunAttributes = runAttributes[i];
    979                         if (currentRunAttributes != null) {
    980                             int j = currentRunAttributes.size();
    981                             while (j-- > 0) {
    982                                 keys.add(currentRunAttributes.get(j));
    983                             }
    984                         }
    985                     }
    986                     i++;
    987                 }
    988                 return keys;
    989             }
    990         }
    991 
    992         public Object getAttribute(Attribute attribute) {
    993             int runIndex = currentRunIndex;
    994             if (runIndex < 0) {
    995                 return null;
    996             }
    997             return AttributedString.this.getAttributeCheckRange(attribute, runIndex, beginIndex, endIndex);
    998         }
    999 
   1000         // internally used methods
   1001 
   1002         private AttributedString getString() {
   1003             return AttributedString.this;
   1004         }
   1005 
   1006         // set the current index, update information about the current run if necessary,
   1007         // return the character at the current index
   1008         private char internalSetIndex(int position) {
   1009             currentIndex = position;
   1010             if (position < currentRunStart || position >= currentRunLimit) {
   1011                 updateRunInfo();
   1012             }
   1013             if (currentIndex == endIndex) {
   1014                 return DONE;
   1015             } else {
   1016                 return charAt(position);
   1017             }
   1018         }
   1019 
   1020         // update the information about the current run
   1021         private void updateRunInfo() {
   1022             if (currentIndex == endIndex) {
   1023                 currentRunStart = currentRunLimit = endIndex;
   1024                 currentRunIndex = -1;
   1025             } else {
   1026                 synchronized (AttributedString.this) {
   1027                     int runIndex = -1;
   1028                     while (runIndex < runCount - 1 && runStarts[runIndex + 1] <= currentIndex)
   1029                         runIndex++;
   1030                     currentRunIndex = runIndex;
   1031                     if (runIndex >= 0) {
   1032                         currentRunStart = runStarts[runIndex];
   1033                         if (currentRunStart < beginIndex)
   1034                             currentRunStart = beginIndex;
   1035                     }
   1036                     else {
   1037                         currentRunStart = beginIndex;
   1038                     }
   1039                     if (runIndex < runCount - 1) {
   1040                         currentRunLimit = runStarts[runIndex + 1];
   1041                         if (currentRunLimit > endIndex)
   1042                             currentRunLimit = endIndex;
   1043                     }
   1044                     else {
   1045                         currentRunLimit = endIndex;
   1046                     }
   1047                 }
   1048             }
   1049         }
   1050 
   1051     }
   1052 
   1053     // the map class associated with this string class, giving access to the attributes of one run
   1054 
   1055     final private class AttributeMap extends AbstractMap<Attribute,Object> {
   1056 
   1057         int runIndex;
   1058         int beginIndex;
   1059         int endIndex;
   1060 
   1061         AttributeMap(int runIndex, int beginIndex, int endIndex) {
   1062             this.runIndex = runIndex;
   1063             this.beginIndex = beginIndex;
   1064             this.endIndex = endIndex;
   1065         }
   1066 
   1067         public Set<Map.Entry<Attribute, Object>> entrySet() {
   1068             HashSet<Map.Entry<Attribute, Object>> set = new HashSet<>();
   1069             synchronized (AttributedString.this) {
   1070                 int size = runAttributes[runIndex].size();
   1071                 for (int i = 0; i < size; i++) {
   1072                     Attribute key = runAttributes[runIndex].get(i);
   1073                     Object value = runAttributeValues[runIndex].get(i);
   1074                     if (value instanceof Annotation) {
   1075                         value = AttributedString.this.getAttributeCheckRange(key,
   1076                                                              runIndex, beginIndex, endIndex);
   1077                         if (value == null) {
   1078                             continue;
   1079                         }
   1080                     }
   1081 
   1082                     Map.Entry<Attribute, Object> entry = new AttributeEntry(key, value);
   1083                     set.add(entry);
   1084                 }
   1085             }
   1086             return set;
   1087         }
   1088 
   1089         public Object get(Object key) {
   1090             return AttributedString.this.getAttributeCheckRange((Attribute) key, runIndex, beginIndex, endIndex);
   1091         }
   1092     }
   1093 }
   1094 
   1095 class AttributeEntry implements Map.Entry<Attribute,Object> {
   1096 
   1097     private Attribute key;
   1098     private Object value;
   1099 
   1100     AttributeEntry(Attribute key, Object value) {
   1101         this.key = key;
   1102         this.value = value;
   1103     }
   1104 
   1105     public boolean equals(Object o) {
   1106         if (!(o instanceof AttributeEntry)) {
   1107             return false;
   1108         }
   1109         AttributeEntry other = (AttributeEntry) o;
   1110         return other.key.equals(key) &&
   1111             (value == null ? other.value == null : other.value.equals(value));
   1112     }
   1113 
   1114     public Attribute getKey() {
   1115         return key;
   1116     }
   1117 
   1118     public Object getValue() {
   1119         return value;
   1120     }
   1121 
   1122     public Object setValue(Object newValue) {
   1123         throw new UnsupportedOperationException();
   1124     }
   1125 
   1126     public int hashCode() {
   1127         return key.hashCode() ^ (value==null ? 0 : value.hashCode());
   1128     }
   1129 
   1130     public String toString() {
   1131         return key.toString()+"="+value.toString();
   1132     }
   1133 }
   1134