KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > text > AttributedString


1 /*
2  * @(#)AttributedString.java 1.36 04/07/16
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package java.text;
9
10 import java.util.*;
11 import java.text.AttributedCharacterIterator.Attribute;
12
13 /**
14 * An AttributedString holds text and related attribute information. It
15 * may be used as the actual data storage in some cases where a text
16 * reader wants to access attributed text through the AttributedCharacterIterator
17 * interface.
18 *
19 * @see AttributedCharacterIterator
20 * @see Annotation
21 * @since 1.2
22 */

23
24 public class AttributedString {
25
26     // since there are no vectors of int, we have to use arrays.
27
// We allocate them in chunks of 10 elements so we don't have to allocate all the time.
28
private static final int ARRAY_SIZE_INCREMENT = 10;
29
30     // field holding the text
31
String JavaDoc text;
32     
33     // fields holding run attribute information
34
// run attributes are organized by run
35
int runArraySize; // current size of the arrays
36
int runCount; // actual number of runs, <= runArraySize
37
int runStarts[]; // start index for each run
38
Vector runAttributes[]; // vector of attribute keys for each run
39
Vector runAttributeValues[]; // parallel vector of attribute values for each run
40

41     /**
42      * Constructs an AttributedString instance with the given
43      * AttributedCharacterIterators.
44      *
45      * @param iterators AttributedCharacterIterators to construct
46      * AttributedString from.
47      * @throws NullPointerException if iterators is null
48      */

49     AttributedString(AttributedCharacterIterator JavaDoc[] iterators) {
50     if (iterators == null) {
51             throw new NullPointerException JavaDoc("Iterators must not be null");
52     }
53         if (iterators.length == 0) {
54             text = "";
55         }
56         else {
57             // Build the String contents
58
StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
59             for (int counter = 0; counter < iterators.length; counter++) {
60                 appendContents(buffer, iterators[counter]);
61             }
62
63             text = buffer.toString();
64
65             if (text.length() > 0) {
66                 // Determine the runs, creating a new run when the attributes
67
// differ.
68
int offset = 0;
69                 Map last = null;
70
71                 for (int counter = 0; counter < iterators.length; counter++) {
72                     AttributedCharacterIterator JavaDoc iterator = iterators[counter];
73                     int start = iterator.getBeginIndex();
74                     int end = iterator.getEndIndex();
75                     int index = start;
76
77                     while (index < end) {
78                         iterator.setIndex(index);
79
80                         Map attrs = iterator.getAttributes();
81
82                         if (mapsDiffer(last, attrs)) {
83                             setAttributes(attrs, index - start + offset);
84                         }
85                         last = attrs;
86                         index = iterator.getRunLimit();
87                     }
88                     offset += (end - start);
89                 }
90             }
91         }
92     }
93
94     /**
95      * Constructs an AttributedString instance with the given text.
96      * @param text The text for this attributed string.
97      */

98     public AttributedString(String JavaDoc text) {
99         if (text == null) {
100             throw new NullPointerException JavaDoc();
101         }
102         this.text = text;
103     }
104     
105     /**
106      * Constructs an AttributedString instance with the given text and attributes.
107      * @param text The text for this attributed string.
108      * @param attributes The attributes that apply to the entire string.
109      * @exception IllegalArgumentException if the text has length 0
110      * and the attributes parameter is not an empty Map (attributes
111      * cannot be applied to a 0-length range).
112      */

113     public AttributedString(String JavaDoc text,
114                 Map<? extends Attribute, ?> attributes)
115     {
116         if (text == null || attributes == null) {
117             throw new NullPointerException JavaDoc();
118         }
119         this.text = text;
120         
121         if (text.length() == 0) {
122         if (attributes.isEmpty())
123         return;
124             throw new IllegalArgumentException JavaDoc("Can't add attribute to 0-length text");
125         }
126         
127         int attributeCount = attributes.size();
128         if (attributeCount > 0) {
129             createRunAttributeDataVectors();
130             Vector newRunAttributes = new Vector(attributeCount);
131             Vector newRunAttributeValues = new Vector(attributeCount);
132             runAttributes[0] = newRunAttributes;
133             runAttributeValues[0] = newRunAttributeValues;
134             Iterator iterator = attributes.entrySet().iterator();
135             while (iterator.hasNext()) {
136                 Map.Entry entry = (Map.Entry) iterator.next();
137                 newRunAttributes.addElement(entry.getKey());
138                 newRunAttributeValues.addElement(entry.getValue());
139             }
140         }
141     }
142     
143     /**
144      * Constructs an AttributedString instance with the given attributed
145      * text represented by AttributedCharacterIterator.
146      * @param text The text for this attributed string.
147      */

148     public AttributedString(AttributedCharacterIterator JavaDoc text) {
149     // If performance is critical, this constructor should be
150
// implemented here rather than invoking the constructor for a
151
// subrange. We can avoid some range checking in the loops.
152
this(text, text.getBeginIndex(), text.getEndIndex(), null);
153     }
154
155     /**
156      * Constructs an AttributedString instance with the subrange of
157      * the given attributed text represented by
158      * AttributedCharacterIterator. If the given range produces an
159      * empty text, all attributes will be discarded. Note that any
160      * attributes wrapped by an Annotation object are discarded for a
161      * subrange of the original attribute range.
162      *
163      * @param text The text for this attributed string.
164      * @param beginIndex Index of the first character of the range.
165      * @param endIndex Index of the character following the last character
166      * of the range.
167      * @exception IllegalArgumentException if the subrange given by
168      * beginIndex and endIndex is out of the text range.
169      * @see java.text.Annotation
170      */

171     public AttributedString(AttributedCharacterIterator JavaDoc text,
172                 int beginIndex,
173                 int endIndex) {
174     this(text, beginIndex, endIndex, null);
175     }
176
177     /**
178      * Constructs an AttributedString instance with the subrange of
179      * the given attributed text represented by
180      * AttributedCharacterIterator. Only attributes that match the
181      * given attributes will be incorporated into the instance. If the
182      * given range produces an empty text, all attributes will be
183      * discarded. Note that any attributes wrapped by an Annotation
184      * object are discarded for a subrange of the original attribute
185      * range.
186      *
187      * @param text The text for this attributed string.
188      * @param beginIndex Index of the first character of the range.
189      * @param endIndex Index of the character following the last character
190      * of the range.
191      * @param attributes Specifies attributes to be extracted
192      * from the text. If null is specified, all available attributes will
193      * be used.
194      * @exception IllegalArgumentException if the subrange given by
195      * beginIndex and endIndex is out of the text range.
196      * @see java.text.Annotation
197      */

198     public AttributedString(AttributedCharacterIterator JavaDoc text,
199                 int beginIndex,
200                 int endIndex,
201                 Attribute[] attributes) {
202         if (text == null) {
203             throw new NullPointerException JavaDoc();
204         }
205
206     // Validate the given subrange
207
int textBeginIndex = text.getBeginIndex();
208     int textEndIndex = text.getEndIndex();
209     if (beginIndex < textBeginIndex || endIndex > textEndIndex || beginIndex > endIndex)
210         throw new IllegalArgumentException JavaDoc("Invalid substring range");
211
212     // Copy the given string
213
StringBuffer JavaDoc textBuffer = new StringBuffer JavaDoc();
214     text.setIndex(beginIndex);
215     for (char c = text.current(); text.getIndex() < endIndex; c = text.next())
216         textBuffer.append(c);
217     this.text = textBuffer.toString();
218
219     if (beginIndex == endIndex)
220         return;
221
222     // Select attribute keys to be taken care of
223
HashSet keys = new HashSet();
224     if (attributes == null) {
225         keys.addAll(text.getAllAttributeKeys());
226     } else {
227         for (int i = 0; i < attributes.length; i++)
228         keys.add(attributes[i]);
229         keys.retainAll(text.getAllAttributeKeys());
230     }
231     if (keys.isEmpty())
232         return;
233
234     // Get and set attribute runs for each attribute name. Need to
235
// scan from the top of the text so that we can discard any
236
// Annotation that is no longer applied to a subset text segment.
237
Iterator itr = keys.iterator();
238     while (itr.hasNext()) {
239         Attribute attributeKey = (Attribute)itr.next();
240         text.setIndex(textBeginIndex);
241         while (text.getIndex() < endIndex) {
242         int start = text.getRunStart(attributeKey);
243         int limit = text.getRunLimit(attributeKey);
244         Object JavaDoc value = text.getAttribute(attributeKey);
245
246         if (value != null) {
247             if (value instanceof Annotation JavaDoc) {
248             if (start >= beginIndex && limit <= endIndex) {
249                 addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex);
250             } else {
251                 if (limit > endIndex)
252                 break;
253             }
254             } else {
255             // if the run is beyond the given (subset) range, we
256
// don't need to process further.
257
if (start >= endIndex)
258                 break;
259             if (limit > beginIndex) {
260                 // attribute is applied to any subrange
261
if (start < beginIndex)
262                 start = beginIndex;
263                 if (limit > endIndex)
264                 limit = endIndex;
265                 if (start != limit) {
266                 addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex);
267                 }
268             }
269             }
270         }
271         text.setIndex(limit);
272         }
273     }
274     }
275
276     /**
277      * Adds an attribute to the entire string.
278      * @param attribute the attribute key
279      * @param value the value of the attribute; may be null
280      * @exception IllegalArgumentException if the AttributedString has length 0
281      * (attributes cannot be applied to a 0-length range).
282      */

