AttributedString.java revision 2362
0N/A/*
0N/A * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
0N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
0N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
873N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
0N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
0N/A * or visit www.oracle.com if you need additional information or have any
0N/A * questions.
0N/A */
3215N/A
0N/Apackage java.text;
0N/A
0N/Aimport java.util.*;
0N/Aimport java.text.AttributedCharacterIterator.Attribute;
0N/A
0N/A/**
0N/A * An AttributedString holds text and related attribute information. It
0N/A * may be used as the actual data storage in some cases where a text
3012N/A * reader wants to access attributed text through the AttributedCharacterIterator
3012N/A * interface.
3012N/A *
1083N/A * <p>
1083N/A * An attribute is a key/value pair, identified by the key. No two
1083N/A * attributes on a given character can have the same key.
1083N/A *
1083N/A * <p>The values for an attribute are immutable, or must not be mutated
1083N/A * by clients or storage. They are always passed by reference, and not
1083N/A * cloned.
1083N/A *
3090N/A * @see AttributedCharacterIterator
3090N/A * @see Annotation
3214N/A * @since 1.2
1083N/A */
1083N/A
1083N/Apublic class AttributedString {
1083N/A
233N/A // since there are no vectors of int, we have to use arrays.
712N/A // We allocate them in chunks of 10 elements so we don't have to allocate all the time.
1177N/A private static final int ARRAY_SIZE_INCREMENT = 10;
564N/A
0N/A // field holding the text
0N/A String text;
0N/A
0N/A // fields holding run attribute information
712N/A // run attributes are organized by run
0N/A int runArraySize; // current size of the arrays
0N/A int runCount; // actual number of runs, <= runArraySize
0N/A int runStarts[]; // start index for each run
0N/A Vector runAttributes[]; // vector of attribute keys for each run
0N/A Vector runAttributeValues[]; // parallel vector of attribute values for each run
233N/A
233N/A /**
0N/A * Constructs an AttributedString instance with the given
0N/A * AttributedCharacterIterators.
0N/A *
0N/A * @param iterators AttributedCharacterIterators to construct
0N/A * AttributedString from.
0N/A * @throws NullPointerException if iterators is null
0N/A */
0N/A AttributedString(AttributedCharacterIterator[] iterators) {
567N/A if (iterators == null) {
567N/A throw new NullPointerException("Iterators must not be null");
567N/A }
567N/A if (iterators.length == 0) {
2046N/A text = "";
2046N/A }
1210N/A else {
2033N/A // Build the String contents
712N/A StringBuffer buffer = new StringBuffer();
2033N/A for (int counter = 0; counter < iterators.length; counter++) {
2046N/A appendContents(buffer, iterators[counter]);
712N/A }
675N/A
675N/A text = buffer.toString();
712N/A
675N/A if (text.length() > 0) {
567N/A // Determine the runs, creating a new run when the attributes
0N/A // differ.
422N/A int offset = 0;
712N/A Map last = null;
422N/A
0N/A for (int counter = 0; counter < iterators.length; counter++) {
422N/A AttributedCharacterIterator iterator = iterators[counter];
0N/A int start = iterator.getBeginIndex();
422N/A int end = iterator.getEndIndex();
0N/A int index = start;
0N/A
0N/A while (index < end) {
0N/A iterator.setIndex(index);
902N/A
902N/A Map attrs = iterator.getAttributes();
902N/A
902N/A if (mapsDiffer(last, attrs)) {
0N/A setAttributes(attrs, index - start + offset);
0N/A }
0N/A last = attrs;
0N/A index = iterator.getRunLimit();
233N/A }
233N/A offset += (end - start);
233N/A }
233N/A }
730N/A }
730N/A }
730N/A
0N/A /**
729N/A * Constructs an AttributedString instance with the given text.
729N/A * @param text The text for this attributed string.
729N/A * @exception NullPointerException if <code>text</code> is null.
0N/A */
0N/A public AttributedString(String text) {
0N/A if (text == null) {
110N/A throw new NullPointerException();
110N/A }
121N/A this.text = text;
699N/A }
0N/A
0N/A /**
1008N/A * Constructs an AttributedString instance with the given text and attributes.
1008N/A * @param text The text for this attributed string.
1008N/A * @param attributes The attributes that apply to the entire string.
1008N/A * @exception NullPointerException if <code>text</code> or
3214N/A * <code>attributes</code> is null.
3214N/A * @exception IllegalArgumentException if the text has length 0
2086N/A * and the attributes parameter is not an empty Map (attributes
2086N/A * cannot be applied to a 0-length range).
2086N/A */
2086N/A public AttributedString(String text,
2086N/A Map<? extends Attribute, ?> attributes)
1177N/A {
1177N/A if (text == null || attributes == null) {
0N/A throw new NullPointerException();
0N/A }
0N/A this.text = text;
0N/A
0N/A if (text.length() == 0) {
0N/A if (attributes.isEmpty())
0N/A return;
0N/A throw new IllegalArgumentException("Can't add attribute to 0-length text");
0N/A }
1344N/A
1177N/A int attributeCount = attributes.size();
1177N/A if (attributeCount > 0) {
0N/A createRunAttributeDataVectors();
0N/A Vector newRunAttributes = new Vector(attributeCount);
0N/A Vector newRunAttributeValues = new Vector(attributeCount);
0N/A runAttributes[0] = newRunAttributes;
0N/A runAttributeValues[0] = newRunAttributeValues;
0N/A Iterator iterator = attributes.entrySet().iterator();
3090N/A while (iterator.hasNext()) {
1177N/A Map.Entry entry = (Map.Entry) iterator.next();
1177N/A newRunAttributes.addElement(entry.getKey());
0N/A newRunAttributeValues.addElement(entry.getValue());
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
1177N/A * Constructs an AttributedString instance with the given attributed
1177N/A * text represented by AttributedCharacterIterator.
0N/A * @param text The text for this attributed string.
0N/A * @exception NullPointerException if <code>text</code> is null.
0N/A */
0N/A public AttributedString(AttributedCharacterIterator text) {
0N/A // If performance is critical, this constructor should be
0N/A // implemented here rather than invoking the constructor for a
3090N/A // subrange. We can avoid some range checking in the loops.
1177N/A this(text, text.getBeginIndex(), text.getEndIndex(), null);
1177N/A }
0N/A
0N/A /**
2086N/A * Constructs an AttributedString instance with the subrange of
2086N/A * the given attributed text represented by
2086N/A * AttributedCharacterIterator. If the given range produces an
2086N/A * empty text, all attributes will be discarded. Note that any
2086N/A * attributes wrapped by an Annotation object are discarded for a
2086N/A * subrange of the original attribute range.
2086N/A *
2086N/A * @param text The text for this attributed string.
2086N/A * @param beginIndex Index of the first character of the range.
2086N/A * @param endIndex Index of the character following the last character
2086N/A * of the range.
2086N/A * @exception NullPointerException if <code>text</code> is null.
2086N/A * @exception IllegalArgumentException if the subrange given by
2086N/A * beginIndex and endIndex is out of the text range.
2086N/A * @see java.text.Annotation
2086N/A */
2086N/A public AttributedString(AttributedCharacterIterator text,
2086N/A int beginIndex,
2086N/A int endIndex) {
2086N/A this(text, beginIndex, endIndex, null);
2086N/A }
2086N/A
2086N/A /**
2086N/A * Constructs an AttributedString instance with the subrange of
2086N/A * the given attributed text represented by
2086N/A * AttributedCharacterIterator. Only attributes that match the
2086N/A * given attributes will be incorporated into the instance. If the
2086N/A * given range produces an empty text, all attributes will be
2086N/A * discarded. Note that any attributes wrapped by an Annotation
2086N/A * object are discarded for a subrange of the original attribute
2086N/A * range.
2086N/A *
2284N/A * @param text The text for this attributed string.
2284N/A * @param beginIndex Index of the first character of the range.
2284N/A * @param endIndex Index of the character following the last character
2086N/A * of the range.
2086N/A * @param attributes Specifies attributes to be extracted
2086N/A * from the text. If null is specified, all available attributes will
2086N/A * be used.
2086N/A * @exception NullPointerException if <code>text</code> is null.
2086N/A * @exception IllegalArgumentException if the subrange given by
2086N/A * beginIndex and endIndex is out of the text range.
2086N/A * @see java.text.Annotation
2086N/A */
2086N/A public AttributedString(AttributedCharacterIterator text,
2086N/A int beginIndex,
2086N/A int endIndex,
2086N/A Attribute[] attributes) {
2086N/A if (text == null) {
2086N/A throw new NullPointerException();
2086N/A }
2086N/A
2086N/A // Validate the given subrange
2086N/A int textBeginIndex = text.getBeginIndex();
2086N/A int textEndIndex = text.getEndIndex();
2086N/A if (beginIndex < textBeginIndex || endIndex > textEndIndex || beginIndex > endIndex)
2086N/A throw new IllegalArgumentException("Invalid substring range");
2086N/A
2086N/A // Copy the given string
2086N/A StringBuffer textBuffer = new StringBuffer();
2086N/A text.setIndex(beginIndex);
2086N/A for (char c = text.current(); text.getIndex() < endIndex; c = text.next())
2086N/A textBuffer.append(c);
2086N/A this.text = textBuffer.toString();
2086N/A
2086N/A if (beginIndex == endIndex)
2086N/A return;
2086N/A
2086N/A // Select attribute keys to be taken care of
2086N/A HashSet keys = new HashSet();
2086N/A if (attributes == null) {
3034N/A keys.addAll(text.getAllAttributeKeys());
3034N/A } else {
3034N/A for (int i = 0; i < attributes.length; i++)
2086N/A keys.add(attributes[i]);
0N/A keys.retainAll(text.getAllAttributeKeys());
0N/A }
0N/A if (keys.isEmpty())
2086N/A return;
0N/A
0N/A // Get and set attribute runs for each attribute name. Need to
2342N/A // scan from the top of the text so that we can discard any
1008N/A // Annotation that is no longer applied to a subset text segment.
1008N/A Iterator itr = keys.iterator();
1008N/A while (itr.hasNext()) {
1008N/A Attribute attributeKey = (Attribute)itr.next();
0N/A text.setIndex(textBeginIndex);
0N/A while (text.getIndex() < endIndex) {
0N/A int start = text.getRunStart(attributeKey);
1177N/A int limit = text.getRunLimit(attributeKey);
121N/A Object value = text.getAttribute(attributeKey);
1177N/A
1177N/A if (value != null) {
1177N/A if (value instanceof Annotation) {
0N/A if (start >= beginIndex && limit <= endIndex) {
0N/A addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex);
1273N/A } else {
1273N/A if (limit > endIndex)
1273N/A break;
1273N/A }
1273N/A } else {
1273N/A // if the run is beyond the given (subset) range, we
1273N/A // don't need to process further.
1273N/A if (start >= endIndex)
1273N/A break;
1273N/A if (limit > beginIndex) {
1273N/A // attribute is applied to any subrange
1273N/A if (start < beginIndex)
1273N/A start = beginIndex;
1273N/A if (limit > endIndex)
1273N/A limit = endIndex;
1273N/A if (start != limit) {
1273N/A addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex);
1273N/A }
1273N/A }
1273N/A }
1273N/A }
1273N/A text.setIndex(limit);
1273N/A }
1273N/A }
1273N/A }
1273N/A
1273N/A /**
1273N/A * Adds an attribute to the entire string.
1960N/A * @param attribute the attribute key
1273N/A * @param value the value of the attribute; may be null
1273N/A * @exception NullPointerException if <code>attribute</code> is null.
0N/A * @exception IllegalArgumentException if the AttributedString has length 0
121N/A * (attributes cannot be applied to a 0-length range).
1177N/A */
1177N/A public void addAttribute(Attribute attribute, Object value) {
1177N/A
1177N/A if (attribute == null) {
1177N/A throw new NullPointerException();
1559N/A }
1559N/A
1559N/A int len = length();
2556N/A if (len == 0) {
0N/A throw new IllegalArgumentException("Can't add attribute to 0-length text");
1234N/A }
1234N/A
0N/A addAttributeImpl(attribute, value, 0, len);
0N/A }
984N/A
0N/A /**
0N/A * Adds an attribute to a subrange of the string.
0N/A * @param attribute the attribute key
0N/A * @param value The value of the attribute. May be null.
0N/A * @param beginIndex Index of the first character of the range.
984N/A * @param endIndex Index of the character following the last character of the range.
984N/A * @exception NullPointerException if <code>attribute</code> is null.
984N/A * @exception IllegalArgumentException if beginIndex is less then 0, endIndex is
984N/A * greater than the length of the string, or beginIndex and endIndex together don't
902N/A * define a non-empty subrange of the string.
902N/A */
0N/A public void addAttribute(Attribute attribute, Object value,
0N/A int beginIndex, int endIndex) {
0N/A
0N/A if (attribute == null) {
0N/A throw new NullPointerException();
0N/A }
0N/A
0N/A if (beginIndex < 0 || endIndex > length() || beginIndex >= endIndex) {
0N/A throw new IllegalArgumentException("Invalid substring range");
0N/A }
0N/A
0N/A addAttributeImpl(attribute, value, beginIndex, endIndex);
0N/A }
0N/A
0N/A /**
0N/A * Adds a set of attributes to a subrange of the string.
0N/A * @param attributes The attributes to be added to the string.
0N/A * @param beginIndex Index of the first character of the range.
729N/A * @param endIndex Index of the character following the last
729N/A * character of the range.
729N/A * @exception NullPointerException if <code>attributes</code> is null.
729N/A * @exception IllegalArgumentException if beginIndex is less then
729N/A * 0, endIndex is greater than the length of the string, or
729N/A * beginIndex and endIndex together don't define a non-empty
729N/A * subrange of the string and the attributes parameter is not an
729N/A * empty Map.
729N/A */
729N/A public void addAttributes(Map<? extends Attribute, ?> attributes,
729N/A int beginIndex, int endIndex)
729N/A {
729N/A if (attributes == null) {
729N/A throw new NullPointerException();
729N/A }
729N/A
729N/A if (beginIndex < 0 || endIndex > length() || beginIndex > endIndex) {
729N/A throw new IllegalArgumentException("Invalid substring range");
729N/A }
729N/A if (beginIndex == endIndex) {
729N/A if (attributes.isEmpty())
0N/A return;
0N/A throw new IllegalArgumentException("Can't add attribute to 0-length text");
1344N/A }
1344N/A
1344N/A // make sure we have run attribute data vectors
1344N/A if (runCount == 0) {
1344N/A createRunAttributeDataVectors();
1344N/A }
1344N/A
1344N/A // break up runs if necessary
1344N/A int beginRunIndex = ensureRunBreak(beginIndex);
1344N/A int endRunIndex = ensureRunBreak(endIndex);
1344N/A
1344N/A Iterator iterator = attributes.entrySet().iterator();
1344N/A while (iterator.hasNext()) {
109N/A Map.Entry entry = (Map.Entry) iterator.next();
109N/A addAttributeRunData((Attribute) entry.getKey(), entry.getValue(), beginRunIndex, endRunIndex);
109N/A }
109N/A }
984N/A
1344N/A private synchronized void addAttributeImpl(Attribute attribute, Object value,
109N/A int beginIndex, int endIndex) {
1238N/A
109N/A // make sure we have run attribute data vectors
1344N/A if (runCount == 0) {
1344N/A createRunAttributeDataVectors();
1344N/A }
1344N/A
1344N/A // break up runs if necessary
1344N/A int beginRunIndex = ensureRunBreak(beginIndex);
110N/A int endRunIndex = ensureRunBreak(endIndex);
109N/A
109N/A addAttributeRunData(attribute, value, beginRunIndex, endRunIndex);
109N/A }
109N/A
109N/A private final void createRunAttributeDataVectors() {
109N/A // use temporary variables so things remain consistent in case of an exception
0N/A int newRunStarts[] = new int[ARRAY_SIZE_INCREMENT];
0N/A Vector newRunAttributes[] = new Vector[ARRAY_SIZE_INCREMENT];
0N/A Vector newRunAttributeValues[] = new Vector[ARRAY_SIZE_INCREMENT];
0N/A runStarts = newRunStarts;
0N/A runAttributes = newRunAttributes;
1344N/A runAttributeValues = newRunAttributeValues;
1344N/A runArraySize = ARRAY_SIZE_INCREMENT;
1344N/A runCount = 1; // assume initial run starting at index 0
1344N/A }
1344N/A
1344N/A // ensure there's a run break at offset, return the index of the run
729N/A private final int ensureRunBreak(int offset) {
729N/A return ensureRunBreak(offset, true);
729N/A }
729N/A
729N/A /**
729N/A * Ensures there is a run break at offset, returning the index of
729N/A * the run. If this results in splitting a run, two things can happen:
729N/A * <ul>
729N/A * <li>If copyAttrs is true, the attributes from the existing run
729N/A * will be placed in both of the newly created runs.
1344N/A * <li>If copyAttrs is false, the attributes from the existing run
729N/A * will NOT be copied to the run to the right (>= offset) of the break,
729N/A * but will exist on the run to the left (< offset).
729N/A * </ul>
729N/A */
0N/A private final int ensureRunBreak(int offset, boolean copyAttrs) {
0N/A if (offset == length()) {
730N/A return runCount;
0N/A }
0N/A
0N/A // search for the run index where this offset should be
567N/A int runIndex = 0;
662N/A while (runIndex < runCount && runStarts[runIndex] < offset) {
0N/A runIndex++;
2086N/A }
2086N/A
2086N/A // if the offset is at a run start already, we're done
2086N/A if (runIndex < runCount && runStarts[runIndex] == offset) {
2086N/A return runIndex;
2086N/A }
2086N/A
2086N/A // we'll have to break up a run
2086N/A // first, make sure we have enough space in our arrays
2086N/A if (runCount == runArraySize) {
0N/A int newArraySize = runArraySize + ARRAY_SIZE_INCREMENT;
0N/A int newRunStarts[] = new int[newArraySize];
662N/A Vector newRunAttributes[] = new Vector[newArraySize];
1210N/A Vector newRunAttributeValues[] = new Vector[newArraySize];
1210N/A for (int i = 0; i < runArraySize; i++) {
1210N/A newRunStarts[i] = runStarts[i];
1210N/A newRunAttributes[i] = runAttributes[i];
1210N/A newRunAttributeValues[i] = runAttributeValues[i];
1210N/A }
567N/A runStarts = newRunStarts;
567N/A runAttributes = newRunAttributes;
567N/A runAttributeValues = newRunAttributeValues;
563N/A runArraySize = newArraySize;
567N/A }
712N/A
712N/A // make copies of the attribute information of the old run that the new one used to be part of
730N/A // use temporary variables so things remain consistent in case of an exception
2033N/A Vector newRunAttributes = null;
712N/A Vector newRunAttributeValues = null;
712N/A
0N/A if (copyAttrs) {
0N/A Vector oldRunAttributes = runAttributes[runIndex - 1];
0N/A Vector oldRunAttributeValues = runAttributeValues[runIndex - 1];
0N/A if (oldRunAttributes != null) {
0N/A newRunAttributes = (Vector) oldRunAttributes.clone();
0N/A }
0N/A if (oldRunAttributeValues != null) {
2058N/A newRunAttributeValues = (Vector) oldRunAttributeValues.clone();
0N/A }
0N/A }
114N/A
114N/A // now actually break up the run
114N/A runCount++;
114N/A for (int i = runCount - 1; i > runIndex; i--) {
114N/A runStarts[i] = runStarts[i - 1];
114N/A runAttributes[i] = runAttributes[i - 1];
0N/A runAttributeValues[i] = runAttributeValues[i - 1];
0N/A }
0N/A runStarts[runIndex] = offset;
0N/A runAttributes[runIndex] = newRunAttributes;
121N/A runAttributeValues[runIndex] = newRunAttributeValues;
729N/A
2033N/A return runIndex;
121N/A }
121N/A
1210N/A // add the attribute attribute/value to all runs where beginRunIndex <= runIndex < endRunIndex
0N/A private void addAttributeRunData(Attribute attribute, Object value,
121N/A int beginRunIndex, int endRunIndex) {
2086N/A
0N/A for (int i = beginRunIndex; i < endRunIndex; i++) {
0N/A int keyValueIndex = -1; // index of key and value in our vectors; assume we don't have an entry yet
0N/A if (runAttributes[i] == null) {
2095N/A Vector newRunAttributes = new Vector();
2095N/A Vector newRunAttributeValues = new Vector();
2095N/A runAttributes[i] = newRunAttributes;
2095N/A runAttributeValues[i] = newRunAttributeValues;
0N/A } else {
0N/A // check whether we have an entry already
0N/A keyValueIndex = runAttributes[i].indexOf(attribute);
0N/A }
0N/A
0N/A if (keyValueIndex == -1) {
2046N/A // create new entry
2033N/A int oldSize = runAttributes[i].size();
2046N/A runAttributes[i].addElement(attribute);
0N/A try {
0N/A runAttributeValues[i].addElement(value);
1607N/A }
1607N/A catch (Exception e) {
1607N/A runAttributes[i].setSize(oldSize);
2033N/A runAttributeValues[i].setSize(oldSize);
2033N/A }
1607N/A } else {
0N/A // update existing entry
0N/A runAttributeValues[i].set(keyValueIndex, value);
1177N/A }
1177N/A }
984N/A }
984N/A
984N/A /**
984N/A * Creates an AttributedCharacterIterator instance that provides access to the entire contents of
984N/A * this string.
984N/A *
1177N/A * @return An iterator providing access to the text and its attributes.
1177N/A */
2033N/A public AttributedCharacterIterator getIterator() {
1149N/A return getIterator(null, 0, length());
1149N/A }
902N/A
902N/A /**
902N/A * Creates an AttributedCharacterIterator instance that provides access to
902N/A * selected contents of this string.
902N/A * Information about attributes not listed in attributes that the
902N/A * implementor may have need not be made accessible through the iterator.
902N/A * If the list is null, all available attribute information should be made
1083N/A * accessible.
902N/A *
902N/A * @param attributes a list of attributes that the client is interested in
902N/A * @return an iterator providing access to the entire text and its selected attributes
902N/A */
902N/A public AttributedCharacterIterator getIterator(Attribute[] attributes) {
902N/A return getIterator(attributes, 0, length());
902N/A }
902N/A
902N/A /**
902N/A * Creates an AttributedCharacterIterator instance that provides access to
1177N/A * selected contents of this string.
567N/A * Information about attributes not listed in attributes that the
1559N/A * implementor may have need not be made accessible through the iterator.
567N/A * If the list is null, all available attribute information should be made
567N/A * accessible.
1210N/A *
1210N/A * @param attributes a list of attributes that the client is interested in
1210N/A * @param beginIndex the index of the first character
1210N/A * @param endIndex the index of the character following the last character
1210N/A * @return an iterator providing access to the text and its attributes
1210N/A * @exception IllegalArgumentException if beginIndex is less then 0,
1210N/A * endIndex is greater than the length of the string, or beginIndex is
1210N/A * greater than endIndex.
1210N/A */
1210N/A public AttributedCharacterIterator getIterator(Attribute[] attributes, int beginIndex, int endIndex) {
1210N/A return new AttributedStringIterator(attributes, beginIndex, endIndex);
1210N/A }
2115N/A
1083N/A // all (with the exception of length) reading operations are private,
1008N/A // since AttributedString instances are accessed through iterators.
1008N/A
1008N/A // length is package private so that CharacterIteratorFieldDelegate can
1210N/A // access it without creating an AttributedCharacterIterator.
1008N/A int length() {
1008N/A return text.length();
2086N/A }
2115N/A
2086N/A private char charAt(int index) {
2086N/A return text.charAt(index);
2086N/A }
2115N/A
2115N/A private synchronized Object getAttribute(Attribute attribute, int runIndex) {
2115N/A Vector currentRunAttributes = runAttributes[runIndex];
1008N/A Vector currentRunAttributeValues = runAttributeValues[runIndex];
1008N/A if (currentRunAttributes == null) {
1083N/A return null;
567N/A }
567N/A int attributeIndex = currentRunAttributes.indexOf(attribute);
567N/A if (attributeIndex != -1) {
0N/A return currentRunAttributeValues.elementAt(attributeIndex);
567N/A }
567N/A else {
567N/A return null;
567N/A }
567N/A }
567N/A
567N/A // gets an attribute value, but returns an annotation only if it's range does not extend outside the range beginIndex..endIndex
567N/A private Object getAttributeCheckRange(Attribute attribute, int runIndex, int beginIndex, int endIndex) {
567N/A Object value = getAttribute(attribute, runIndex);
567N/A if (value instanceof Annotation) {
567N/A // need to check whether the annotation's range extends outside the iterator's range
2115N/A if (beginIndex > 0) {
2115N/A int currIndex = runIndex;
2115N/A int runStart = runStarts[currIndex];
2115N/A while (runStart >= beginIndex &&
2115N/A valuesMatch(value, getAttribute(attribute, currIndex - 1))) {
2556N/A currIndex--;
563N/A runStart = runStarts[currIndex];
712N/A }
1177N/A if (runStart < beginIndex) {
2710N/A // annotation's range starts before iterator's range
2710N/A return null;
2710N/A }
2710N/A }
2710N/A int textLength = length();
2710N/A if (endIndex < textLength) {
2710N/A int currIndex = runIndex;
2710N/A int runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength;
2710N/A while (runLimit <= endIndex &&
2710N/A valuesMatch(value, getAttribute(attribute, currIndex + 1))) {
2710N/A currIndex++;
0N/A runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength;
563N/A }
0N/A if (runLimit > endIndex) {
0N/A // annotation's range ends after iterator's range
0N/A return null;
0N/A }
2143N/A }
0N/A // annotation's range is subrange of iterator's range,
0N/A // so we can return the value
1362N/A }
1362N/A return value;
1362N/A }
1362N/A
1362N/A // returns whether all specified attributes have equal values in the runs with the given indices
1362N/A private boolean attributeValuesMatch(Set attributes, int runIndex1, int runIndex2) {
1362N/A Iterator iterator = attributes.iterator();
1362N/A while (iterator.hasNext()) {
1362N/A Attribute key = (Attribute) iterator.next();
1362N/A if (!valuesMatch(getAttribute(key, runIndex1), getAttribute(key, runIndex2))) {
1362N/A return false;
1362N/A }
0N/A }
0N/A return true;
1960N/A }
0N/A
0N/A // returns whether the two objects are either both null or equal
0N/A private final static boolean valuesMatch(Object value1, Object value2) {
1960N/A if (value1 == null) {
0N/A return value2 == null;
563N/A } else {
563N/A return value1.equals(value2);
712N/A }
1008N/A }
1008N/A
1008N/A /**
0N/A * Appends the contents of the CharacterIterator iterator into the
0N/A * StringBuffer buf.
2098N/A */
1557N/A private final void appendContents(StringBuffer buf,
1083N/A CharacterIterator iterator) {
0N/A int index = iterator.getBeginIndex();
0N/A int end = iterator.getEndIndex();
0N/A
0N/A while (index < end) {
0N/A iterator.setIndex(index++);
0N/A buf.append(iterator.current());
0N/A }
0N/A }
2046N/A
2033N/A /**
2046N/A * Sets the attributes for the range from offset to the next run break
0N/A * (typically the end of the text) to the ones specified in attrs.
0N/A * This is only meant to be called from the constructor!
0N/A */
1607N/A private void setAttributes(Map attrs, int offset) {
1607N/A if (runCount == 0) {
2046N/A createRunAttributeDataVectors();
2033N/A }
1607N/A
1607N/A int index = ensureRunBreak(offset, false);
2086N/A int size;
2710N/A
2086N/A if (attrs != null && (size = attrs.size()) > 0) {
2122N/A Vector runAttrs = new Vector(size);
2122N/A Vector runValues = new Vector(size);
2122N/A Iterator iterator = attrs.entrySet().iterator();
2122N/A
0N/A while (iterator.hasNext()) {
1960N/A Map.Entry entry = (Map.Entry)iterator.next();
2033N/A
2046N/A runAttrs.add(entry.getKey());
2046N/A runValues.add(entry.getValue());
712N/A }
563N/A runAttributes[index] = runAttrs;
563N/A runAttributeValues[index] = runValues;
1210N/A }
1210N/A }
1210N/A
1210N/A /**
1210N/A * Returns true if the attributes specified in last and attrs differ.
1210N/A */
1210N/A private static boolean mapsDiffer(Map last, Map attrs) {
1210N/A if (last == null) {
1210N/A return (attrs != null && attrs.size() > 0);
1210N/A }
1210N/A return (!last.equals(attrs));
1210N/A }
1008N/A
1083N/A
1008N/A // the iterator class associated with this string class
1008N/A
1008N/A final private class AttributedStringIterator implements AttributedCharacterIterator {
1210N/A
1008N/A // note on synchronization:
1008N/A // we don't synchronize on the iterator, assuming that an iterator is only used in one thread.
1008N/A // we do synchronize access to the AttributedString however, since it's more likely to be shared between threads.
1008N/A
1083N/A // start and end index for our iteration
1083N/A private int beginIndex;
563N/A private int endIndex;
563N/A
1008N/A // attributes that our client is interested in
1008N/A private Attribute[] relevantAttributes;
1008N/A
1008N/A // the current index for our iteration
1008N/A // invariant: beginIndex <= currentIndex <= endIndex
1008N/A private int currentIndex;
1960N/A
1008N/A // information about the run that includes currentIndex
1008N/A private int currentRunIndex;
563N/A private int currentRunStart;
563N/A private int currentRunLimit;
563N/A
1008N/A // constructor
563N/A AttributedStringIterator(Attribute[] attributes, int beginIndex, int endIndex) {
563N/A
563N/A if (beginIndex < 0 || beginIndex > endIndex || endIndex > length()) {
563N/A throw new IllegalArgumentException("Invalid substring range");
563N/A }
563N/A
712N/A this.beginIndex = beginIndex;
1008N/A this.endIndex = endIndex;
1008N/A this.currentIndex = beginIndex;
0N/A updateRunInfo();
0N/A if (attributes != null) {
0N/A relevantAttributes = (Attribute[]) attributes.clone();
3214N/A }
3214N/A }
3214N/A
3214N/A // Object methods. See documentation in that class.
3214N/A
3214N/A public boolean equals(Object obj) {
3214N/A if (this == obj) {
3214N/A return true;
3090N/A }
3214N/A if (!(obj instanceof AttributedStringIterator)) {
3214N/A return false;
3214N/A }
3214N/A
3090N/A AttributedStringIterator that = (AttributedStringIterator) obj;
3090N/A
3090N/A if (AttributedString.this != that.getString())
3090N/A return false;
3090N/A if (currentIndex != that.currentIndex || beginIndex != that.beginIndex || endIndex != that.endIndex)
3090N/A return false;
3090N/A return true;
3214N/A }
3214N/A
3214N/A public int hashCode() {
3214N/A return text.hashCode() ^ currentIndex ^ beginIndex ^ endIndex;
3214N/A }
3214N/A
3214N/A public Object clone() {
3214N/A try {
3214N/A AttributedStringIterator other = (AttributedStringIterator) super.clone();
3214N/A return other;
3214N/A }
3214N/A catch (CloneNotSupportedException e) {
3214N/A throw new InternalError();
3214N/A }
3214N/A }
3214N/A
3214N/A // CharacterIterator methods. See documentation in that interface.
3214N/A
3214N/A public char first() {
3214N/A return internalSetIndex(beginIndex);
3214N/A }
3090N/A
3090N/A public char last() {
0N/A if (endIndex == beginIndex) {
0N/A return internalSetIndex(endIndex);
121N/A } else {
0N/A return internalSetIndex(endIndex - 1);
0N/A }
1960N/A }
0N/A
2556N/A public char current() {
2556N/A if (currentIndex == endIndex) {
2556N/A return DONE;
2556N/A } else {
2556N/A return charAt(currentIndex);
2556N/A }
2556N/A }
2556N/A
2556N/A public char next() {
2556N/A if (currentIndex < endIndex) {
2556N/A return internalSetIndex(currentIndex + 1);
3108N/A }
2556N/A else {
2556N/A return DONE;
2556N/A }
2556N/A }
2556N/A
2556N/A public char previous() {
2556N/A if (currentIndex > beginIndex) {
2556N/A return internalSetIndex(currentIndex - 1);
2556N/A }
2556N/A else {
2556N/A return DONE;
2556N/A }
2556N/A }
2556N/A
2556N/A public char setIndex(int position) {
2556N/A if (position < beginIndex || position > endIndex)
2556N/A throw new IllegalArgumentException("Invalid index");
2556N/A return internalSetIndex(position);
2556N/A }
2556N/A
2556N/A public int getBeginIndex() {
2556N/A return beginIndex;
0N/A }
2086N/A
2086N/A public int getEndIndex() {
2710N/A return endIndex;
2086N/A }
2122N/A
2122N/A public int getIndex() {
2122N/A return currentIndex;
2122N/A }
1960N/A
2556N/A // AttributedCharacterIterator methods. See documentation in that interface.
2556N/A
2046N/A public int getRunStart() {
712N/A return currentRunStart;
563N/A }
563N/A
712N/A public int getRunStart(Attribute attribute) {
0N/A if (currentRunStart == beginIndex || currentRunIndex == -1) {
0N/A return currentRunStart;
0N/A } else {
1008N/A Object value = getAttribute(attribute);
1008N/A int runStart = currentRunStart;
1008N/A int runIndex = currentRunIndex;
1008N/A while (runStart > beginIndex &&
0N/A valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex - 1))) {
1008N/A runIndex--;
1008N/A runStart = runStarts[runIndex];
2976N/A }
2976N/A if (runStart < beginIndex) {
1194N/A runStart = beginIndex;
2976N/A }
2976N/A return runStart;
1008N/A }
0N/A }
0N/A
0N/A public int getRunStart(Set<? extends Attribute> attributes) {
0N/A if (currentRunStart == beginIndex || currentRunIndex == -1) {
920N/A return currentRunStart;
920N/A } else {
920N/A int runStart = currentRunStart;
920N/A int runIndex = currentRunIndex;
920N/A while (runStart > beginIndex &&
920N/A AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex - 1)) {
920N/A runIndex--;
920N/A runStart = runStarts[runIndex];
920N/A }
920N/A if (runStart < beginIndex) {
920N/A runStart = beginIndex;
920N/A }
920N/A return runStart;
920N/A }
920N/A }
0N/A
0N/A public int getRunLimit() {
0N/A return currentRunLimit;
0N/A }
0N/A
0N/A public int getRunLimit(Attribute attribute) {
0N/A if (currentRunLimit == endIndex || currentRunIndex == -1) {
0N/A return currentRunLimit;
107N/A } else {
107N/A Object value = getAttribute(attribute);
107N/A int runLimit = currentRunLimit;
107N/A int runIndex = currentRunIndex;
29N/A while (runLimit < endIndex &&
29N/A valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex + 1))) {
29N/A runIndex++;
29N/A runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex;
29N/A }
29N/A if (runLimit > endIndex) {
29N/A runLimit = endIndex;
3012N/A }
2911N/A return runLimit;
2911N/A }
2911N/A }
3012N/A
2911N/A public int getRunLimit(Set<? extends Attribute> attributes) {
2911N/A if (currentRunLimit == endIndex || currentRunIndex == -1) {
3012N/A return currentRunLimit;
3012N/A } else {
2549N/A int runLimit = currentRunLimit;
547N/A int runIndex = currentRunIndex;
2549N/A while (runLimit < endIndex &&
547N/A AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex + 1)) {
84N/A runIndex++;
565N/A runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex;
712N/A }
1345N/A if (runLimit > endIndex) {
2556N/A runLimit = endIndex;
2556N/A }
2556N/A return runLimit;
2556N/A }
2556N/A }
2556N/A
2556N/A public Map<Attribute,Object> getAttributes() {
2556N/A if (runAttributes == null || currentRunIndex == -1 || runAttributes[currentRunIndex] == null) {
2556N/A // ??? would be nice to return null, but current spec doesn't allow it
2556N/A // returning Hashtable saves AttributeMap from dealing with emptiness
2556N/A return new Hashtable();
2556N/A }
2556N/A return new AttributeMap(currentRunIndex, beginIndex, endIndex);
2556N/A }
2556N/A
2556N/A public Set<Attribute> getAllAttributeKeys() {
2556N/A // ??? This should screen out attribute keys that aren't relevant to the client
2556N/A if (runAttributes == null) {
2556N/A // ??? would be nice to return null, but current spec doesn't allow it
2556N/A // returning HashSet saves us from dealing with emptiness
2556N/A return new HashSet();
2556N/A }
2556N/A synchronized (AttributedString.this) {
2556N/A // ??? should try to create this only once, then update if necessary,
2556N/A // and give callers read-only view
2556N/A Set keys = new HashSet();
485N/A int i = 0;
485N/A while (i < runCount) {
485N/A if (runStarts[i] < endIndex && (i == runCount - 1 || runStarts[i + 1] > beginIndex)) {
485N/A Vector currentRunAttributes = runAttributes[i];
1197N/A if (currentRunAttributes != null) {
1197N/A int j = currentRunAttributes.size();
2976N/A while (j-- > 0) {
2976N/A keys.add(currentRunAttributes.get(j));
0N/A }
0N/A }
0N/A }
0N/A i++;
0N/A }
0N/A return keys;
0N/A }
0N/A }
1960N/A
3100N/A public Object getAttribute(Attribute attribute) {
3100N/A int runIndex = currentRunIndex;
1960N/A if (runIndex < 0) {
2976N/A return null;
0N/A }
499N/A return AttributedString.this.getAttributeCheckRange(attribute, runIndex, beginIndex, endIndex);
2976N/A }
1194N/A
1960N/A // internally used methods
1960N/A
499N/A private AttributedString getString() {
1960N/A return AttributedString.this;
1210N/A }
2556N/A
499N/A // set the current index, update information about the current run if necessary,
0N/A // return the character at the current index
1559N/A private char internalSetIndex(int position) {
3069N/A currentIndex = position;
2308N/A if (position < currentRunStart || position >= currentRunLimit) {
2308N/A updateRunInfo();
2308N/A }
2308N/A if (currentIndex == endIndex) {
2308N/A return DONE;
0N/A } else {
0N/A return charAt(position);
3069N/A }
3069N/A }
3069N/A
3069N/A // update the information about the current run
3069N/A private void updateRunInfo() {
3069N/A if (currentIndex == endIndex) {
3069N/A currentRunStart = currentRunLimit = endIndex;
3069N/A currentRunIndex = -1;
3069N/A } else {
3069N/A synchronized (AttributedString.this) {
3069N/A int runIndex = -1;
3069N/A while (runIndex < runCount - 1 && runStarts[runIndex + 1] <= currentIndex)
3069N/A runIndex++;
3069N/A currentRunIndex = runIndex;
3069N/A if (runIndex >= 0) {
3069N/A currentRunStart = runStarts[runIndex];
3079N/A if (currentRunStart < beginIndex)
3069N/A currentRunStart = beginIndex;
3069N/A }
3069N/A else {
3069N/A currentRunStart = beginIndex;
3069N/A }
3069N/A if (runIndex < runCount - 1) {
3079N/A currentRunLimit = runStarts[runIndex + 1];
3069N/A if (currentRunLimit > endIndex)
3069N/A currentRunLimit = endIndex;
3069N/A }
3069N/A else {
3069N/A currentRunLimit = endIndex;
3069N/A }
3069N/A }
3069N/A }
3069N/A }
3069N/A
3069N/A }
3069N/A
3069N/A // the map class associated with this string class, giving access to the attributes of one run
3069N/A
3069N/A final private class AttributeMap extends AbstractMap<Attribute,Object> {
3069N/A
3069N/A int runIndex;
3069N/A int beginIndex;
3069N/A int endIndex;
3069N/A
3069N/A AttributeMap(int runIndex, int beginIndex, int endIndex) {
3069N/A this.runIndex = runIndex;
3069N/A this.beginIndex = beginIndex;
3069N/A this.endIndex = endIndex;
3069N/A }
3069N/A
3069N/A public Set entrySet() {
3069N/A HashSet set = new HashSet();
3069N/A synchronized (AttributedString.this) {
3069N/A int size = runAttributes[runIndex].size();
3069N/A for (int i = 0; i < size; i++) {
3069N/A Attribute key = (Attribute) runAttributes[runIndex].get(i);
3069N/A Object value = runAttributeValues[runIndex].get(i);
3069N/A if (value instanceof Annotation) {
3084N/A value = AttributedString.this.getAttributeCheckRange(key,
3069N/A runIndex, beginIndex, endIndex);
3069N/A if (value == null) {
1177N/A continue;
1177N/A }
0N/A }
0N/A Map.Entry entry = new AttributeEntry(key, value);
0N/A set.add(entry);
0N/A }
0N/A }
0N/A return set;
0N/A }
0N/A
0N/A public Object get(Object key) {
0N/A return AttributedString.this.getAttributeCheckRange((Attribute) key, runIndex, beginIndex, endIndex);
0N/A }
0N/A }
0N/A}
0N/A
0N/Aclass AttributeEntry implements Map.Entry {
0N/A
0N/A private Attribute key;
0N/A private Object value;
0N/A
0N/A AttributeEntry(Attribute key, Object value) {
0N/A this.key = key;
1083N/A this.value = value;
0N/A }
0N/A
0N/A public boolean equals(Object o) {
3107N/A if (!(o instanceof AttributeEntry)) {
0N/A return false;
0N/A }
0N/A AttributeEntry other = (AttributeEntry) o;
0N/A return other.key.equals(key) &&
0N/A (value == null ? other.value == null : other.value.equals(value));
0N/A }
0N/A
0N/A public Object getKey() {
0N/A return key;
1083N/A }
0N/A
0N/A public Object getValue() {
0N/A return value;
3107N/A }
0N/A
0N/A public Object setValue(Object newValue) {
0N/A throw new UnsupportedOperationException();
0N/A }
0N/A
0N/A public int hashCode() {
0N/A return key.hashCode() ^ (value==null ? 0 : value.hashCode());
0N/A }
0N/A
0N/A public String toString() {
3000N/A return key.toString()+"="+value.toString();
3000N/A }
3000N/A}
3000N/A