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