0N/A/*
2362N/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
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/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,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/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
0N/A * reader wants to access attributed text through the AttributedCharacterIterator
0N/A * interface.
0N/A *
0N/A * <p>
0N/A * An attribute is a key/value pair, identified by the key. No two
0N/A * attributes on a given character can have the same key.
0N/A *
0N/A * <p>The values for an attribute are immutable, or must not be mutated
0N/A * by clients or storage. They are always passed by reference, and not
0N/A * cloned.
0N/A *
0N/A * @see AttributedCharacterIterator
0N/A * @see Annotation
0N/A * @since 1.2
0N/A */
0N/A
0N/Apublic class AttributedString {
0N/A
0N/A // since there are no vectors of int, we have to use arrays.
0N/A // We allocate them in chunks of 10 elements so we don't have to allocate all the time.
0N/A private static final int ARRAY_SIZE_INCREMENT = 10;
0N/A
0N/A // field holding the text
0N/A String text;
0N/A
0N/A // fields holding run attribute information
0N/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
0N/A
0N/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) {
0N/A if (iterators == null) {
0N/A throw new NullPointerException("Iterators must not be null");
0N/A }
0N/A if (iterators.length == 0) {
0N/A text = "";
0N/A }
0N/A else {
0N/A // Build the String contents
0N/A StringBuffer buffer = new StringBuffer();
0N/A for (int counter = 0; counter < iterators.length; counter++) {
0N/A appendContents(buffer, iterators[counter]);
0N/A }
0N/A
0N/A text = buffer.toString();
0N/A
0N/A if (text.length() > 0) {
0N/A // Determine the runs, creating a new run when the attributes
0N/A // differ.
0N/A int offset = 0;
0N/A Map last = null;
0N/A
0N/A for (int counter = 0; counter < iterators.length; counter++) {
0N/A AttributedCharacterIterator iterator = iterators[counter];
0N/A int start = iterator.getBeginIndex();
0N/A int end = iterator.getEndIndex();
0N/A int index = start;
0N/A
0N/A while (index < end) {
0N/A iterator.setIndex(index);
0N/A
0N/A Map attrs = iterator.getAttributes();
0N/A
0N/A if (mapsDiffer(last, attrs)) {
0N/A setAttributes(attrs, index - start + offset);
0N/A }
0N/A last = attrs;
0N/A index = iterator.getRunLimit();
0N/A }
0N/A offset += (end - start);
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Constructs an AttributedString instance with the given text.
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(String text) {
0N/A if (text == null) {
0N/A throw new NullPointerException();
0N/A }
0N/A this.text = text;
0N/A }
0N/A
0N/A /**
0N/A * Constructs an AttributedString instance with the given text and attributes.
0N/A * @param text The text for this attributed string.
0N/A * @param attributes The attributes that apply to the entire string.
0N/A * @exception NullPointerException if <code>text</code> or
0N/A * <code>attributes</code> is null.
0N/A * @exception IllegalArgumentException if the text has length 0
0N/A * and the attributes parameter is not an empty Map (attributes
0N/A * cannot be applied to a 0-length range).
0N/A */
0N/A public AttributedString(String text,
0N/A Map<? extends Attribute, ?> attributes)
0N/A {
0N/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 }
0N/A
0N/A int attributeCount = attributes.size();
0N/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();
0N/A while (iterator.hasNext()) {
0N/A Map.Entry entry = (Map.Entry) iterator.next();
0N/A newRunAttributes.addElement(entry.getKey());
0N/A newRunAttributeValues.addElement(entry.getValue());
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Constructs an AttributedString instance with the given attributed
0N/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
0N/A // subrange. We can avoid some range checking in the loops.
0N/A this(text, text.getBeginIndex(), text.getEndIndex(), null);
0N/A }
0N/A
0N/A /**
0N/A * Constructs an AttributedString instance with the subrange of
0N/A * the given attributed text represented by
0N/A * AttributedCharacterIterator. If the given range produces an
0N/A * empty text, all attributes will be discarded. Note that any
0N/A * attributes wrapped by an Annotation object are discarded for a
0N/A * subrange of the original attribute range.
0N/A *
0N/A * @param text The text for this attributed string.
0N/A * @param beginIndex Index of the first character of the range.
0N/A * @param endIndex Index of the character following the last character
0N/A * of the range.
0N/A * @exception NullPointerException if <code>text</code> is null.
0N/A * @exception IllegalArgumentException if the subrange given by
0N/A * beginIndex and endIndex is out of the text range.
0N/A * @see java.text.Annotation
0N/A */
0N/A public AttributedString(AttributedCharacterIterator text,
0N/A int beginIndex,
0N/A int endIndex) {
0N/A this(text, beginIndex, endIndex, null);
0N/A }
0N/A
0N/A /**
0N/A * Constructs an AttributedString instance with the subrange of
0N/A * the given attributed text represented by
0N/A * AttributedCharacterIterator. Only attributes that match the
0N/A * given attributes will be incorporated into the instance. If the
0N/A * given range produces an empty text, all attributes will be
0N/A * discarded. Note that any attributes wrapped by an Annotation
0N/A * object are discarded for a subrange of the original attribute
0N/A * range.
0N/A *
0N/A * @param text The text for this attributed string.
0N/A * @param beginIndex Index of the first character of the range.
0N/A * @param endIndex Index of the character following the last character
0N/A * of the range.
0N/A * @param attributes Specifies attributes to be extracted
0N/A * from the text. If null is specified, all available attributes will
0N/A * be used.
0N/A * @exception NullPointerException if <code>text</code> is null.
0N/A * @exception IllegalArgumentException if the subrange given by
0N/A * beginIndex and endIndex is out of the text range.
0N/A * @see java.text.Annotation
0N/A */
0N/A public AttributedString(AttributedCharacterIterator text,
0N/A int beginIndex,
0N/A int endIndex,
0N/A Attribute[] attributes) {
0N/A if (text == null) {
0N/A throw new NullPointerException();
0N/A }
0N/A
0N/A // Validate the given subrange
0N/A int textBeginIndex = text.getBeginIndex();
0N/A int textEndIndex = text.getEndIndex();
0N/A if (beginIndex < textBeginIndex || endIndex > textEndIndex || beginIndex > endIndex)
0N/A throw new IllegalArgumentException("Invalid substring range");
0N/A
0N/A // Copy the given string
0N/A StringBuffer textBuffer = new StringBuffer();
0N/A text.setIndex(beginIndex);
0N/A for (char c = text.current(); text.getIndex() < endIndex; c = text.next())
0N/A textBuffer.append(c);
0N/A this.text = textBuffer.toString();
0N/A
0N/A if (beginIndex == endIndex)
0N/A return;
0N/A
0N/A // Select attribute keys to be taken care of
0N/A HashSet keys = new HashSet();
0N/A if (attributes == null) {
0N/A keys.addAll(text.getAllAttributeKeys());
0N/A } else {
0N/A for (int i = 0; i < attributes.length; i++)
0N/A keys.add(attributes[i]);
0N/A keys.retainAll(text.getAllAttributeKeys());
0N/A }
0N/A if (keys.isEmpty())
0N/A return;
0N/A
0N/A // Get and set attribute runs for each attribute name. Need to
0N/A // scan from the top of the text so that we can discard any
0N/A // Annotation that is no longer applied to a subset text segment.
0N/A Iterator itr = keys.iterator();
0N/A while (itr.hasNext()) {
0N/A Attribute attributeKey = (Attribute)itr.next();
0N/A text.setIndex(textBeginIndex);
0N/A while (text.getIndex() < endIndex) {
0N/A int start = text.getRunStart(attributeKey);
0N/A int limit = text.getRunLimit(attributeKey);
0N/A Object value = text.getAttribute(attributeKey);
0N/A
0N/A if (value != null) {
0N/A if (value instanceof Annotation) {
0N/A if (start >= beginIndex && limit <= endIndex) {
0N/A addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex);
0N/A } else {
0N/A if (limit > endIndex)
0N/A break;
0N/A }
0N/A } else {
0N/A // if the run is beyond the given (subset) range, we
0N/A // don't need to process further.
0N/A if (start >= endIndex)
0N/A break;
0N/A if (limit > beginIndex) {
0N/A // attribute is applied to any subrange
0N/A if (start < beginIndex)
0N/A start = beginIndex;
0N/A if (limit > endIndex)
0N/A limit = endIndex;
0N/A if (start != limit) {
0N/A addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex);
0N/A }
0N/A }
0N/A }
0N/A }
0N/A text.setIndex(limit);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Adds an attribute to the entire string.
0N/A * @param attribute the attribute key
0N/A * @param value the value of the attribute; may be null
0N/A * @exception NullPointerException if <code>attribute</code> is null.
0N/A * @exception IllegalArgumentException if the AttributedString has length 0
0N/A * (attributes cannot be applied to a 0-length range).
0N/A */
0N/A public void addAttribute(Attribute attribute, Object value) {
0N/A
0N/A if (attribute == null) {
0N/A throw new NullPointerException();
0N/A }
0N/A
0N/A int len = length();
0N/A if (len == 0) {
0N/A throw new IllegalArgumentException("Can't add attribute to 0-length text");
0N/A }
0N/A
0N/A addAttributeImpl(attribute, value, 0, len);
0N/A }
0N/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.
0N/A * @param endIndex Index of the character following the last character of the range.
0N/A * @exception NullPointerException if <code>attribute</code> is null.
0N/A * @exception IllegalArgumentException if beginIndex is less then 0, endIndex is
0N/A * greater than the length of the string, or beginIndex and endIndex together don't
0N/A * define a non-empty subrange of the string.
0N/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.
0N/A * @param endIndex Index of the character following the last
0N/A * character of the range.
0N/A * @exception NullPointerException if <code>attributes</code> is null.
0N/A * @exception IllegalArgumentException if beginIndex is less then
0N/A * 0, endIndex is greater than the length of the string, or
0N/A * beginIndex and endIndex together don't define a non-empty
0N/A * subrange of the string and the attributes parameter is not an
0N/A * empty Map.
0N/A */
0N/A public void addAttributes(Map<? extends Attribute, ?> attributes,
0N/A int beginIndex, int endIndex)
0N/A {
0N/A if (attributes == 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 if (beginIndex == endIndex) {
0N/A if (attributes.isEmpty())
0N/A return;
0N/A throw new IllegalArgumentException("Can't add attribute to 0-length text");
0N/A }
0N/A
0N/A // make sure we have run attribute data vectors
0N/A if (runCount == 0) {
0N/A createRunAttributeDataVectors();
0N/A }
0N/A
0N/A // break up runs if necessary
0N/A int beginRunIndex = ensureRunBreak(beginIndex);
0N/A int endRunIndex = ensureRunBreak(endIndex);
0N/A
0N/A Iterator iterator = attributes.entrySet().iterator();
0N/A while (iterator.hasNext()) {
0N/A Map.Entry entry = (Map.Entry) iterator.next();
0N/A addAttributeRunData((Attribute) entry.getKey(), entry.getValue(), beginRunIndex, endRunIndex);
0N/A }
0N/A }
0N/A
0N/A private synchronized void addAttributeImpl(Attribute attribute, Object value,
0N/A int beginIndex, int endIndex) {
0N/A
0N/A // make sure we have run attribute data vectors
0N/A if (runCount == 0) {
0N/A createRunAttributeDataVectors();
0N/A }
0N/A
0N/A // break up runs if necessary
0N/A int beginRunIndex = ensureRunBreak(beginIndex);
0N/A int endRunIndex = ensureRunBreak(endIndex);
0N/A
0N/A addAttributeRunData(attribute, value, beginRunIndex, endRunIndex);
0N/A }
0N/A
0N/A private final void createRunAttributeDataVectors() {
0N/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;
0N/A runAttributeValues = newRunAttributeValues;
0N/A runArraySize = ARRAY_SIZE_INCREMENT;
0N/A runCount = 1; // assume initial run starting at index 0
0N/A }
0N/A
0N/A // ensure there's a run break at offset, return the index of the run
0N/A private final int ensureRunBreak(int offset) {
0N/A return ensureRunBreak(offset, true);
0N/A }
0N/A
0N/A /**
0N/A * Ensures there is a run break at offset, returning the index of
0N/A * the run. If this results in splitting a run, two things can happen:
0N/A * <ul>
0N/A * <li>If copyAttrs is true, the attributes from the existing run
0N/A * will be placed in both of the newly created runs.
0N/A * <li>If copyAttrs is false, the attributes from the existing run
0N/A * will NOT be copied to the run to the right (>= offset) of the break,
0N/A * but will exist on the run to the left (< offset).
0N/A * </ul>
0N/A */
0N/A private final int ensureRunBreak(int offset, boolean copyAttrs) {
0N/A if (offset == length()) {
0N/A return runCount;
0N/A }
0N/A
0N/A // search for the run index where this offset should be
0N/A int runIndex = 0;
0N/A while (runIndex < runCount && runStarts[runIndex] < offset) {
0N/A runIndex++;
0N/A }
0N/A
0N/A // if the offset is at a run start already, we're done
0N/A if (runIndex < runCount && runStarts[runIndex] == offset) {
0N/A return runIndex;
0N/A }
0N/A
0N/A // we'll have to break up a run
0N/A // first, make sure we have enough space in our arrays
0N/A if (runCount == runArraySize) {
0N/A int newArraySize = runArraySize + ARRAY_SIZE_INCREMENT;
0N/A int newRunStarts[] = new int[newArraySize];
0N/A Vector newRunAttributes[] = new Vector[newArraySize];
0N/A Vector newRunAttributeValues[] = new Vector[newArraySize];
0N/A for (int i = 0; i < runArraySize; i++) {
0N/A newRunStarts[i] = runStarts[i];
0N/A newRunAttributes[i] = runAttributes[i];
0N/A newRunAttributeValues[i] = runAttributeValues[i];
0N/A }
0N/A runStarts = newRunStarts;
0N/A runAttributes = newRunAttributes;
0N/A runAttributeValues = newRunAttributeValues;
0N/A runArraySize = newArraySize;
0N/A }
0N/A
0N/A // make copies of the attribute information of the old run that the new one used to be part of
0N/A // use temporary variables so things remain consistent in case of an exception
0N/A Vector newRunAttributes = null;
0N/A Vector newRunAttributeValues = null;
0N/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) {
0N/A newRunAttributeValues = (Vector) oldRunAttributeValues.clone();
0N/A }
0N/A }
0N/A
0N/A // now actually break up the run
0N/A runCount++;
0N/A for (int i = runCount - 1; i > runIndex; i--) {
0N/A runStarts[i] = runStarts[i - 1];
0N/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;
0N/A runAttributeValues[runIndex] = newRunAttributeValues;
0N/A
0N/A return runIndex;
0N/A }
0N/A
0N/A // add the attribute attribute/value to all runs where beginRunIndex <= runIndex < endRunIndex
0N/A private void addAttributeRunData(Attribute attribute, Object value,
0N/A int beginRunIndex, int endRunIndex) {
0N/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) {
0N/A Vector newRunAttributes = new Vector();
0N/A Vector newRunAttributeValues = new Vector();
0N/A runAttributes[i] = newRunAttributes;
0N/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) {
0N/A // create new entry
0N/A int oldSize = runAttributes[i].size();
0N/A runAttributes[i].addElement(attribute);
0N/A try {
0N/A runAttributeValues[i].addElement(value);
0N/A }
0N/A catch (Exception e) {
0N/A runAttributes[i].setSize(oldSize);
0N/A runAttributeValues[i].setSize(oldSize);
0N/A }
0N/A } else {
0N/A // update existing entry
0N/A runAttributeValues[i].set(keyValueIndex, value);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Creates an AttributedCharacterIterator instance that provides access to the entire contents of
0N/A * this string.
0N/A *
0N/A * @return An iterator providing access to the text and its attributes.
0N/A */
0N/A public AttributedCharacterIterator getIterator() {
0N/A return getIterator(null, 0, length());
0N/A }
0N/A
0N/A /**
0N/A * Creates an AttributedCharacterIterator instance that provides access to
0N/A * selected contents of this string.
0N/A * Information about attributes not listed in attributes that the
0N/A * implementor may have need not be made accessible through the iterator.
0N/A * If the list is null, all available attribute information should be made
0N/A * accessible.
0N/A *
0N/A * @param attributes a list of attributes that the client is interested in
0N/A * @return an iterator providing access to the entire text and its selected attributes
0N/A */
0N/A public AttributedCharacterIterator getIterator(Attribute[] attributes) {
0N/A return getIterator(attributes, 0, length());
0N/A }
0N/A
0N/A /**
0N/A * Creates an AttributedCharacterIterator instance that provides access to
0N/A * selected contents of this string.
0N/A * Information about attributes not listed in attributes that the
0N/A * implementor may have need not be made accessible through the iterator.
0N/A * If the list is null, all available attribute information should be made
0N/A * accessible.
0N/A *
0N/A * @param attributes a list of attributes that the client is interested in
0N/A * @param beginIndex the index of the first character
0N/A * @param endIndex the index of the character following the last character
0N/A * @return an iterator providing access to the text and its attributes
0N/A * @exception IllegalArgumentException if beginIndex is less then 0,
0N/A * endIndex is greater than the length of the string, or beginIndex is
0N/A * greater than endIndex.
0N/A */
0N/A public AttributedCharacterIterator getIterator(Attribute[] attributes, int beginIndex, int endIndex) {
0N/A return new AttributedStringIterator(attributes, beginIndex, endIndex);
0N/A }
0N/A
0N/A // all (with the exception of length) reading operations are private,
0N/A // since AttributedString instances are accessed through iterators.
0N/A
0N/A // length is package private so that CharacterIteratorFieldDelegate can
0N/A // access it without creating an AttributedCharacterIterator.
0N/A int length() {
0N/A return text.length();
0N/A }
0N/A
0N/A private char charAt(int index) {
0N/A return text.charAt(index);
0N/A }
0N/A
0N/A private synchronized Object getAttribute(Attribute attribute, int runIndex) {
0N/A Vector currentRunAttributes = runAttributes[runIndex];
0N/A Vector currentRunAttributeValues = runAttributeValues[runIndex];
0N/A if (currentRunAttributes == null) {
0N/A return null;
0N/A }
0N/A int attributeIndex = currentRunAttributes.indexOf(attribute);
0N/A if (attributeIndex != -1) {
0N/A return currentRunAttributeValues.elementAt(attributeIndex);
0N/A }
0N/A else {
0N/A return null;
0N/A }
0N/A }
0N/A
0N/A // gets an attribute value, but returns an annotation only if it's range does not extend outside the range beginIndex..endIndex
0N/A private Object getAttributeCheckRange(Attribute attribute, int runIndex, int beginIndex, int endIndex) {
0N/A Object value = getAttribute(attribute, runIndex);
0N/A if (value instanceof Annotation) {
0N/A // need to check whether the annotation's range extends outside the iterator's range
0N/A if (beginIndex > 0) {
0N/A int currIndex = runIndex;
0N/A int runStart = runStarts[currIndex];
0N/A while (runStart >= beginIndex &&
0N/A valuesMatch(value, getAttribute(attribute, currIndex - 1))) {
0N/A currIndex--;
0N/A runStart = runStarts[currIndex];
0N/A }
0N/A if (runStart < beginIndex) {
0N/A // annotation's range starts before iterator's range
0N/A return null;
0N/A }
0N/A }
0N/A int textLength = length();
0N/A if (endIndex < textLength) {
0N/A int currIndex = runIndex;
0N/A int runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength;
0N/A while (runLimit <= endIndex &&
0N/A valuesMatch(value, getAttribute(attribute, currIndex + 1))) {
0N/A currIndex++;
0N/A runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength;
0N/A }
0N/A if (runLimit > endIndex) {
0N/A // annotation's range ends after iterator's range
0N/A return null;
0N/A }
0N/A }
0N/A // annotation's range is subrange of iterator's range,
0N/A // so we can return the value
0N/A }
0N/A return value;
0N/A }
0N/A
0N/A // returns whether all specified attributes have equal values in the runs with the given indices
0N/A private boolean attributeValuesMatch(Set attributes, int runIndex1, int runIndex2) {
0N/A Iterator iterator = attributes.iterator();
0N/A while (iterator.hasNext()) {
0N/A Attribute key = (Attribute) iterator.next();
0N/A if (!valuesMatch(getAttribute(key, runIndex1), getAttribute(key, runIndex2))) {
0N/A return false;
0N/A }
0N/A }
0N/A return true;
0N/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) {
0N/A if (value1 == null) {
0N/A return value2 == null;
0N/A } else {
0N/A return value1.equals(value2);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Appends the contents of the CharacterIterator iterator into the
0N/A * StringBuffer buf.
0N/A */
0N/A private final void appendContents(StringBuffer buf,
0N/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 }
0N/A
0N/A /**
0N/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 */
0N/A private void setAttributes(Map attrs, int offset) {
0N/A if (runCount == 0) {
0N/A createRunAttributeDataVectors();
0N/A }
0N/A
0N/A int index = ensureRunBreak(offset, false);
0N/A int size;
0N/A
0N/A if (attrs != null && (size = attrs.size()) > 0) {
0N/A Vector runAttrs = new Vector(size);
0N/A Vector runValues = new Vector(size);
0N/A Iterator iterator = attrs.entrySet().iterator();
0N/A
0N/A while (iterator.hasNext()) {
0N/A Map.Entry entry = (Map.Entry)iterator.next();
0N/A
0N/A runAttrs.add(entry.getKey());
0N/A runValues.add(entry.getValue());
0N/A }
0N/A runAttributes[index] = runAttrs;
0N/A runAttributeValues[index] = runValues;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Returns true if the attributes specified in last and attrs differ.
0N/A */
0N/A private static boolean mapsDiffer(Map last, Map attrs) {
0N/A if (last == null) {
0N/A return (attrs != null && attrs.size() > 0);
0N/A }
0N/A return (!last.equals(attrs));
0N/A }
0N/A
0N/A
0N/A // the iterator class associated with this string class
0N/A
0N/A final private class AttributedStringIterator implements AttributedCharacterIterator {
0N/A
0N/A // note on synchronization:
0N/A // we don't synchronize on the iterator, assuming that an iterator is only used in one thread.
0N/A // we do synchronize access to the AttributedString however, since it's more likely to be shared between threads.
0N/A
0N/A // start and end index for our iteration
0N/A private int beginIndex;
0N/A private int endIndex;
0N/A
0N/A // attributes that our client is interested in
0N/A private Attribute[] relevantAttributes;
0N/A
0N/A // the current index for our iteration
0N/A // invariant: beginIndex <= currentIndex <= endIndex
0N/A private int currentIndex;
0N/A
0N/A // information about the run that includes currentIndex
0N/A private int currentRunIndex;
0N/A private int currentRunStart;
0N/A private int currentRunLimit;
0N/A
0N/A // constructor
0N/A AttributedStringIterator(Attribute[] attributes, int beginIndex, int endIndex) {
0N/A
0N/A if (beginIndex < 0 || beginIndex > endIndex || endIndex > length()) {
0N/A throw new IllegalArgumentException("Invalid substring range");
0N/A }
0N/A
0N/A this.beginIndex = beginIndex;
0N/A this.endIndex = endIndex;
0N/A this.currentIndex = beginIndex;
0N/A updateRunInfo();
0N/A if (attributes != null) {
0N/A relevantAttributes = (Attribute[]) attributes.clone();
0N/A }
0N/A }
0N/A
0N/A // Object methods. See documentation in that class.
0N/A
0N/A public boolean equals(Object obj) {
0N/A if (this == obj) {
0N/A return true;
0N/A }
0N/A if (!(obj instanceof AttributedStringIterator)) {
0N/A return false;
0N/A }
0N/A
0N/A AttributedStringIterator that = (AttributedStringIterator) obj;
0N/A
0N/A if (AttributedString.this != that.getString())
0N/A return false;
0N/A if (currentIndex != that.currentIndex || beginIndex != that.beginIndex || endIndex != that.endIndex)
0N/A return false;
0N/A return true;
0N/A }
0N/A
0N/A public int hashCode() {
0N/A return text.hashCode() ^ currentIndex ^ beginIndex ^ endIndex;
0N/A }
0N/A
0N/A public Object clone() {
0N/A try {
0N/A AttributedStringIterator other = (AttributedStringIterator) super.clone();
0N/A return other;
0N/A }
0N/A catch (CloneNotSupportedException e) {
0N/A throw new InternalError();
0N/A }
0N/A }
0N/A
0N/A // CharacterIterator methods. See documentation in that interface.
0N/A
0N/A public char first() {
0N/A return internalSetIndex(beginIndex);
0N/A }
0N/A
0N/A public char last() {
0N/A if (endIndex == beginIndex) {
0N/A return internalSetIndex(endIndex);
0N/A } else {
0N/A return internalSetIndex(endIndex - 1);
0N/A }
0N/A }
0N/A
0N/A public char current() {
0N/A if (currentIndex == endIndex) {
0N/A return DONE;
0N/A } else {
0N/A return charAt(currentIndex);
0N/A }
0N/A }
0N/A
0N/A public char next() {
0N/A if (currentIndex < endIndex) {
0N/A return internalSetIndex(currentIndex + 1);
0N/A }
0N/A else {
0N/A return DONE;
0N/A }
0N/A }
0N/A
0N/A public char previous() {
0N/A if (currentIndex > beginIndex) {
0N/A return internalSetIndex(currentIndex - 1);
0N/A }
0N/A else {
0N/A return DONE;
0N/A }
0N/A }
0N/A
0N/A public char setIndex(int position) {
0N/A if (position < beginIndex || position > endIndex)
0N/A throw new IllegalArgumentException("Invalid index");
0N/A return internalSetIndex(position);
0N/A }
0N/A
0N/A public int getBeginIndex() {
0N/A return beginIndex;
0N/A }
0N/A
0N/A public int getEndIndex() {
0N/A return endIndex;
0N/A }
0N/A
0N/A public int getIndex() {
0N/A return currentIndex;
0N/A }
0N/A
0N/A // AttributedCharacterIterator methods. See documentation in that interface.
0N/A
0N/A public int getRunStart() {
0N/A return currentRunStart;
0N/A }
0N/A
0N/A public int getRunStart(Attribute attribute) {
0N/A if (currentRunStart == beginIndex || currentRunIndex == -1) {
0N/A return currentRunStart;
0N/A } else {
0N/A Object value = getAttribute(attribute);
0N/A int runStart = currentRunStart;
0N/A int runIndex = currentRunIndex;
0N/A while (runStart > beginIndex &&
0N/A valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex - 1))) {
0N/A runIndex--;
0N/A runStart = runStarts[runIndex];
0N/A }
0N/A if (runStart < beginIndex) {
0N/A runStart = beginIndex;
0N/A }
0N/A return runStart;
0N/A }
0N/A }
0N/A
0N/A public int getRunStart(Set<? extends Attribute> attributes) {
0N/A if (currentRunStart == beginIndex || currentRunIndex == -1) {
0N/A return currentRunStart;
0N/A } else {
0N/A int runStart = currentRunStart;
0N/A int runIndex = currentRunIndex;
0N/A while (runStart > beginIndex &&
0N/A AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex - 1)) {
0N/A runIndex--;
0N/A runStart = runStarts[runIndex];
0N/A }
0N/A if (runStart < beginIndex) {
0N/A runStart = beginIndex;
0N/A }
0N/A return runStart;
0N/A }
0N/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;
0N/A } else {
0N/A Object value = getAttribute(attribute);
0N/A int runLimit = currentRunLimit;
0N/A int runIndex = currentRunIndex;
0N/A while (runLimit < endIndex &&
0N/A valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex + 1))) {
0N/A runIndex++;
0N/A runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex;
0N/A }
0N/A if (runLimit > endIndex) {
0N/A runLimit = endIndex;
0N/A }
0N/A return runLimit;
0N/A }
0N/A }
0N/A
0N/A public int getRunLimit(Set<? extends Attribute> attributes) {
0N/A if (currentRunLimit == endIndex || currentRunIndex == -1) {
0N/A return currentRunLimit;
0N/A } else {
0N/A int runLimit = currentRunLimit;
0N/A int runIndex = currentRunIndex;
0N/A while (runLimit < endIndex &&
0N/A AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex + 1)) {
0N/A runIndex++;
0N/A runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex;
0N/A }
0N/A if (runLimit > endIndex) {
0N/A runLimit = endIndex;
0N/A }
0N/A return runLimit;
0N/A }
0N/A }
0N/A
0N/A public Map<Attribute,Object> getAttributes() {
0N/A if (runAttributes == null || currentRunIndex == -1 || runAttributes[currentRunIndex] == null) {
0N/A // ??? would be nice to return null, but current spec doesn't allow it
0N/A // returning Hashtable saves AttributeMap from dealing with emptiness
0N/A return new Hashtable();
0N/A }
0N/A return new AttributeMap(currentRunIndex, beginIndex, endIndex);
0N/A }
0N/A
0N/A public Set<Attribute> getAllAttributeKeys() {
0N/A // ??? This should screen out attribute keys that aren't relevant to the client
0N/A if (runAttributes == null) {
0N/A // ??? would be nice to return null, but current spec doesn't allow it
0N/A // returning HashSet saves us from dealing with emptiness
0N/A return new HashSet();
0N/A }
0N/A synchronized (AttributedString.this) {
0N/A // ??? should try to create this only once, then update if necessary,
0N/A // and give callers read-only view
0N/A Set keys = new HashSet();
0N/A int i = 0;
0N/A while (i < runCount) {
0N/A if (runStarts[i] < endIndex && (i == runCount - 1 || runStarts[i + 1] > beginIndex)) {
0N/A Vector currentRunAttributes = runAttributes[i];
0N/A if (currentRunAttributes != null) {
0N/A int j = currentRunAttributes.size();
0N/A while (j-- > 0) {
0N/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 }
0N/A
0N/A public Object getAttribute(Attribute attribute) {
0N/A int runIndex = currentRunIndex;
0N/A if (runIndex < 0) {
0N/A return null;
0N/A }
0N/A return AttributedString.this.getAttributeCheckRange(attribute, runIndex, beginIndex, endIndex);
0N/A }
0N/A
0N/A // internally used methods
0N/A
0N/A private AttributedString getString() {
0N/A return AttributedString.this;
0N/A }
0N/A
0N/A // set the current index, update information about the current run if necessary,
0N/A // return the character at the current index
0N/A private char internalSetIndex(int position) {
0N/A currentIndex = position;
0N/A if (position < currentRunStart || position >= currentRunLimit) {
0N/A updateRunInfo();
0N/A }
0N/A if (currentIndex == endIndex) {
0N/A return DONE;
0N/A } else {
0N/A return charAt(position);
0N/A }
0N/A }
0N/A
0N/A // update the information about the current run
0N/A private void updateRunInfo() {
0N/A if (currentIndex == endIndex) {
0N/A currentRunStart = currentRunLimit = endIndex;
0N/A currentRunIndex = -1;
0N/A } else {
0N/A synchronized (AttributedString.this) {
0N/A int runIndex = -1;
0N/A while (runIndex < runCount - 1 && runStarts[runIndex + 1] <= currentIndex)
0N/A runIndex++;
0N/A currentRunIndex = runIndex;
0N/A if (runIndex >= 0) {
0N/A currentRunStart = runStarts[runIndex];
0N/A if (currentRunStart < beginIndex)
0N/A currentRunStart = beginIndex;
0N/A }
0N/A else {
0N/A currentRunStart = beginIndex;
0N/A }
0N/A if (runIndex < runCount - 1) {
0N/A currentRunLimit = runStarts[runIndex + 1];
0N/A if (currentRunLimit > endIndex)
0N/A currentRunLimit = endIndex;
0N/A }
0N/A else {
0N/A currentRunLimit = endIndex;
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A }
0N/A
0N/A // the map class associated with this string class, giving access to the attributes of one run
0N/A
0N/A final private class AttributeMap extends AbstractMap<Attribute,Object> {
0N/A
0N/A int runIndex;
0N/A int beginIndex;
0N/A int endIndex;
0N/A
0N/A AttributeMap(int runIndex, int beginIndex, int endIndex) {
0N/A this.runIndex = runIndex;
0N/A this.beginIndex = beginIndex;
0N/A this.endIndex = endIndex;
0N/A }
0N/A
0N/A public Set entrySet() {
0N/A HashSet set = new HashSet();
0N/A synchronized (AttributedString.this) {
0N/A int size = runAttributes[runIndex].size();
0N/A for (int i = 0; i < size; i++) {
0N/A Attribute key = (Attribute) runAttributes[runIndex].get(i);
0N/A Object value = runAttributeValues[runIndex].get(i);
0N/A if (value instanceof Annotation) {
0N/A value = AttributedString.this.getAttributeCheckRange(key,
0N/A runIndex, beginIndex, endIndex);
0N/A if (value == null) {
0N/A continue;
0N/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;
0N/A this.value = value;
0N/A }
0N/A
0N/A public boolean equals(Object o) {
0N/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;
0N/A }
0N/A
0N/A public Object getValue() {
0N/A return value;
0N/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() {
0N/A return key.toString()+"="+value.toString();
0N/A }
0N/A}