283     public void addAttribute(Attribute attribute, Object JavaDoc value) {
284         
285         if (attribute == null) {
286             throw new NullPointerException JavaDoc();
287         }
288
289         int len = length();
290         if (len == 0) {
291             throw new IllegalArgumentException JavaDoc("Can't add attribute to 0-length text");
292         }
293         
294         addAttributeImpl(attribute, value, 0, len);
295     }
296
297     /**
298      * Adds an attribute to a subrange of the string.
299      * @param attribute the attribute key
300      * @param value The value of the attribute. May be null.
301      * @param beginIndex Index of the first character of the range.
302      * @param endIndex Index of the character following the last character of the range.
303      * @exception IllegalArgumentException if beginIndex is less then 0, endIndex is
304      * greater than the length of the string, or beginIndex and endIndex together don't
305      * define a non-empty subrange of the string.
306      */

307     public void addAttribute(Attribute attribute, Object JavaDoc value,
308             int beginIndex, int endIndex) {
309         
310         if (attribute == null) {
311             throw new NullPointerException JavaDoc();
312         }
313
314         if (beginIndex < 0 || endIndex > length() || beginIndex >= endIndex) {
315             throw new IllegalArgumentException JavaDoc("Invalid substring range");
316         }
317         
318         addAttributeImpl(attribute, value, beginIndex, endIndex);
319     }
320     
321     /**
322      * Adds a set of attributes to a subrange of the string.
323      * @param attributes The attributes to be added to the string.
324      * @param beginIndex Index of the first character of the range.
325      * @param endIndex Index of the character following the last
326      * character of the range.
327      * @exception IllegalArgumentException if beginIndex is less then
328      * 0, endIndex is greater than the length of the string, or
329      * beginIndex and endIndex together don't define a non-empty
330      * subrange of the string and the attributes parameter is not an
331      * empty Map.
332      */

