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.
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.
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).
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.
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
29 import java.text.AttributedCharacterIterator.Attribute;
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
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.
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
45 * @see AttributedCharacterIterator
50 public class AttributedString {
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;
56 // field holding the text
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
68 * Constructs an AttributedString instance with the given
69 * AttributedCharacterIterators.
71 * @param iterators AttributedCharacterIterators to construct
72 * AttributedString from.
73 * @throws NullPointerException if iterators is null
75 AttributedString(AttributedCharacterIterator[] iterators) {
76 if (iterators == null) {
77 throw new NullPointerException("Iterators must not be null");
79 if (iterators.length == 0) {
83 // Build the String contents
84 StringBuffer buffer = new StringBuffer();
85 for (int counter = 0; counter < iterators.length; counter++) {
86 appendContents(buffer, iterators[counter]);
89 text = buffer.toString();
91 if (text.length() > 0) {
92 // Determine the runs, creating a new run when the attributes
97 for (int counter = 0; counter < iterators.length; counter++) {
98 AttributedCharacterIterator iterator = iterators[counter];
99 int start = iterator.getBeginIndex();
100 int end = iterator.getEndIndex();
103 while (index < end) {
104 iterator.setIndex(index);
106 Map attrs = iterator.getAttributes();
108 if (mapsDiffer(last, attrs)) {
109 setAttributes(attrs, index - start + offset);
112 index = iterator.getRunLimit();
114 offset += (end - start);
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.
125 public AttributedString(String text) {
127 throw new NullPointerException();
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).
142 public AttributedString(String text,
143 Map<? extends Attribute, ?> attributes)
145 if (text == null || attributes == null) {
146 throw new NullPointerException();
150 if (text.length() == 0) {
151 if (attributes.isEmpty())
153 throw new IllegalArgumentException("Can't add attribute to 0-length text");
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());
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.
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);
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.
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
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
202 public AttributedString(AttributedCharacterIterator text,
205 this(text, beginIndex, endIndex, null);
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
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
222 * @param attributes Specifies attributes to be extracted
223 * from the text. If null is specified, all available attributes will
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
230 public AttributedString(AttributedCharacterIterator text,
233 Attribute[] attributes) {
235 throw new NullPointerException();
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");
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();
251 if (beginIndex == endIndex)
254 // Select attribute keys to be taken care of
255 HashSet keys = new HashSet();
256 if (attributes == null) {
257 keys.addAll(text.getAllAttributeKeys());
259 for (int i = 0; i < attributes.length; i++)
260 keys.add(attributes[i]);
261 keys.retainAll(text.getAllAttributeKeys());
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);
279 if (value instanceof Annotation) {
280 if (start >= beginIndex && limit <= endIndex) {
281 addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex);
283 if (limit > endIndex)
287 // if the run is beyond the given (subset) range, we
288 // don't need to process further.
289 if (start >= endIndex)
291 if (limit > beginIndex) {
292 // attribute is applied to any subrange
293 if (start < beginIndex)
295 if (limit > endIndex)
297 if (start != limit) {
298 addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex);
303 text.setIndex(limit);
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).
316 public void addAttribute(Attribute attribute, Object value) {
318 if (attribute == null) {
319 throw new NullPointerException();
324 throw new IllegalArgumentException("Can't add attribute to 0-length text");
327 addAttributeImpl(attribute, value, 0, len);
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.
341 public void addAttribute(Attribute attribute, Object value,
342 int beginIndex, int endIndex) {
344 if (attribute == null) {
345 throw new NullPointerException();
348 if (beginIndex < 0 || endIndex > length() || beginIndex >= endIndex) {
349 throw new IllegalArgumentException("Invalid substring range");
352 addAttributeImpl(attribute, value, beginIndex, endIndex);
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
368 public void addAttributes(Map<? extends Attribute, ?> attributes,
369 int beginIndex, int endIndex)
371 if (attributes == null) {
372 throw new NullPointerException();
375 if (beginIndex < 0 || endIndex > length() || beginIndex > endIndex) {
376 throw new IllegalArgumentException("Invalid substring range");
378 if (beginIndex == endIndex) {
379 if (attributes.isEmpty())
381 throw new IllegalArgumentException("Can't add attribute to 0-length text");
384 // make sure we have run attribute data vectors
386 createRunAttributeDataVectors();
389 // break up runs if necessary
390 int beginRunIndex = ensureRunBreak(beginIndex);
391 int endRunIndex = ensureRunBreak(endIndex);
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);
400 private synchronized void addAttributeImpl(Attribute attribute, Object value,
401 int beginIndex, int endIndex) {
403 // make sure we have run attribute data vectors
405 createRunAttributeDataVectors();
408 // break up runs if necessary
409 int beginRunIndex = ensureRunBreak(beginIndex);
410 int endRunIndex = ensureRunBreak(endIndex);
412 addAttributeRunData(attribute, value, beginRunIndex, endRunIndex);
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
427 // ensure there's a run break at offset, return the index of the run
428 private final int ensureRunBreak(int offset) {
429 return ensureRunBreak(offset, true);
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:
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).
443 private final int ensureRunBreak(int offset, boolean copyAttrs) {
444 if (offset == length()) {
448 // search for the run index where this offset should be
450 while (runIndex < runCount && runStarts[runIndex] < offset) {
454 // if the offset is at a run start already, we're done
455 if (runIndex < runCount && runStarts[runIndex] == offset) {
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];
471 runStarts = newRunStarts;
472 runAttributes = newRunAttributes;
473 runAttributeValues = newRunAttributeValues;
474 runArraySize = newArraySize;
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;
483 Vector oldRunAttributes = runAttributes[runIndex - 1];
484 Vector oldRunAttributeValues = runAttributeValues[runIndex - 1];
485 if (oldRunAttributes != null) {
486 newRunAttributes = (Vector) oldRunAttributes.clone();
488 if (oldRunAttributeValues != null) {
489 newRunAttributeValues = (Vector) oldRunAttributeValues.clone();
493 // now actually break up the run
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];
500 runStarts[runIndex] = offset;
501 runAttributes[runIndex] = newRunAttributes;
502 runAttributeValues[runIndex] = newRunAttributeValues;
507 // add the attribute attribute/value to all runs where beginRunIndex <= runIndex < endRunIndex
508 private void addAttributeRunData(Attribute attribute, Object value,
509 int beginRunIndex, int endRunIndex) {
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;
519 // check whether we have an entry already
520 keyValueIndex = runAttributes[i].indexOf(attribute);
523 if (keyValueIndex == -1) {
525 int oldSize = runAttributes[i].size();
526 runAttributes[i].addElement(attribute);
528 runAttributeValues[i].addElement(value);
530 catch (Exception e) {
531 runAttributes[i].setSize(oldSize);
532 runAttributeValues[i].setSize(oldSize);
535 // update existing entry
536 runAttributeValues[i].set(keyValueIndex, value);
542 * Creates an AttributedCharacterIterator instance that provides access to the entire contents of
545 * @return An iterator providing access to the text and its attributes.
547 public AttributedCharacterIterator getIterator() {
548 return getIterator(null, 0, length());
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
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
562 public AttributedCharacterIterator getIterator(Attribute[] attributes) {
563 return getIterator(attributes, 0, length());
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
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.
582 public AttributedCharacterIterator getIterator(Attribute[] attributes, int beginIndex, int endIndex) {
583 return new AttributedStringIterator(attributes, beginIndex, endIndex);
586 // all (with the exception of length) reading operations are private,
587 // since AttributedString instances are accessed through iterators.
589 // length is package private so that CharacterIteratorFieldDelegate can
590 // access it without creating an AttributedCharacterIterator.
592 return text.length();
595 private char charAt(int index) {
596 return text.charAt(index);
599 private synchronized Object getAttribute(Attribute attribute, int runIndex) {
600 Vector currentRunAttributes = runAttributes[runIndex];
601 Vector currentRunAttributeValues = runAttributeValues[runIndex];
602 if (currentRunAttributes == null) {
605 int attributeIndex = currentRunAttributes.indexOf(attribute);
606 if (attributeIndex != -1) {
607 return currentRunAttributeValues.elementAt(attributeIndex);
614 // gets an attribute value, but returns an annotation only if it's range does not extend outside the range beginIndex..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))) {
625 runStart = runStarts[currIndex];
627 if (runStart < beginIndex) {
628 // annotation's range starts before iterator's range
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))) {
639 runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength;
641 if (runLimit > endIndex) {
642 // annotation's range ends after iterator's range
646 // annotation's range is subrange of iterator's range,
647 // so we can return the value
652 // returns whether all specified attributes have equal values in the runs with the given indices
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))) {
664 // returns whether the two objects are either both null or equal
665 private final static boolean valuesMatch(Object value1, Object value2) {
666 if (value1 == null) {
667 return value2 == null;
669 return value1.equals(value2);
674 * Appends the contents of the CharacterIterator iterator into the
677 private final void appendContents(StringBuffer buf,
678 CharacterIterator iterator) {
679 int index = iterator.getBeginIndex();
680 int end = iterator.getEndIndex();
682 while (index < end) {
683 iterator.setIndex(index++);
684 buf.append(iterator.current());
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!
693 private void setAttributes(Map attrs, int offset) {
695 createRunAttributeDataVectors();
698 int index = ensureRunBreak(offset, false);
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();
706 while (iterator.hasNext()) {
707 Map.Entry entry = (Map.Entry)iterator.next();
709 runAttrs.add(entry.getKey());
710 runValues.add(entry.getValue());
712 runAttributes[index] = runAttrs;
713 runAttributeValues[index] = runValues;
718 * Returns true if the attributes specified in last and attrs differ.
720 private static boolean mapsDiffer(Map last, Map attrs) {
722 return (attrs != null && attrs.size() > 0);
724 return (!last.equals(attrs));
728 // the iterator class associated with this string class
730 final private class AttributedStringIterator implements AttributedCharacterIterator {
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.
736 // start and end index for our iteration
737 private int beginIndex;
738 private int endIndex;
740 // attributes that our client is interested in
741 private Attribute[] relevantAttributes;
743 // the current index for our iteration
744 // invariant: beginIndex <= currentIndex <= endIndex
745 private int currentIndex;
747 // information about the run that includes currentIndex
748 private int currentRunIndex;
749 private int currentRunStart;
750 private int currentRunLimit;
753 AttributedStringIterator(Attribute[] attributes, int beginIndex, int endIndex) {
755 if (beginIndex < 0 || beginIndex > endIndex || endIndex > length()) {
756 throw new IllegalArgumentException("Invalid substring range");
759 this.beginIndex = beginIndex;
760 this.endIndex = endIndex;
761 this.currentIndex = beginIndex;
763 if (attributes != null) {
764 relevantAttributes = (Attribute[]) attributes.clone();
768 // Object methods. See documentation in that class.
770 public boolean equals(Object obj) {
774 if (!(obj instanceof AttributedStringIterator)) {
778 AttributedStringIterator that = (AttributedStringIterator) obj;
780 if (AttributedString.this != that.getString())
782 if (currentIndex != that.currentIndex || beginIndex != that.beginIndex || endIndex != that.endIndex)
787 public int hashCode() {
788 return text.hashCode() ^ currentIndex ^ beginIndex ^ endIndex;
791 public Object clone() {
793 AttributedStringIterator other = (AttributedStringIterator) super.clone();
796 catch (CloneNotSupportedException e) {
797 throw new InternalError();
801 // CharacterIterator methods. See documentation in that interface.
803 public char first() {
804 return internalSetIndex(beginIndex);
808 if (endIndex == beginIndex) {
809 return internalSetIndex(endIndex);
811 return internalSetIndex(endIndex - 1);
815 public char current() {
816 if (currentIndex == endIndex) {
819 return charAt(currentIndex);
824 if (currentIndex < endIndex) {
825 return internalSetIndex(currentIndex + 1);
832 public char previous() {
833 if (currentIndex > beginIndex) {
834 return internalSetIndex(currentIndex - 1);
841 public char setIndex(int position) {
842 if (position < beginIndex || position > endIndex)
843 throw new IllegalArgumentException("Invalid index");
844 return internalSetIndex(position);
847 public int getBeginIndex() {
851 public int getEndIndex() {
855 public int getIndex() {
859 // AttributedCharacterIterator methods. See documentation in that interface.
861 public int getRunStart() {
862 return currentRunStart;
865 public int getRunStart(Attribute attribute) {
866 if (currentRunStart == beginIndex || currentRunIndex == -1) {
867 return currentRunStart;
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))) {
875 runStart = runStarts[runIndex];
877 if (runStart < beginIndex) {
878 runStart = beginIndex;
884 public int getRunStart(Set<? extends Attribute> attributes) {
885 if (currentRunStart == beginIndex || currentRunIndex == -1) {
886 return currentRunStart;
888 int runStart = currentRunStart;
889 int runIndex = currentRunIndex;
890 while (runStart > beginIndex &&
891 AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex - 1)) {
893 runStart = runStarts[runIndex];
895 if (runStart < beginIndex) {
896 runStart = beginIndex;
902 public int getRunLimit() {
903 return currentRunLimit;
906 public int getRunLimit(Attribute attribute) {
907 if (currentRunLimit == endIndex || currentRunIndex == -1) {
908 return currentRunLimit;
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))) {
916 runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex;
918 if (runLimit > endIndex) {
925 public int getRunLimit(Set<? extends Attribute> attributes) {
926 if (currentRunLimit == endIndex || currentRunIndex == -1) {
927 return currentRunLimit;
929 int runLimit = currentRunLimit;
930 int runIndex = currentRunIndex;
931 while (runLimit < endIndex &&
932 AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex + 1)) {
934 runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex;
936 if (runLimit > endIndex) {
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();
949 return new AttributeMap(currentRunIndex, beginIndex, endIndex);
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();
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();
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();
970 keys.add(currentRunAttributes.get(j));
980 public Object getAttribute(Attribute attribute) {
981 int runIndex = currentRunIndex;
985 return AttributedString.this.getAttributeCheckRange(attribute, runIndex, beginIndex, endIndex);
988 // internally used methods
990 private AttributedString getString() {
991 return AttributedString.this;
994 // set the current index, update information about the current run if necessary,
995 // return the character at the current index
996 private char internalSetIndex(int position) {
997 currentIndex = position;
998 if (position < currentRunStart || position >= currentRunLimit) {
1001 if (currentIndex == endIndex) {
1004 return charAt(position);
1008 // update the information about the current run
1009 private void updateRunInfo() {
1010 if (currentIndex == endIndex) {
1011 currentRunStart = currentRunLimit = endIndex;
1012 currentRunIndex = -1;
1014 synchronized (AttributedString.this) {
1016 while (runIndex < runCount - 1 && runStarts[runIndex + 1] <= currentIndex)
1018 currentRunIndex = runIndex;
1019 if (runIndex >= 0) {
1020 currentRunStart = runStarts[runIndex];
1021 if (currentRunStart < beginIndex)
1022 currentRunStart = beginIndex;
1025 currentRunStart = beginIndex;
1027 if (runIndex < runCount - 1) {
1028 currentRunLimit = runStarts[runIndex + 1];
1029 if (currentRunLimit > endIndex)
1030 currentRunLimit = endIndex;
1033 currentRunLimit = endIndex;
1041 // the map class associated with this string class, giving access to the attributes of one run
1043 final private class AttributeMap extends AbstractMap<Attribute,Object> {
1049 AttributeMap(int runIndex, int beginIndex, int endIndex) {
1050 this.runIndex = runIndex;
1051 this.beginIndex = beginIndex;
1052 this.endIndex = endIndex;
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) {
1069 Map.Entry entry = new AttributeEntry(key, value);
1076 public Object get(Object key) {
1077 return AttributedString.this.getAttributeCheckRange((Attribute) key, runIndex, beginIndex, endIndex);
1082 class AttributeEntry implements Map.Entry {
1084 private Attribute key;
1085 private Object value;
1087 AttributeEntry(Attribute key, Object value) {
1092 public boolean equals(Object o) {
1093 if (!(o instanceof AttributeEntry)) {
1096 AttributeEntry other = (AttributeEntry) o;
1097 return other.key.equals(key) &&
1098 (value == null ? other.value == null : other.value.equals(value));
1101 public Object getKey() {
1105 public Object getValue() {
1109 public Object setValue(Object newValue) {
1110 throw new UnsupportedOperationException();
1113 public int hashCode() {
1114 return key.hashCode() ^ (value==null ? 0 : value.hashCode());
1117 public String toString() {
1118 return key.toString()+"="+value.toString();