1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/rt/emul/compact/src/main/java/java/text/AttributedString.java Thu Oct 03 15:40:35 2013 +0200
1.3 @@ -0,0 +1,1120 @@
1.4 +/*
1.5 + * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved.
1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
1.7 + *
1.8 + * This code is free software; you can redistribute it and/or modify it
1.9 + * under the terms of the GNU General Public License version 2 only, as
1.10 + * published by the Free Software Foundation. Oracle designates this
1.11 + * particular file as subject to the "Classpath" exception as provided
1.12 + * by Oracle in the LICENSE file that accompanied this code.
1.13 + *
1.14 + * This code is distributed in the hope that it will be useful, but WITHOUT
1.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1.16 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
1.17 + * version 2 for more details (a copy is included in the LICENSE file that
1.18 + * accompanied this code).
1.19 + *
1.20 + * You should have received a copy of the GNU General Public License version
1.21 + * 2 along with this work; if not, write to the Free Software Foundation,
1.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
1.23 + *
1.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
1.25 + * or visit www.oracle.com if you need additional information or have any
1.26 + * questions.
1.27 + */
1.28 +
1.29 +package java.text;
1.30 +
1.31 +import java.util.*;
1.32 +import java.text.AttributedCharacterIterator.Attribute;
1.33 +
1.34 +/**
1.35 + * An AttributedString holds text and related attribute information. It
1.36 + * may be used as the actual data storage in some cases where a text
1.37 + * reader wants to access attributed text through the AttributedCharacterIterator
1.38 + * interface.
1.39 + *
1.40 + * <p>
1.41 + * An attribute is a key/value pair, identified by the key. No two
1.42 + * attributes on a given character can have the same key.
1.43 + *
1.44 + * <p>The values for an attribute are immutable, or must not be mutated
1.45 + * by clients or storage. They are always passed by reference, and not
1.46 + * cloned.
1.47 + *
1.48 + * @see AttributedCharacterIterator
1.49 + * @see Annotation
1.50 + * @since 1.2
1.51 + */
1.52 +
1.53 +public class AttributedString {
1.54 +
1.55 + // since there are no vectors of int, we have to use arrays.
1.56 + // We allocate them in chunks of 10 elements so we don't have to allocate all the time.
1.57 + private static final int ARRAY_SIZE_INCREMENT = 10;
1.58 +
1.59 + // field holding the text
1.60 + String text;
1.61 +
1.62 + // fields holding run attribute information
1.63 + // run attributes are organized by run
1.64 + int runArraySize; // current size of the arrays
1.65 + int runCount; // actual number of runs, <= runArraySize
1.66 + int runStarts[]; // start index for each run
1.67 + Vector runAttributes[]; // vector of attribute keys for each run
1.68 + Vector runAttributeValues[]; // parallel vector of attribute values for each run
1.69 +
1.70 + /**
1.71 + * Constructs an AttributedString instance with the given
1.72 + * AttributedCharacterIterators.
1.73 + *
1.74 + * @param iterators AttributedCharacterIterators to construct
1.75 + * AttributedString from.
1.76 + * @throws NullPointerException if iterators is null
1.77 + */
1.78 + AttributedString(AttributedCharacterIterator[] iterators) {
1.79 + if (iterators == null) {
1.80 + throw new NullPointerException("Iterators must not be null");
1.81 + }
1.82 + if (iterators.length == 0) {
1.83 + text = "";
1.84 + }
1.85 + else {
1.86 + // Build the String contents
1.87 + StringBuffer buffer = new StringBuffer();
1.88 + for (int counter = 0; counter < iterators.length; counter++) {
1.89 + appendContents(buffer, iterators[counter]);
1.90 + }
1.91 +
1.92 + text = buffer.toString();
1.93 +
1.94 + if (text.length() > 0) {
1.95 + // Determine the runs, creating a new run when the attributes
1.96 + // differ.
1.97 + int offset = 0;
1.98 + Map last = null;
1.99 +
1.100 + for (int counter = 0; counter < iterators.length; counter++) {
1.101 + AttributedCharacterIterator iterator = iterators[counter];
1.102 + int start = iterator.getBeginIndex();
1.103 + int end = iterator.getEndIndex();
1.104 + int index = start;
1.105 +
1.106 + while (index < end) {
1.107 + iterator.setIndex(index);
1.108 +
1.109 + Map attrs = iterator.getAttributes();
1.110 +
1.111 + if (mapsDiffer(last, attrs)) {
1.112 + setAttributes(attrs, index - start + offset);
1.113 + }
1.114 + last = attrs;
1.115 + index = iterator.getRunLimit();
1.116 + }
1.117 + offset += (end - start);
1.118 + }
1.119 + }
1.120 + }
1.121 + }
1.122 +
1.123 + /**
1.124 + * Constructs an AttributedString instance with the given text.
1.125 + * @param text The text for this attributed string.
1.126 + * @exception NullPointerException if <code>text</code> is null.
1.127 + */
1.128 + public AttributedString(String text) {
1.129 + if (text == null) {
1.130 + throw new NullPointerException();
1.131 + }
1.132 + this.text = text;
1.133 + }
1.134 +
1.135 + /**
1.136 + * Constructs an AttributedString instance with the given text and attributes.
1.137 + * @param text The text for this attributed string.
1.138 + * @param attributes The attributes that apply to the entire string.
1.139 + * @exception NullPointerException if <code>text</code> or
1.140 + * <code>attributes</code> is null.
1.141 + * @exception IllegalArgumentException if the text has length 0
1.142 + * and the attributes parameter is not an empty Map (attributes
1.143 + * cannot be applied to a 0-length range).
1.144 + */
1.145 + public AttributedString(String text,
1.146 + Map<? extends Attribute, ?> attributes)
1.147 + {
1.148 + if (text == null || attributes == null) {
1.149 + throw new NullPointerException();
1.150 + }
1.151 + this.text = text;
1.152 +
1.153 + if (text.length() == 0) {
1.154 + if (attributes.isEmpty())
1.155 + return;
1.156 + throw new IllegalArgumentException("Can't add attribute to 0-length text");
1.157 + }
1.158 +
1.159 + int attributeCount = attributes.size();
1.160 + if (attributeCount > 0) {
1.161 + createRunAttributeDataVectors();
1.162 + Vector newRunAttributes = new Vector(attributeCount);
1.163 + Vector newRunAttributeValues = new Vector(attributeCount);
1.164 + runAttributes[0] = newRunAttributes;
1.165 + runAttributeValues[0] = newRunAttributeValues;
1.166 + Iterator iterator = attributes.entrySet().iterator();
1.167 + while (iterator.hasNext()) {
1.168 + Map.Entry entry = (Map.Entry) iterator.next();
1.169 + newRunAttributes.addElement(entry.getKey());
1.170 + newRunAttributeValues.addElement(entry.getValue());
1.171 + }
1.172 + }
1.173 + }
1.174 +
1.175 + /**
1.176 + * Constructs an AttributedString instance with the given attributed
1.177 + * text represented by AttributedCharacterIterator.
1.178 + * @param text The text for this attributed string.
1.179 + * @exception NullPointerException if <code>text</code> is null.
1.180 + */
1.181 + public AttributedString(AttributedCharacterIterator text) {
1.182 + // If performance is critical, this constructor should be
1.183 + // implemented here rather than invoking the constructor for a
1.184 + // subrange. We can avoid some range checking in the loops.
1.185 + this(text, text.getBeginIndex(), text.getEndIndex(), null);
1.186 + }
1.187 +
1.188 + /**
1.189 + * Constructs an AttributedString instance with the subrange of
1.190 + * the given attributed text represented by
1.191 + * AttributedCharacterIterator. If the given range produces an
1.192 + * empty text, all attributes will be discarded. Note that any
1.193 + * attributes wrapped by an Annotation object are discarded for a
1.194 + * subrange of the original attribute range.
1.195 + *
1.196 + * @param text The text for this attributed string.
1.197 + * @param beginIndex Index of the first character of the range.
1.198 + * @param endIndex Index of the character following the last character
1.199 + * of the range.
1.200 + * @exception NullPointerException if <code>text</code> is null.
1.201 + * @exception IllegalArgumentException if the subrange given by
1.202 + * beginIndex and endIndex is out of the text range.
1.203 + * @see java.text.Annotation
1.204 + */
1.205 + public AttributedString(AttributedCharacterIterator text,
1.206 + int beginIndex,
1.207 + int endIndex) {
1.208 + this(text, beginIndex, endIndex, null);
1.209 + }
1.210 +
1.211 + /**
1.212 + * Constructs an AttributedString instance with the subrange of
1.213 + * the given attributed text represented by
1.214 + * AttributedCharacterIterator. Only attributes that match the
1.215 + * given attributes will be incorporated into the instance. If the
1.216 + * given range produces an empty text, all attributes will be
1.217 + * discarded. Note that any attributes wrapped by an Annotation
1.218 + * object are discarded for a subrange of the original attribute
1.219 + * range.
1.220 + *
1.221 + * @param text The text for this attributed string.
1.222 + * @param beginIndex Index of the first character of the range.
1.223 + * @param endIndex Index of the character following the last character
1.224 + * of the range.
1.225 + * @param attributes Specifies attributes to be extracted
1.226 + * from the text. If null is specified, all available attributes will
1.227 + * be used.
1.228 + * @exception NullPointerException if <code>text</code> is null.
1.229 + * @exception IllegalArgumentException if the subrange given by
1.230 + * beginIndex and endIndex is out of the text range.
1.231 + * @see java.text.Annotation
1.232 + */
1.233 + public AttributedString(AttributedCharacterIterator text,
1.234 + int beginIndex,
1.235 + int endIndex,
1.236 + Attribute[] attributes) {
1.237 + if (text == null) {
1.238 + throw new NullPointerException();
1.239 + }
1.240 +
1.241 + // Validate the given subrange
1.242 + int textBeginIndex = text.getBeginIndex();
1.243 + int textEndIndex = text.getEndIndex();
1.244 + if (beginIndex < textBeginIndex || endIndex > textEndIndex || beginIndex > endIndex)
1.245 + throw new IllegalArgumentException("Invalid substring range");
1.246 +
1.247 + // Copy the given string
1.248 + StringBuffer textBuffer = new StringBuffer();
1.249 + text.setIndex(beginIndex);
1.250 + for (char c = text.current(); text.getIndex() < endIndex; c = text.next())
1.251 + textBuffer.append(c);
1.252 + this.text = textBuffer.toString();
1.253 +
1.254 + if (beginIndex == endIndex)
1.255 + return;
1.256 +
1.257 + // Select attribute keys to be taken care of
1.258 + HashSet keys = new HashSet();
1.259 + if (attributes == null) {
1.260 + keys.addAll(text.getAllAttributeKeys());
1.261 + } else {
1.262 + for (int i = 0; i < attributes.length; i++)
1.263 + keys.add(attributes[i]);
1.264 + keys.retainAll(text.getAllAttributeKeys());
1.265 + }
1.266 + if (keys.isEmpty())
1.267 + return;
1.268 +
1.269 + // Get and set attribute runs for each attribute name. Need to
1.270 + // scan from the top of the text so that we can discard any
1.271 + // Annotation that is no longer applied to a subset text segment.
1.272 + Iterator itr = keys.iterator();
1.273 + while (itr.hasNext()) {
1.274 + Attribute attributeKey = (Attribute)itr.next();
1.275 + text.setIndex(textBeginIndex);
1.276 + while (text.getIndex() < endIndex) {
1.277 + int start = text.getRunStart(attributeKey);
1.278 + int limit = text.getRunLimit(attributeKey);
1.279 + Object value = text.getAttribute(attributeKey);
1.280 +
1.281 + if (value != null) {
1.282 + if (value instanceof Annotation) {
1.283 + if (start >= beginIndex && limit <= endIndex) {
1.284 + addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex);
1.285 + } else {
1.286 + if (limit > endIndex)
1.287 + break;
1.288 + }
1.289 + } else {
1.290 + // if the run is beyond the given (subset) range, we
1.291 + // don't need to process further.
1.292 + if (start >= endIndex)
1.293 + break;
1.294 + if (limit > beginIndex) {
1.295 + // attribute is applied to any subrange
1.296 + if (start < beginIndex)
1.297 + start = beginIndex;
1.298 + if (limit > endIndex)
1.299 + limit = endIndex;
1.300 + if (start != limit) {
1.301 + addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex);
1.302 + }
1.303 + }
1.304 + }
1.305 + }
1.306 + text.setIndex(limit);
1.307 + }
1.308 + }
1.309 + }
1.310 +
1.311 + /**
1.312 + * Adds an attribute to the entire string.
1.313 + * @param attribute the attribute key
1.314 + * @param value the value of the attribute; may be null
1.315 + * @exception NullPointerException if <code>attribute</code> is null.
1.316 + * @exception IllegalArgumentException if the AttributedString has length 0
1.317 + * (attributes cannot be applied to a 0-length range).
1.318 + */
1.319 + public void addAttribute(Attribute attribute, Object value) {
1.320 +
1.321 + if (attribute == null) {
1.322 + throw new NullPointerException();
1.323 + }
1.324 +
1.325 + int len = length();
1.326 + if (len == 0) {
1.327 + throw new IllegalArgumentException("Can't add attribute to 0-length text");
1.328 + }
1.329 +
1.330 + addAttributeImpl(attribute, value, 0, len);
1.331 + }
1.332 +
1.333 + /**
1.334 + * Adds an attribute to a subrange of the string.
1.335 + * @param attribute the attribute key
1.336 + * @param value The value of the attribute. May be null.
1.337 + * @param beginIndex Index of the first character of the range.
1.338 + * @param endIndex Index of the character following the last character of the range.
1.339 + * @exception NullPointerException if <code>attribute</code> is null.
1.340 + * @exception IllegalArgumentException if beginIndex is less then 0, endIndex is
1.341 + * greater than the length of the string, or beginIndex and endIndex together don't
1.342 + * define a non-empty subrange of the string.
1.343 + */
1.344 + public void addAttribute(Attribute attribute, Object value,
1.345 + int beginIndex, int endIndex) {
1.346 +
1.347 + if (attribute == null) {
1.348 + throw new NullPointerException();
1.349 + }
1.350 +
1.351 + if (beginIndex < 0 || endIndex > length() || beginIndex >= endIndex) {
1.352 + throw new IllegalArgumentException("Invalid substring range");
1.353 + }
1.354 +
1.355 + addAttributeImpl(attribute, value, beginIndex, endIndex);
1.356 + }
1.357 +
1.358 + /**
1.359 + * Adds a set of attributes to a subrange of the string.
1.360 + * @param attributes The attributes to be added to the string.
1.361 + * @param beginIndex Index of the first character of the range.
1.362 + * @param endIndex Index of the character following the last
1.363 + * character of the range.
1.364 + * @exception NullPointerException if <code>attributes</code> is null.
1.365 + * @exception IllegalArgumentException if beginIndex is less then
1.366 + * 0, endIndex is greater than the length of the string, or
1.367 + * beginIndex and endIndex together don't define a non-empty
1.368 + * subrange of the string and the attributes parameter is not an
1.369 + * empty Map.
1.370 + */
1.371 + public void addAttributes(Map<? extends Attribute, ?> attributes,
1.372 + int beginIndex, int endIndex)
1.373 + {
1.374 + if (attributes == null) {
1.375 + throw new NullPointerException();
1.376 + }
1.377 +
1.378 + if (beginIndex < 0 || endIndex > length() || beginIndex > endIndex) {
1.379 + throw new IllegalArgumentException("Invalid substring range");
1.380 + }
1.381 + if (beginIndex == endIndex) {
1.382 + if (attributes.isEmpty())
1.383 + return;
1.384 + throw new IllegalArgumentException("Can't add attribute to 0-length text");
1.385 + }
1.386 +
1.387 + // make sure we have run attribute data vectors
1.388 + if (runCount == 0) {
1.389 + createRunAttributeDataVectors();
1.390 + }
1.391 +
1.392 + // break up runs if necessary
1.393 + int beginRunIndex = ensureRunBreak(beginIndex);
1.394 + int endRunIndex = ensureRunBreak(endIndex);
1.395 +
1.396 + Iterator iterator = attributes.entrySet().iterator();
1.397 + while (iterator.hasNext()) {
1.398 + Map.Entry entry = (Map.Entry) iterator.next();
1.399 + addAttributeRunData((Attribute) entry.getKey(), entry.getValue(), beginRunIndex, endRunIndex);
1.400 + }
1.401 + }
1.402 +
1.403 + private synchronized void addAttributeImpl(Attribute attribute, Object value,
1.404 + int beginIndex, int endIndex) {
1.405 +
1.406 + // make sure we have run attribute data vectors
1.407 + if (runCount == 0) {
1.408 + createRunAttributeDataVectors();
1.409 + }
1.410 +
1.411 + // break up runs if necessary
1.412 + int beginRunIndex = ensureRunBreak(beginIndex);
1.413 + int endRunIndex = ensureRunBreak(endIndex);
1.414 +
1.415 + addAttributeRunData(attribute, value, beginRunIndex, endRunIndex);
1.416 + }
1.417 +
1.418 + private final void createRunAttributeDataVectors() {
1.419 + // use temporary variables so things remain consistent in case of an exception
1.420 + int newRunStarts[] = new int[ARRAY_SIZE_INCREMENT];
1.421 + Vector newRunAttributes[] = new Vector[ARRAY_SIZE_INCREMENT];
1.422 + Vector newRunAttributeValues[] = new Vector[ARRAY_SIZE_INCREMENT];
1.423 + runStarts = newRunStarts;
1.424 + runAttributes = newRunAttributes;
1.425 + runAttributeValues = newRunAttributeValues;
1.426 + runArraySize = ARRAY_SIZE_INCREMENT;
1.427 + runCount = 1; // assume initial run starting at index 0
1.428 + }
1.429 +
1.430 + // ensure there's a run break at offset, return the index of the run
1.431 + private final int ensureRunBreak(int offset) {
1.432 + return ensureRunBreak(offset, true);
1.433 + }
1.434 +
1.435 + /**
1.436 + * Ensures there is a run break at offset, returning the index of
1.437 + * the run. If this results in splitting a run, two things can happen:
1.438 + * <ul>
1.439 + * <li>If copyAttrs is true, the attributes from the existing run
1.440 + * will be placed in both of the newly created runs.
1.441 + * <li>If copyAttrs is false, the attributes from the existing run
1.442 + * will NOT be copied to the run to the right (>= offset) of the break,
1.443 + * but will exist on the run to the left (< offset).
1.444 + * </ul>
1.445 + */
1.446 + private final int ensureRunBreak(int offset, boolean copyAttrs) {
1.447 + if (offset == length()) {
1.448 + return runCount;
1.449 + }
1.450 +
1.451 + // search for the run index where this offset should be
1.452 + int runIndex = 0;
1.453 + while (runIndex < runCount && runStarts[runIndex] < offset) {
1.454 + runIndex++;
1.455 + }
1.456 +
1.457 + // if the offset is at a run start already, we're done
1.458 + if (runIndex < runCount && runStarts[runIndex] == offset) {
1.459 + return runIndex;
1.460 + }
1.461 +
1.462 + // we'll have to break up a run
1.463 + // first, make sure we have enough space in our arrays
1.464 + if (runCount == runArraySize) {
1.465 + int newArraySize = runArraySize + ARRAY_SIZE_INCREMENT;
1.466 + int newRunStarts[] = new int[newArraySize];
1.467 + Vector newRunAttributes[] = new Vector[newArraySize];
1.468 + Vector newRunAttributeValues[] = new Vector[newArraySize];
1.469 + for (int i = 0; i < runArraySize; i++) {
1.470 + newRunStarts[i] = runStarts[i];
1.471 + newRunAttributes[i] = runAttributes[i];
1.472 + newRunAttributeValues[i] = runAttributeValues[i];
1.473 + }
1.474 + runStarts = newRunStarts;
1.475 + runAttributes = newRunAttributes;
1.476 + runAttributeValues = newRunAttributeValues;
1.477 + runArraySize = newArraySize;
1.478 + }
1.479 +
1.480 + // make copies of the attribute information of the old run that the new one used to be part of
1.481 + // use temporary variables so things remain consistent in case of an exception
1.482 + Vector newRunAttributes = null;
1.483 + Vector newRunAttributeValues = null;
1.484 +
1.485 + if (copyAttrs) {
1.486 + Vector oldRunAttributes = runAttributes[runIndex - 1];
1.487 + Vector oldRunAttributeValues = runAttributeValues[runIndex - 1];
1.488 + if (oldRunAttributes != null) {
1.489 + newRunAttributes = (Vector) oldRunAttributes.clone();
1.490 + }
1.491 + if (oldRunAttributeValues != null) {
1.492 + newRunAttributeValues = (Vector) oldRunAttributeValues.clone();
1.493 + }
1.494 + }
1.495 +
1.496 + // now actually break up the run
1.497 + runCount++;
1.498 + for (int i = runCount - 1; i > runIndex; i--) {
1.499 + runStarts[i] = runStarts[i - 1];
1.500 + runAttributes[i] = runAttributes[i - 1];
1.501 + runAttributeValues[i] = runAttributeValues[i - 1];
1.502 + }
1.503 + runStarts[runIndex] = offset;
1.504 + runAttributes[runIndex] = newRunAttributes;
1.505 + runAttributeValues[runIndex] = newRunAttributeValues;
1.506 +
1.507 + return runIndex;
1.508 + }
1.509 +
1.510 + // add the attribute attribute/value to all runs where beginRunIndex <= runIndex < endRunIndex
1.511 + private void addAttributeRunData(Attribute attribute, Object value,
1.512 + int beginRunIndex, int endRunIndex) {
1.513 +
1.514 + for (int i = beginRunIndex; i < endRunIndex; i++) {
1.515 + int keyValueIndex = -1; // index of key and value in our vectors; assume we don't have an entry yet
1.516 + if (runAttributes[i] == null) {
1.517 + Vector newRunAttributes = new Vector();
1.518 + Vector newRunAttributeValues = new Vector();
1.519 + runAttributes[i] = newRunAttributes;
1.520 + runAttributeValues[i] = newRunAttributeValues;
1.521 + } else {
1.522 + // check whether we have an entry already
1.523 + keyValueIndex = runAttributes[i].indexOf(attribute);
1.524 + }
1.525 +
1.526 + if (keyValueIndex == -1) {
1.527 + // create new entry
1.528 + int oldSize = runAttributes[i].size();
1.529 + runAttributes[i].addElement(attribute);
1.530 + try {
1.531 + runAttributeValues[i].addElement(value);
1.532 + }
1.533 + catch (Exception e) {
1.534 + runAttributes[i].setSize(oldSize);
1.535 + runAttributeValues[i].setSize(oldSize);
1.536 + }
1.537 + } else {
1.538 + // update existing entry
1.539 + runAttributeValues[i].set(keyValueIndex, value);
1.540 + }
1.541 + }
1.542 + }
1.543 +
1.544 + /**
1.545 + * Creates an AttributedCharacterIterator instance that provides access to the entire contents of
1.546 + * this string.
1.547 + *
1.548 + * @return An iterator providing access to the text and its attributes.
1.549 + */
1.550 + public AttributedCharacterIterator getIterator() {
1.551 + return getIterator(null, 0, length());
1.552 + }
1.553 +
1.554 + /**
1.555 + * Creates an AttributedCharacterIterator instance that provides access to
1.556 + * selected contents of this string.
1.557 + * Information about attributes not listed in attributes that the
1.558 + * implementor may have need not be made accessible through the iterator.
1.559 + * If the list is null, all available attribute information should be made
1.560 + * accessible.
1.561 + *
1.562 + * @param attributes a list of attributes that the client is interested in
1.563 + * @return an iterator providing access to the entire text and its selected attributes
1.564 + */
1.565 + public AttributedCharacterIterator getIterator(Attribute[] attributes) {
1.566 + return getIterator(attributes, 0, length());
1.567 + }
1.568 +
1.569 + /**
1.570 + * Creates an AttributedCharacterIterator instance that provides access to
1.571 + * selected contents of this string.
1.572 + * Information about attributes not listed in attributes that the
1.573 + * implementor may have need not be made accessible through the iterator.
1.574 + * If the list is null, all available attribute information should be made
1.575 + * accessible.
1.576 + *
1.577 + * @param attributes a list of attributes that the client is interested in
1.578 + * @param beginIndex the index of the first character
1.579 + * @param endIndex the index of the character following the last character
1.580 + * @return an iterator providing access to the text and its attributes
1.581 + * @exception IllegalArgumentException if beginIndex is less then 0,
1.582 + * endIndex is greater than the length of the string, or beginIndex is
1.583 + * greater than endIndex.
1.584 + */
1.585 + public AttributedCharacterIterator getIterator(Attribute[] attributes, int beginIndex, int endIndex) {
1.586 + return new AttributedStringIterator(attributes, beginIndex, endIndex);
1.587 + }
1.588 +
1.589 + // all (with the exception of length) reading operations are private,
1.590 + // since AttributedString instances are accessed through iterators.
1.591 +
1.592 + // length is package private so that CharacterIteratorFieldDelegate can
1.593 + // access it without creating an AttributedCharacterIterator.
1.594 + int length() {
1.595 + return text.length();
1.596 + }
1.597 +
1.598 + private char charAt(int index) {
1.599 + return text.charAt(index);
1.600 + }
1.601 +
1.602 + private synchronized Object getAttribute(Attribute attribute, int runIndex) {
1.603 + Vector currentRunAttributes = runAttributes[runIndex];
1.604 + Vector currentRunAttributeValues = runAttributeValues[runIndex];
1.605 + if (currentRunAttributes == null) {
1.606 + return null;
1.607 + }
1.608 + int attributeIndex = currentRunAttributes.indexOf(attribute);
1.609 + if (attributeIndex != -1) {
1.610 + return currentRunAttributeValues.elementAt(attributeIndex);
1.611 + }
1.612 + else {
1.613 + return null;
1.614 + }
1.615 + }
1.616 +
1.617 + // gets an attribute value, but returns an annotation only if it's range does not extend outside the range beginIndex..endIndex
1.618 + private Object getAttributeCheckRange(Attribute attribute, int runIndex, int beginIndex, int endIndex) {
1.619 + Object value = getAttribute(attribute, runIndex);
1.620 + if (value instanceof Annotation) {
1.621 + // need to check whether the annotation's range extends outside the iterator's range
1.622 + if (beginIndex > 0) {
1.623 + int currIndex = runIndex;
1.624 + int runStart = runStarts[currIndex];
1.625 + while (runStart >= beginIndex &&
1.626 + valuesMatch(value, getAttribute(attribute, currIndex - 1))) {
1.627 + currIndex--;
1.628 + runStart = runStarts[currIndex];
1.629 + }
1.630 + if (runStart < beginIndex) {
1.631 + // annotation's range starts before iterator's range
1.632 + return null;
1.633 + }
1.634 + }
1.635 + int textLength = length();
1.636 + if (endIndex < textLength) {
1.637 + int currIndex = runIndex;
1.638 + int runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength;
1.639 + while (runLimit <= endIndex &&
1.640 + valuesMatch(value, getAttribute(attribute, currIndex + 1))) {
1.641 + currIndex++;
1.642 + runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength;
1.643 + }
1.644 + if (runLimit > endIndex) {
1.645 + // annotation's range ends after iterator's range
1.646 + return null;
1.647 + }
1.648 + }
1.649 + // annotation's range is subrange of iterator's range,
1.650 + // so we can return the value
1.651 + }
1.652 + return value;
1.653 + }
1.654 +
1.655 + // returns whether all specified attributes have equal values in the runs with the given indices
1.656 + private boolean attributeValuesMatch(Set attributes, int runIndex1, int runIndex2) {
1.657 + Iterator iterator = attributes.iterator();
1.658 + while (iterator.hasNext()) {
1.659 + Attribute key = (Attribute) iterator.next();
1.660 + if (!valuesMatch(getAttribute(key, runIndex1), getAttribute(key, runIndex2))) {
1.661 + return false;
1.662 + }
1.663 + }
1.664 + return true;
1.665 + }
1.666 +
1.667 + // returns whether the two objects are either both null or equal
1.668 + private final static boolean valuesMatch(Object value1, Object value2) {
1.669 + if (value1 == null) {
1.670 + return value2 == null;
1.671 + } else {
1.672 + return value1.equals(value2);
1.673 + }
1.674 + }
1.675 +
1.676 + /**
1.677 + * Appends the contents of the CharacterIterator iterator into the
1.678 + * StringBuffer buf.
1.679 + */
1.680 + private final void appendContents(StringBuffer buf,
1.681 + CharacterIterator iterator) {
1.682 + int index = iterator.getBeginIndex();
1.683 + int end = iterator.getEndIndex();
1.684 +
1.685 + while (index < end) {
1.686 + iterator.setIndex(index++);
1.687 + buf.append(iterator.current());
1.688 + }
1.689 + }
1.690 +
1.691 + /**
1.692 + * Sets the attributes for the range from offset to the next run break
1.693 + * (typically the end of the text) to the ones specified in attrs.
1.694 + * This is only meant to be called from the constructor!
1.695 + */
1.696 + private void setAttributes(Map attrs, int offset) {
1.697 + if (runCount == 0) {
1.698 + createRunAttributeDataVectors();
1.699 + }
1.700 +
1.701 + int index = ensureRunBreak(offset, false);
1.702 + int size;
1.703 +
1.704 + if (attrs != null && (size = attrs.size()) > 0) {
1.705 + Vector runAttrs = new Vector(size);
1.706 + Vector runValues = new Vector(size);
1.707 + Iterator iterator = attrs.entrySet().iterator();
1.708 +
1.709 + while (iterator.hasNext()) {
1.710 + Map.Entry entry = (Map.Entry)iterator.next();
1.711 +
1.712 + runAttrs.add(entry.getKey());
1.713 + runValues.add(entry.getValue());
1.714 + }
1.715 + runAttributes[index] = runAttrs;
1.716 + runAttributeValues[index] = runValues;
1.717 + }
1.718 + }
1.719 +
1.720 + /**
1.721 + * Returns true if the attributes specified in last and attrs differ.
1.722 + */
1.723 + private static boolean mapsDiffer(Map last, Map attrs) {
1.724 + if (last == null) {
1.725 + return (attrs != null && attrs.size() > 0);
1.726 + }
1.727 + return (!last.equals(attrs));
1.728 + }
1.729 +
1.730 +
1.731 + // the iterator class associated with this string class
1.732 +
1.733 + final private class AttributedStringIterator implements AttributedCharacterIterator {
1.734 +
1.735 + // note on synchronization:
1.736 + // we don't synchronize on the iterator, assuming that an iterator is only used in one thread.
1.737 + // we do synchronize access to the AttributedString however, since it's more likely to be shared between threads.
1.738 +
1.739 + // start and end index for our iteration
1.740 + private int beginIndex;
1.741 + private int endIndex;
1.742 +
1.743 + // attributes that our client is interested in
1.744 + private Attribute[] relevantAttributes;
1.745 +
1.746 + // the current index for our iteration
1.747 + // invariant: beginIndex <= currentIndex <= endIndex
1.748 + private int currentIndex;
1.749 +
1.750 + // information about the run that includes currentIndex
1.751 + private int currentRunIndex;
1.752 + private int currentRunStart;
1.753 + private int currentRunLimit;
1.754 +
1.755 + // constructor
1.756 + AttributedStringIterator(Attribute[] attributes, int beginIndex, int endIndex) {
1.757 +
1.758 + if (beginIndex < 0 || beginIndex > endIndex || endIndex > length()) {
1.759 + throw new IllegalArgumentException("Invalid substring range");
1.760 + }
1.761 +
1.762 + this.beginIndex = beginIndex;
1.763 + this.endIndex = endIndex;
1.764 + this.currentIndex = beginIndex;
1.765 + updateRunInfo();
1.766 + if (attributes != null) {
1.767 + relevantAttributes = (Attribute[]) attributes.clone();
1.768 + }
1.769 + }
1.770 +
1.771 + // Object methods. See documentation in that class.
1.772 +
1.773 + public boolean equals(Object obj) {
1.774 + if (this == obj) {
1.775 + return true;
1.776 + }
1.777 + if (!(obj instanceof AttributedStringIterator)) {
1.778 + return false;
1.779 + }
1.780 +
1.781 + AttributedStringIterator that = (AttributedStringIterator) obj;
1.782 +
1.783 + if (AttributedString.this != that.getString())
1.784 + return false;
1.785 + if (currentIndex != that.currentIndex || beginIndex != that.beginIndex || endIndex != that.endIndex)
1.786 + return false;
1.787 + return true;
1.788 + }
1.789 +
1.790 + public int hashCode() {
1.791 + return text.hashCode() ^ currentIndex ^ beginIndex ^ endIndex;
1.792 + }
1.793 +
1.794 + public Object clone() {
1.795 + try {
1.796 + AttributedStringIterator other = (AttributedStringIterator) super.clone();
1.797 + return other;
1.798 + }
1.799 + catch (CloneNotSupportedException e) {
1.800 + throw new InternalError();
1.801 + }
1.802 + }
1.803 +
1.804 + // CharacterIterator methods. See documentation in that interface.
1.805 +
1.806 + public char first() {
1.807 + return internalSetIndex(beginIndex);
1.808 + }
1.809 +
1.810 + public char last() {
1.811 + if (endIndex == beginIndex) {
1.812 + return internalSetIndex(endIndex);
1.813 + } else {
1.814 + return internalSetIndex(endIndex - 1);
1.815 + }
1.816 + }
1.817 +
1.818 + public char current() {
1.819 + if (currentIndex == endIndex) {
1.820 + return DONE;
1.821 + } else {
1.822 + return charAt(currentIndex);
1.823 + }
1.824 + }
1.825 +
1.826 + public char next() {
1.827 + if (currentIndex < endIndex) {
1.828 + return internalSetIndex(currentIndex + 1);
1.829 + }
1.830 + else {
1.831 + return DONE;
1.832 + }
1.833 + }
1.834 +
1.835 + public char previous() {
1.836 + if (currentIndex > beginIndex) {
1.837 + return internalSetIndex(currentIndex - 1);
1.838 + }
1.839 + else {
1.840 + return DONE;
1.841 + }
1.842 + }
1.843 +
1.844 + public char setIndex(int position) {
1.845 + if (position < beginIndex || position > endIndex)
1.846 + throw new IllegalArgumentException("Invalid index");
1.847 + return internalSetIndex(position);
1.848 + }
1.849 +
1.850 + public int getBeginIndex() {
1.851 + return beginIndex;
1.852 + }
1.853 +
1.854 + public int getEndIndex() {
1.855 + return endIndex;
1.856 + }
1.857 +
1.858 + public int getIndex() {
1.859 + return currentIndex;
1.860 + }
1.861 +
1.862 + // AttributedCharacterIterator methods. See documentation in that interface.
1.863 +
1.864 + public int getRunStart() {
1.865 + return currentRunStart;
1.866 + }
1.867 +
1.868 + public int getRunStart(Attribute attribute) {
1.869 + if (currentRunStart == beginIndex || currentRunIndex == -1) {
1.870 + return currentRunStart;
1.871 + } else {
1.872 + Object value = getAttribute(attribute);
1.873 + int runStart = currentRunStart;
1.874 + int runIndex = currentRunIndex;
1.875 + while (runStart > beginIndex &&
1.876 + valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex - 1))) {
1.877 + runIndex--;
1.878 + runStart = runStarts[runIndex];
1.879 + }
1.880 + if (runStart < beginIndex) {
1.881 + runStart = beginIndex;
1.882 + }
1.883 + return runStart;
1.884 + }
1.885 + }
1.886 +
1.887 + public int getRunStart(Set<? extends Attribute> attributes) {
1.888 + if (currentRunStart == beginIndex || currentRunIndex == -1) {
1.889 + return currentRunStart;
1.890 + } else {
1.891 + int runStart = currentRunStart;
1.892 + int runIndex = currentRunIndex;
1.893 + while (runStart > beginIndex &&
1.894 + AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex - 1)) {
1.895 + runIndex--;
1.896 + runStart = runStarts[runIndex];
1.897 + }
1.898 + if (runStart < beginIndex) {
1.899 + runStart = beginIndex;
1.900 + }
1.901 + return runStart;
1.902 + }
1.903 + }
1.904 +
1.905 + public int getRunLimit() {
1.906 + return currentRunLimit;
1.907 + }
1.908 +
1.909 + public int getRunLimit(Attribute attribute) {
1.910 + if (currentRunLimit == endIndex || currentRunIndex == -1) {
1.911 + return currentRunLimit;
1.912 + } else {
1.913 + Object value = getAttribute(attribute);
1.914 + int runLimit = currentRunLimit;
1.915 + int runIndex = currentRunIndex;
1.916 + while (runLimit < endIndex &&
1.917 + valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex + 1))) {
1.918 + runIndex++;
1.919 + runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex;
1.920 + }
1.921 + if (runLimit > endIndex) {
1.922 + runLimit = endIndex;
1.923 + }
1.924 + return runLimit;
1.925 + }
1.926 + }
1.927 +
1.928 + public int getRunLimit(Set<? extends Attribute> attributes) {
1.929 + if (currentRunLimit == endIndex || currentRunIndex == -1) {
1.930 + return currentRunLimit;
1.931 + } else {
1.932 + int runLimit = currentRunLimit;
1.933 + int runIndex = currentRunIndex;
1.934 + while (runLimit < endIndex &&
1.935 + AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex + 1)) {
1.936 + runIndex++;
1.937 + runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex;
1.938 + }
1.939 + if (runLimit > endIndex) {
1.940 + runLimit = endIndex;
1.941 + }
1.942 + return runLimit;
1.943 + }
1.944 + }
1.945 +
1.946 + public Map<Attribute,Object> getAttributes() {
1.947 + if (runAttributes == null || currentRunIndex == -1 || runAttributes[currentRunIndex] == null) {
1.948 + // ??? would be nice to return null, but current spec doesn't allow it
1.949 + // returning Hashtable saves AttributeMap from dealing with emptiness
1.950 + return new Hashtable();
1.951 + }
1.952 + return new AttributeMap(currentRunIndex, beginIndex, endIndex);
1.953 + }
1.954 +
1.955 + public Set<Attribute> getAllAttributeKeys() {
1.956 + // ??? This should screen out attribute keys that aren't relevant to the client
1.957 + if (runAttributes == null) {
1.958 + // ??? would be nice to return null, but current spec doesn't allow it
1.959 + // returning HashSet saves us from dealing with emptiness
1.960 + return new HashSet();
1.961 + }
1.962 + synchronized (AttributedString.this) {
1.963 + // ??? should try to create this only once, then update if necessary,
1.964 + // and give callers read-only view
1.965 + Set keys = new HashSet();
1.966 + int i = 0;
1.967 + while (i < runCount) {
1.968 + if (runStarts[i] < endIndex && (i == runCount - 1 || runStarts[i + 1] > beginIndex)) {
1.969 + Vector currentRunAttributes = runAttributes[i];
1.970 + if (currentRunAttributes != null) {
1.971 + int j = currentRunAttributes.size();
1.972 + while (j-- > 0) {
1.973 + keys.add(currentRunAttributes.get(j));
1.974 + }
1.975 + }
1.976 + }
1.977 + i++;
1.978 + }
1.979 + return keys;
1.980 + }
1.981 + }
1.982 +
1.983 + public Object getAttribute(Attribute attribute) {
1.984 + int runIndex = currentRunIndex;
1.985 + if (runIndex < 0) {
1.986 + return null;
1.987 + }
1.988 + return AttributedString.this.getAttributeCheckRange(attribute, runIndex, beginIndex, endIndex);
1.989 + }
1.990 +
1.991 + // internally used methods
1.992 +
1.993 + private AttributedString getString() {
1.994 + return AttributedString.this;
1.995 + }
1.996 +
1.997 + // set the current index, update information about the current run if necessary,
1.998 + // return the character at the current index
1.999 + private char internalSetIndex(int position) {
1.1000 + currentIndex = position;
1.1001 + if (position < currentRunStart || position >= currentRunLimit) {
1.1002 + updateRunInfo();
1.1003 + }
1.1004 + if (currentIndex == endIndex) {
1.1005 + return DONE;
1.1006 + } else {
1.1007 + return charAt(position);
1.1008 + }
1.1009 + }
1.1010 +
1.1011 + // update the information about the current run
1.1012 + private void updateRunInfo() {
1.1013 + if (currentIndex == endIndex) {
1.1014 + currentRunStart = currentRunLimit = endIndex;
1.1015 + currentRunIndex = -1;
1.1016 + } else {
1.1017 + synchronized (AttributedString.this) {
1.1018 + int runIndex = -1;
1.1019 + while (runIndex < runCount - 1 && runStarts[runIndex + 1] <= currentIndex)
1.1020 + runIndex++;
1.1021 + currentRunIndex = runIndex;
1.1022 + if (runIndex >= 0) {
1.1023 + currentRunStart = runStarts[runIndex];
1.1024 + if (currentRunStart < beginIndex)
1.1025 + currentRunStart = beginIndex;
1.1026 + }
1.1027 + else {
1.1028 + currentRunStart = beginIndex;
1.1029 + }
1.1030 + if (runIndex < runCount - 1) {
1.1031 + currentRunLimit = runStarts[runIndex + 1];
1.1032 + if (currentRunLimit > endIndex)
1.1033 + currentRunLimit = endIndex;
1.1034 + }
1.1035 + else {
1.1036 + currentRunLimit = endIndex;
1.1037 + }
1.1038 + }
1.1039 + }
1.1040 + }
1.1041 +
1.1042 + }
1.1043 +
1.1044 + // the map class associated with this string class, giving access to the attributes of one run
1.1045 +
1.1046 + final private class AttributeMap extends AbstractMap<Attribute,Object> {
1.1047 +
1.1048 + int runIndex;
1.1049 + int beginIndex;
1.1050 + int endIndex;
1.1051 +
1.1052 + AttributeMap(int runIndex, int beginIndex, int endIndex) {
1.1053 + this.runIndex = runIndex;
1.1054 + this.beginIndex = beginIndex;
1.1055 + this.endIndex = endIndex;
1.1056 + }
1.1057 +
1.1058 + public Set entrySet() {
1.1059 + HashSet set = new HashSet();
1.1060 + synchronized (AttributedString.this) {
1.1061 + int size = runAttributes[runIndex].size();
1.1062 + for (int i = 0; i < size; i++) {
1.1063 + Attribute key = (Attribute) runAttributes[runIndex].get(i);
1.1064 + Object value = runAttributeValues[runIndex].get(i);
1.1065 + if (value instanceof Annotation) {
1.1066 + value = AttributedString.this.getAttributeCheckRange(key,
1.1067 + runIndex, beginIndex, endIndex);
1.1068 + if (value == null) {
1.1069 + continue;
1.1070 + }
1.1071 + }
1.1072 + Map.Entry entry = new AttributeEntry(key, value);
1.1073 + set.add(entry);
1.1074 + }
1.1075 + }
1.1076 + return set;
1.1077 + }
1.1078 +
1.1079 + public Object get(Object key) {
1.1080 + return AttributedString.this.getAttributeCheckRange((Attribute) key, runIndex, beginIndex, endIndex);
1.1081 + }
1.1082 + }
1.1083 +}
1.1084 +
1.1085 +class AttributeEntry implements Map.Entry {
1.1086 +
1.1087 + private Attribute key;
1.1088 + private Object value;
1.1089 +
1.1090 + AttributeEntry(Attribute key, Object value) {
1.1091 + this.key = key;
1.1092 + this.value = value;
1.1093 + }
1.1094 +
1.1095 + public boolean equals(Object o) {
1.1096 + if (!(o instanceof AttributeEntry)) {
1.1097 + return false;
1.1098 + }
1.1099 + AttributeEntry other = (AttributeEntry) o;
1.1100 + return other.key.equals(key) &&
1.1101 + (value == null ? other.value == null : other.value.equals(value));
1.1102 + }
1.1103 +
1.1104 + public Object getKey() {
1.1105 + return key;
1.1106 + }
1.1107 +
1.1108 + public Object getValue() {
1.1109 + return value;
1.1110 + }
1.1111 +
1.1112 + public Object setValue(Object newValue) {
1.1113 + throw new UnsupportedOperationException();
1.1114 + }
1.1115 +
1.1116 + public int hashCode() {
1.1117 + return key.hashCode() ^ (value==null ? 0 : value.hashCode());
1.1118 + }
1.1119 +
1.1120 + public String toString() {
1.1121 + return key.toString()+"="+value.toString();
1.1122 + }
1.1123 +}