333     public void addAttributes(Map<? extends Attribute, ?> attributes,
334                   int beginIndex, int endIndex)
335     {
336         if (attributes == null) {
337             throw new NullPointerException JavaDoc();
338         }
339
340         if (beginIndex < 0 || endIndex > length() || beginIndex > endIndex) {
341             throw new IllegalArgumentException JavaDoc("Invalid substring range");
342         }
343     if (beginIndex == endIndex) {
344         if (attributes.isEmpty())
345         return;
346             throw new IllegalArgumentException JavaDoc("Can't add attribute to 0-length text");
347         }
348
349         // make sure we have run attribute data vectors
350
if (runCount == 0) {
351             createRunAttributeDataVectors();
352         }
353         
354         // break up runs if necessary
355
int beginRunIndex = ensureRunBreak(beginIndex);
356         int endRunIndex = ensureRunBreak(endIndex);
357         
358         Iterator iterator = attributes.entrySet().iterator();
359         while (iterator.hasNext()) {
360             Map.Entry entry = (Map.Entry) iterator.next();
361             addAttributeRunData((Attribute) entry.getKey(), entry.getValue(), beginRunIndex, endRunIndex);
362         }
363     }
364
365     private synchronized void addAttributeImpl(Attribute attribute, Object JavaDoc value,
366             int beginIndex, int endIndex) {
367         
368         // make sure we have run attribute data vectors
369
if (runCount == 0) {
370             createRunAttributeDataVectors();
371         }
372         
373         // break up runs if necessary
374
int beginRunIndex = ensureRunBreak(beginIndex);
375         int endRunIndex = ensureRunBreak(endIndex);
376         
377         addAttributeRunData(attribute, value, beginRunIndex, endRunIndex);
378     }
379     
380     private final void createRunAttributeDataVectors() {
381         // use temporary variables so things remain consistent in case of an exception
382
int newRunStarts[] = new int[ARRAY_SIZE_INCREMENT];
383         Vector newRunAttributes[] = new Vector[ARRAY_SIZE_INCREMENT];
384         Vector newRunAttributeValues[] = new Vector[ARRAY_SIZE_INCREMENT];
385         runStarts = newRunStarts;
386         runAttributes = newRunAttributes;
387         runAttributeValues = newRunAttributeValues;
388         runArraySize = ARRAY_SIZE_INCREMENT;
389         runCount = 1; // assume initial run starting at index 0
390
}
391
392     // ensure there's a run break at offset, return the index of the run
393
private final int ensureRunBreak(int offset) {
394         return ensureRunBreak(offset, true);
395     }
396
397     /**
398      * Ensures there is a run break at offset, returning the index of
399      * the run. If this results in splitting a run, two things can happen:
400      * <ul>
401      * <li>If copyAttrs is true, the attributes from the existing run
402      * will be placed in both of the newly created runs.
403      * <li>If copyAttrs is false, the attributes from the existing run
404      * will NOT be copied to the run to the right (>= offset) of the break,
405      * but will exist on the run to the left (< offset).
406      * </ul>
407      */

