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