blob: c25655b1ea10647b68ae4a7f12cd1012872c3227 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package java.text;
27
28import java.util.*;
29import 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
50public 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 */
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 */
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 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 */
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 */
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 */
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 */
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 */
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 */
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
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
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
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 */
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
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 */
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 */
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 */
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.
591 int length() {
592 return text.length();
593 }
594
595 private char charAt(int index) {
596 return text.charAt(index);
597 }
598
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
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
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
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 */
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 */
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 */
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
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
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
787 public int hashCode() {
788 return text.hashCode() ^ currentIndex ^ beginIndex ^ endIndex;
789 }
790
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
803 public char first() {
804 return internalSetIndex(beginIndex);
805 }
806
807 public char last() {
808 if (endIndex == beginIndex) {
809 return internalSetIndex(endIndex);
810 } else {
811 return internalSetIndex(endIndex - 1);
812 }
813 }
814
815 public char current() {
816 if (currentIndex == endIndex) {
817 return DONE;
818 } else {
819 return charAt(currentIndex);
820 }
821 }
822
823 public char next() {
824 if (currentIndex < endIndex) {
825 return internalSetIndex(currentIndex + 1);
826 }
827 else {
828 return DONE;
829 }
830 }
831
832 public char previous() {
833 if (currentIndex > beginIndex) {
834 return internalSetIndex(currentIndex - 1);
835 }
836 else {
837 return DONE;
838 }
839 }
840
841 public char setIndex(int position) {
842 if (position < beginIndex || position > endIndex)
843 throw new IllegalArgumentException("Invalid index");
844 return internalSetIndex(position);
845 }
846
847 public int getBeginIndex() {
848 return beginIndex;
849 }
850
851 public int getEndIndex() {
852 return endIndex;
853 }
854
855 public int getIndex() {
856 return currentIndex;
857 }
858
859 // AttributedCharacterIterator methods. See documentation in that interface.
860
861 public int getRunStart() {
862 return currentRunStart;
863 }
864
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
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
902 public int getRunLimit() {
903 return currentRunLimit;
904 }
905
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
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
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
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
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
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
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
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
1049 AttributeMap(int runIndex, int beginIndex, int endIndex) {
1050 this.runIndex = runIndex;
1051 this.beginIndex = beginIndex;
1052 this.endIndex = endIndex;
1053 }
1054
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
1076 public Object get(Object key) {
1077 return AttributedString.this.getAttributeCheckRange((Attribute) key, runIndex, beginIndex, endIndex);
1078 }
1079 }
1080}
1081
1082class AttributeEntry implements Map.Entry {
1083
1084 private Attribute key;
1085 private Object value;
1086
1087 AttributeEntry(Attribute key, Object value) {
1088 this.key = key;
1089 this.value = value;
1090 }
1091
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
1101 public Object getKey() {
1102 return key;
1103 }
1104
1105 public Object getValue() {
1106 return value;
1107 }
1108
1109 public Object setValue(Object newValue) {
1110 throw new UnsupportedOperationException();
1111 }
1112
1113 public int hashCode() {
1114 return key.hashCode() ^ (value==null ? 0 : value.hashCode());
1115 }
1116
1117 public String toString() {
1118 return key.toString()+"="+value.toString();
1119 }
1120}