408     private final int ensureRunBreak(int offset, boolean copyAttrs) {
409         if (offset == length()) {
410             return runCount;
411         }
412
413         // search for the run index where this offset should be
414
int runIndex = 0;
415         while (runIndex < runCount && runStarts[runIndex] < offset) {
416             runIndex++;
417         }
418
419         // if the offset is at a run start already, we're done
420
if (runIndex < runCount && runStarts[runIndex] == offset) {
421             return runIndex;
422         }
423         
424         // we'll have to break up a run
425
// first, make sure we have enough space in our arrays
426
if (runCount == runArraySize) {
427             int newArraySize = runArraySize + ARRAY_SIZE_INCREMENT;
428             int newRunStarts[] = new int[newArraySize];
429             Vector newRunAttributes[] = new Vector[newArraySize];
430             Vector newRunAttributeValues[] = new Vector[newArraySize];
431             for (int i = 0; i < runArraySize; i++) {
432                 newRunStarts[i] = runStarts[i];
433                 newRunAttributes[i] = runAttributes[i];
434                 newRunAttributeValues[i] = runAttributeValues[i];
435             }
436             runStarts = newRunStarts;
437             runAttributes = newRunAttributes;
438             runAttributeValues = newRunAttributeValues;
439             runArraySize = newArraySize;
440         }
441         
442         // make copies of the attribute information of the old run that the new one used to be part of
443
// use temporary variables so things remain consistent in case of an exception
444
Vector newRunAttributes = null;
445         Vector newRunAttributeValues = null;
446
447         if (copyAttrs) {
448             Vector oldRunAttributes = runAttributes[runIndex - 1];
449             Vector oldRunAttributeValues = runAttributeValues[runIndex - 1];
450             if (oldRunAttributes != null) {
451                 newRunAttributes = (Vector) oldRunAttributes.clone();
452             }
453             if (oldRunAttributeValues != null) {
454                 newRunAttributeValues = (Vector) oldRunAttributeValues.clone();
455             }
456         }
457         
458         // now actually break up the run
459
runCount++;
460         for (int i = runCount - 1; i > runIndex; i--) {
461             runStarts[i] = runStarts[i - 1];
462             runAttributes[i] = runAttributes[i - 1];
463             runAttributeValues[i] = runAttributeValues[i - 1];
464         }
465         runStarts[runIndex] = offset;
466         runAttributes[runIndex] = newRunAttributes;
467         runAttributeValues[runIndex] = newRunAttributeValues;
468
469         return runIndex;
470     }
471
472     // add the attribute attribute/value to all runs where beginRunIndex <= runIndex < endRunIndex
473
private void addAttributeRunData(Attribute attribute, Object JavaDoc value,
474             int beginRunIndex, int endRunIndex) {
475
476         for (int i = beginRunIndex; i < endRunIndex; i++) {
477             int keyValueIndex = -1; // index of key and value in our vectors; assume we don't have an entry yet
478
if (runAttributes[i] == null) {
479                 Vector newRunAttributes = new Vector();
480                 Vector newRunAttributeValues = new Vector();
481                 runAttributes[i] = newRunAttributes;
482                 runAttributeValues[i] = newRunAttributeValues;
483             } else {
484                 // check whether we have an entry already
485
keyValueIndex = runAttributes[i].indexOf(attribute);
486             }
487
488             if (keyValueIndex == -1) {
489                 // create new entry
490
int oldSize = runAttributes[i].size();
491                 runAttributes[i].addElement(attribute);
492                 try {
493                     runAttributeValues[i].addElement(value);
494                 }
495                 catch (Exception JavaDoc e) {
496                     runAttributes[i].setSize(oldSize);
497                     runAttributeValues[i].setSize(oldSize);
498                 }
499             } else {
500                 // update existing entry
501
runAttributeValues[i].set(keyValueIndex, value);
502             }
503         }
504     }
505
506     /**
507      * Creates an AttributedCharacterIterator instance that provides access to the entire contents of
508      * this string.
509      *
510      * @return An iterator providing access to the text and its attributes.
511      */

