home *** CD-ROM | disk | FTP | other *** search
/ Apple Developer Connection Student Program / ADC Tools Sampler CD Disk 3 1999.iso / Metrowerks CodeWarrior / Java Support / Java_Source / Java2 / src / java / text / AttributedString.java < prev    next >
Encoding:
Java Source  |  1999-05-28  |  35.3 KB  |  961 lines  |  [TEXT/CWIE]

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