512     public AttributedCharacterIterator JavaDoc getIterator() {
513         return getIterator(null, 0, length());
514     }
515
516     /**
517      * Creates an AttributedCharacterIterator instance that provides access to
518      * selected contents of this string.
519      * Information about attributes not listed in attributes that the
520      * implementor may have need not be made accessible through the iterator.
521      * If the list is null, all available attribute information should be made
522      * accessible.
523      *
524      * @param attributes a list of attributes that the client is interested in
525      * @return an iterator providing access to the text and its attributes
526      */

527     public AttributedCharacterIterator JavaDoc getIterator(Attribute[] attributes) {
528         return getIterator(attributes, 0, length());
529     }
530
531     /**
532      * Creates an AttributedCharacterIterator instance that provides access to
533      * selected contents of this string.
534      * Information about attributes not listed in attributes that the
535      * implementor may have need not be made accessible through the iterator.
536      * If the list is null, all available attribute information should be made
537      * accessible.
538      *
539      * @param attributes a list of attributes that the client is interested in
540      * @param beginIndex the index of the first character
541      * @param endIndex the index of the character following the last character
542      * @return an iterator providing access to the text and its attributes
543      * @exception IllegalArgumentException if beginIndex is less then 0,
544      * endIndex is greater than the length of the string, or beginIndex is
545      * greater than endIndex.
546      */

547     public AttributedCharacterIterator JavaDoc getIterator(Attribute[] attributes, int beginIndex, int endIndex) {
548         return new AttributedStringIterator(attributes, beginIndex, endIndex);
549     }
550
551     // all (with the exception of length) reading operations are private,
552
// since AttributedString instances are accessed through iterators.
553

554     // length is package private so that CharacterIteratorFieldDelegate can
555
// access it without creating an AttributedCharacterIterator.
556
int length() {
557         return text.length();
558     }
559     
560     private char charAt(int index) {
561         return text.charAt(index);
562     }
563     
564     private synchronized Object JavaDoc getAttribute(Attribute attribute, int runIndex) {
565         Vector currentRunAttributes = runAttributes[runIndex];
566         Vector currentRunAttributeValues = runAttributeValues[runIndex];
567         if (currentRunAttributes == null) {
568             return null;
569         }
570         int attributeIndex = currentRunAttributes.indexOf(attribute);
571         if (attributeIndex != -1) {
572             return currentRunAttributeValues.elementAt(attributeIndex);
573         }
574         else {
575             return null;
576         }
577     }
578
579     // gets an attribute value, but returns an annotation only if it's range does not extend outside the range beginIndex..endIndex
580
private Object JavaDoc getAttributeCheckRange(Attribute attribute, int runIndex, int beginIndex, int endIndex) {
581         Object JavaDoc value = getAttribute(attribute, runIndex);
582         if (value instanceof Annotation JavaDoc) {
583             // need to check whether the annotation's range extends outside the iterator's range
584
if (beginIndex > 0) {
585                 int currIndex = runIndex;
586                 int runStart = runStarts[currIndex];
587                 while (runStart >= beginIndex &&
588                         valuesMatch(value, getAttribute(attribute, currIndex - 1))) {
589                     currIndex--;
590                     runStart = runStarts[currIndex];
591                 }
592                 if (runStart < beginIndex) {
593                     // annotation's range starts before iterator's range
594
return null;
595                 }
596             }
597             int textLength = length();
598             if (endIndex < textLength) {
599                 int currIndex = runIndex;
600                 int runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength;
601                 while (runLimit <= endIndex &&
602                         valuesMatch(value, getAttribute(attribute, currIndex + 1))) {
603                     currIndex++;
604                     runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength;
605                 }
606                 if (runLimit > endIndex) {
607                     // annotation's range ends after iterator's range
608
return null;
609                 }
610             }
611             // annotation's range is subrange of iterator's range,
612
// so we can return the value
613
}
614         return value;
615     }
616
617     // returns whether all specified attributes have equal values in the runs with the given indices
618
private boolean attributeValuesMatch(Set attributes, int runIndex1, int runIndex2) {
619         Iterator iterator = attributes.iterator();
620         while (iterator.hasNext()) {
621             Attribute key = (Attribute) iterator.next();
622            if (!valuesMatch(getAttribute(key, runIndex1), getAttribute(key, runIndex2))) {
623                 return false;
624             }
625         }
626         return true;
627     }
628
629     // returns whether the two objects are either both null or equal
630
private final static boolean valuesMatch(Object JavaDoc value1, Object JavaDoc value2) {
631         if (value1 == null) {
632             return value2 == null;
633         } else {
634             return value1.equals(value2);
635         }
636     }
637
638     /**
639      * Appends the contents of the CharacterIterator iterator into the
640      * StringBuffer buf.
641      */

642     private final void appendContents(StringBuffer JavaDoc buf,
643                                       CharacterIterator JavaDoc iterator) {
644         int index = iterator.getBeginIndex();
645         int end = iterator.getEndIndex();
646
647         while (index < end) {
648             iterator.setIndex(index++);
649             buf.append(iterator.current());
650         }
651     }
652
653     /**
654      * Sets the attributes for the range from offset to the the next run break
655      * (typically the end of the text) to the ones specified in attrs.
656      * This is only meant to be called from the constructor!
657      */

658     private void setAttributes(Map attrs, int offset) {
659         if (runCount == 0) {
660             createRunAttributeDataVectors();
661         }
662
663         int index = ensureRunBreak(offset, false);
664         int size;
665
666         if (attrs != null && (size = attrs.size()) > 0) {
667             Vector runAttrs = new Vector(size);
668             Vector runValues = new Vector(size);
669             Iterator iterator = attrs.entrySet().iterator();
670
671             while (iterator.hasNext()) {
672                 Map.Entry entry = (Map.Entry)iterator.next();
673
674                 runAttrs.add(entry.getKey());
675                 runValues.add(entry.getValue());
676             }
677             runAttributes[index] = runAttrs;
678             runAttributeValues[index] = runValues;
679         }
680     }
681
682     /**
683      * Returns true if the attributes specified in last and attrs differ.
684      */

685     private static boolean mapsDiffer(Map last, Map attrs) {
686         if (last == null) {
687             return (attrs != null && attrs.size() > 0);
688         }
689         return (!last.equals(attrs));
690     }
691
692
693     // the iterator class associated with this string class
694

695     final private class AttributedStringIterator implements AttributedCharacterIterator JavaDoc {
696         
697         // note on synchronization:
698
// we don't synchronize on the iterator, assuming that an iterator is only used in one thread.
699
// we do synchronize access to the AttributedString however, since it's more likely to be shared between threads.
700

701         // start and end index for our iteration
702
private int beginIndex;
703         private int endIndex;
704         
705         // attributes that our client is interested in
706
private Attribute[] relevantAttributes;
707
708         // the current index for our iteration
709
// invariant: beginIndex <= currentIndex <= endIndex
710
private int currentIndex;
711
712         // information about the run that includes currentIndex
713
private int currentRunIndex;
714         private int currentRunStart;
715         private int currentRunLimit;
716         
717         // constructor
718
AttributedStringIterator(Attribute[] attributes, int beginIndex, int endIndex) {
719         
720             if (beginIndex < 0 || beginIndex > endIndex || endIndex > length()) {
721                 throw new IllegalArgumentException JavaDoc("Invalid substring range");
722             }
723             
724             this.beginIndex = beginIndex;
725             this.endIndex = endIndex;
726             this.currentIndex = beginIndex;
727             updateRunInfo();
728             if (attributes != null) {
729                 relevantAttributes = (Attribute[]) attributes.clone();
730             }
731         }
732         
733         // Object methods. See documentation in that class.
734

735         public boolean equals(Object JavaDoc obj) {
736             if (this == obj) {
737                 return true;
738             }
739             if (!(obj instanceof AttributedStringIterator)) {
740                 return false;
741             }
742
743             AttributedStringIterator that = (AttributedStringIterator) obj;
744
745             if (AttributedString.this != that.getString())
746                 return false;
747             if (currentIndex != that.currentIndex || beginIndex != that.beginIndex || endIndex != that.endIndex)
748                 return false;
749             return true;
750         }
751
752         public int hashCode() {
753             return text.hashCode() ^ currentIndex ^ beginIndex ^ endIndex;
754         }
755
756         public Object JavaDoc clone() {
757             try {
758                 AttributedStringIterator other = (AttributedStringIterator) super.clone();
759                 return other;
760             }
761             catch (CloneNotSupportedException JavaDoc e) {
762                 throw new InternalError JavaDoc();
763             }
764         }
765         
766         // CharacterIterator methods. See documentation in that interface.
767

768         public char first() {
769             return internalSetIndex(beginIndex);
770         }
771         
772         public char last() {
773             if (endIndex == beginIndex) {
774                 return internalSetIndex(endIndex);
775             } else {
776                 return internalSetIndex(endIndex - 1);
777             }
778         }
779         
780         public char current() {
781             if (currentIndex == endIndex) {
782                 return DONE;
783             } else {
784                 return charAt(currentIndex);
785             }
786         }
787
788         public char next() {
789             if (currentIndex < endIndex) {
790                 return internalSetIndex(currentIndex + 1);
791             }
792             else {
793                 return DONE;
794             }
795         }
796
797         public char previous() {
798             if (currentIndex > beginIndex) {
799                 return internalSetIndex(currentIndex - 1);
800             }
801             else {
802                 return DONE;
803             }
804         }
805
806         public char setIndex(int position) {
807             if (position < beginIndex || position > endIndex)
808                 throw new IllegalArgumentException JavaDoc("Invalid index");
809             return internalSetIndex(position);
810         }
811
812         public int getBeginIndex() {
813             return beginIndex;
814         }
815
816         public int getEndIndex() {
817             return endIndex;
818         }
819
820         public int getIndex() {
821             return currentIndex;
822         }
823
824         // AttributedCharacterIterator methods. See documentation in that interface.
825

826         public int getRunStart() {
827             return currentRunStart;
828         }
829         
830         public int getRunStart(Attribute attribute) {
831             if (currentRunStart == beginIndex || currentRunIndex == -1) {
832                 return currentRunStart;
833             } else {
834                 Object JavaDoc value = getAttribute(attribute);
835                 int runStart = currentRunStart;
836                 int runIndex = currentRunIndex;
837                 while (runStart > beginIndex &&
838                         valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex - 1))) {
839                     runIndex--;
840                     runStart = runStarts[runIndex];
841                 }
842                 if (runStart < beginIndex) {
843                     runStart = beginIndex;
844                 }
845                 return runStart;
846             }
847         }
848
849         public int getRunStart(Set<? extends Attribute> attributes) {
850             if (currentRunStart == beginIndex || currentRunIndex == -1) {
851                 return currentRunStart;
852             } else {
853                 int runStart = currentRunStart;
854                 int runIndex = currentRunIndex;
855                 while (runStart > beginIndex &&
856                         AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex - 1)) {
857                     runIndex--;
858                     runStart = runStarts[runIndex];
859                 }
860                 if (runStart < beginIndex) {
861                     runStart = beginIndex;
862                 }
863                 return runStart;
864             }
865         }
866
867         public int getRunLimit() {
868             return currentRunLimit;
869         }
870         
871         public int getRunLimit(Attribute attribute) {
872             if (currentRunLimit == endIndex || currentRunIndex == -1) {
873                 return currentRunLimit;
874             } else {
875                 Object JavaDoc value = getAttribute(attribute);
876                 int runLimit = currentRunLimit;
877                 int runIndex = currentRunIndex;
878                 while (runLimit < endIndex &&
879                         valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex + 1))) {
880                     runIndex++;
881                     runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex;
882                 }
883                 if (runLimit > endIndex) {
884                     runLimit = endIndex;
885                 }
886                 return runLimit;
887             }
888         }
889         
890         public int getRunLimit(Set<? extends Attribute> attributes) {
891             if (currentRunLimit == endIndex || currentRunIndex == -1) {
892                 return currentRunLimit;
893             } else {
894                 int runLimit = currentRunLimit;
895                 int runIndex = currentRunIndex;
896                 while (runLimit < endIndex &&
897                         AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex + 1)) {
898                     runIndex++;
899                     runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex;
900                 }
901                 if (runLimit > endIndex) {
902                     runLimit = endIndex;
903                 }
904                 return runLimit;
905             }
906         }
907         
908         public Map<Attribute,Object JavaDoc> getAttributes() {
909             if (runAttributes == null || currentRunIndex == -1 || runAttributes[currentRunIndex] == null) {
910                 // ??? would be nice to return null, but current spec doesn't allow it
911
// returning Hashtable saves AttributeMap from dealing with emptiness
912
return new Hashtable();
913             }
914             return new AttributeMap(currentRunIndex, beginIndex, endIndex);
915         }
916         
917         public Set<Attribute> getAllAttributeKeys() {
918             // ??? This should screen out attribute keys that aren't relevant to the client
919
if (runAttributes == null) {
920                 // ??? would be nice to return null, but current spec doesn't allow it
921
// returning HashSet saves us from dealing with emptiness
922
return new HashSet();
923             }
924             synchronized (AttributedString.this) {
925                 // ??? should try to create this only once, then update if necessary,
926
// and give callers read-only view
927
Set keys = new HashSet();
928                 int i = 0;
929                 while (i < runCount) {
930                     if (runStarts[i] < endIndex && (i == runCount - 1 || runStarts[i + 1] > beginIndex)) {
931                         Vector currentRunAttributes = runAttributes[i];
932                         if (currentRunAttributes != null) {
933                             int j = currentRunAttributes.size();
934                             while (j-- > 0) {
935                                 keys.add(currentRunAttributes.get(j));
936                             }
937                         }
938                     }
939                     i++;
940                 }
941                 return keys;
942             }
943         }
944         
945         public Object JavaDoc getAttribute(Attribute attribute) {
946             int runIndex = currentRunIndex;
947             if (runIndex < 0) {
948                 return null;
949             }
950             return AttributedString.this.getAttributeCheckRange(attribute, runIndex, beginIndex, endIndex);
951         }
952         
953         // internally used methods
954

955         private AttributedString JavaDoc getString() {
956             return AttributedString.this;
957         }
958         
959         // set the current index, update information about the current run if necessary,
960
// return the character at the current index
961
private char internalSetIndex(int position) {
962             currentIndex = position;
963             if (position < currentRunStart || position >= currentRunLimit) {
964                 updateRunInfo();
965             }
966             if (currentIndex == endIndex) {
967                 return DONE;
968             } else {
969                 return charAt(position);
970             }
971         }
972
973         // update the information about the current run
974
private void updateRunInfo() {
975             if (currentIndex == endIndex) {
976                 currentRunStart = currentRunLimit = endIndex;
977                 currentRunIndex = -1;
978             } else {
979                 synchronized (AttributedString.this) {
980                     int runIndex = -1;
981                     while (runIndex < runCount - 1 && runStarts[runIndex + 1] <= currentIndex)
982                         runIndex++;
983                     currentRunIndex = runIndex;
984                     if (runIndex >= 0) {
985                         currentRunStart = runStarts[runIndex];
986                         if (currentRunStart < beginIndex)
987                             currentRunStart = beginIndex;
988                     }
989                     else {
990                         currentRunStart = beginIndex;
991                     }
992                     if (runIndex < runCount - 1) {
993                         currentRunLimit = runStarts[runIndex + 1];
994                         if (currentRunLimit > endIndex)
995                             currentRunLimit = endIndex;
996                     }
997                     else {
998                         currentRunLimit = endIndex;
999                     }
1000                }
1001            }
1002        }
1003
1004    }
1005
1006    // the map class associated with this string class, giving access to the attributes of one run
1007

1008    final private class AttributeMap extends AbstractMap<Attribute,Object JavaDoc> {
1009    
1010        int runIndex;
1011        int beginIndex;
1012        int endIndex;
1013
1014        AttributeMap(int runIndex, int beginIndex, int endIndex) {
1015            this.runIndex = runIndex;
1016            this.beginIndex = beginIndex;
1017            this.endIndex = endIndex;
1018        }
1019
1020        public Set entrySet() {
1021            HashSet set = new HashSet();
1022            synchronized (AttributedString.this) {
1023                int size = runAttributes[runIndex].size();
1024                for (int i = 0; i < size; i++) {
1025                    Attribute key = (Attribute) runAttributes[runIndex].get(i);
1026                    Object JavaDoc value = runAttributeValues[runIndex].get(i);
1027                    if (value instanceof Annotation JavaDoc) {
1028                        value = AttributedString.this.getAttributeCheckRange(key,
1029                                 runIndex, beginIndex, endIndex);
1030                        if (value == null) {
1031                            continue;
1032                        }
1033                    }
1034                    Map.Entry entry = new AttributeEntry(key, value);
1035                    set.add(entry);
1036                }
1037            }
1038            return set;
1039        }
1040        
1041        public Object JavaDoc get(Object JavaDoc key) {
1042            return AttributedString.this.getAttributeCheckRange((Attribute) key, runIndex, beginIndex, endIndex);
1043        }
1044    }
1045}
1046
1047class AttributeEntry implements Map.Entry {
1048
1049    private Attribute key;
1050    private Object JavaDoc value;
1051
1052    AttributeEntry(Attribute key, Object JavaDoc value) {
1053        this.key = key;
1054        this.value = value;
1055    }
1056    
1057    public boolean equals(Object JavaDoc o) {
1058        if (!(o instanceof AttributeEntry)) {
1059            return false;
1060        }
1061        AttributeEntry other = (AttributeEntry) o;
1062        return other.key.equals(key) &&
1063            (value == null ? other.value == null : other.value.equals(value));
1064    }
1065    
1066    public Object JavaDoc getKey() {
1067        return key;
1068    }
1069    
1070    public Object JavaDoc getValue() {
1071        return value;
1072    }
1073    
1074    public Object JavaDoc setValue(Object JavaDoc newValue) {
1075        throw new UnsupportedOperationException JavaDoc();
1076    }
1077    
1078    public int hashCode() {
1079        return key.hashCode() ^ (value==null ? 0 : value.hashCode());
1080    }
1081
1082    public String JavaDoc toString() {
1083        return key.toString()+"="+value.toString();
1084    }
1085}
1086
Popular Tags