1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 * License for the specific language governing permissions and limitations under 15 * the License. 16 */ 17 18package java.text; 19 20import java.text.AttributedCharacterIterator.Attribute; 21import java.util.ArrayList; 22import java.util.Arrays; 23import java.util.HashMap; 24import java.util.HashSet; 25import java.util.Iterator; 26import java.util.List; 27import java.util.ListIterator; 28import java.util.Map; 29import java.util.Set; 30 31/** 32 * Holds a string with attributes describing the characters of 33 * this string. 34 */ 35public class AttributedString { 36 37 String text; 38 39 Map<AttributedCharacterIterator.Attribute, List<Range>> attributeMap; 40 41 static class Range { 42 int start; 43 44 int end; 45 46 Object value; 47 48 Range(int s, int e, Object v) { 49 start = s; 50 end = e; 51 value = v; 52 } 53 } 54 55 static class AttributedIterator implements AttributedCharacterIterator { 56 57 private int begin, end, offset; 58 59 private AttributedString attrString; 60 61 private HashSet<Attribute> attributesAllowed; 62 63 AttributedIterator(AttributedString attrString) { 64 this.attrString = attrString; 65 begin = 0; 66 end = attrString.text.length(); 67 offset = 0; 68 } 69 70 AttributedIterator(AttributedString attrString, 71 AttributedCharacterIterator.Attribute[] attributes, int begin, 72 int end) { 73 if (begin < 0 || end > attrString.text.length() || begin > end) { 74 throw new IllegalArgumentException(); 75 } 76 this.begin = begin; 77 this.end = end; 78 offset = begin; 79 this.attrString = attrString; 80 if (attributes != null) { 81 HashSet<Attribute> set = new HashSet<Attribute>( 82 (attributes.length * 4 / 3) + 1); 83 for (int i = attributes.length; --i >= 0;) { 84 set.add(attributes[i]); 85 } 86 attributesAllowed = set; 87 } 88 } 89 90 /** 91 * Returns a new {@code AttributedIterator} with the same source string, 92 * begin, end, and current index as this attributed iterator. 93 * 94 * @return a shallow copy of this attributed iterator. 95 * @see java.lang.Cloneable 96 */ 97 @Override 98 @SuppressWarnings("unchecked") 99 public Object clone() { 100 try { 101 AttributedIterator clone = (AttributedIterator) super.clone(); 102 if (attributesAllowed != null) { 103 clone.attributesAllowed = (HashSet<Attribute>) attributesAllowed 104 .clone(); 105 } 106 return clone; 107 } catch (CloneNotSupportedException e) { 108 throw new AssertionError(e); 109 } 110 } 111 112 public char current() { 113 if (offset == end) { 114 return DONE; 115 } 116 return attrString.text.charAt(offset); 117 } 118 119 public char first() { 120 if (begin == end) { 121 return DONE; 122 } 123 offset = begin; 124 return attrString.text.charAt(offset); 125 } 126 127 /** 128 * Returns the begin index in the source string. 129 * 130 * @return the index of the first character to iterate. 131 */ 132 public int getBeginIndex() { 133 return begin; 134 } 135 136 /** 137 * Returns the end index in the source String. 138 * 139 * @return the index one past the last character to iterate. 140 */ 141 public int getEndIndex() { 142 return end; 143 } 144 145 /** 146 * Returns the current index in the source String. 147 * 148 * @return the current index. 149 */ 150 public int getIndex() { 151 return offset; 152 } 153 154 private boolean inRange(Range range) { 155 if (!(range.value instanceof Annotation)) { 156 return true; 157 } 158 return range.start >= begin && range.start < end 159 && range.end > begin && range.end <= end; 160 } 161 162 private boolean inRange(List<Range> ranges) { 163 Iterator<Range> it = ranges.iterator(); 164 while (it.hasNext()) { 165 Range range = it.next(); 166 if (range.start >= begin && range.start < end) { 167 return !(range.value instanceof Annotation) 168 || (range.end > begin && range.end <= end); 169 } else if (range.end > begin && range.end <= end) { 170 return !(range.value instanceof Annotation) 171 || (range.start >= begin && range.start < end); 172 } 173 } 174 return false; 175 } 176 177 /** 178 * Returns a set of attributes present in the {@code AttributedString}. 179 * An empty set returned indicates that no attributes where defined. 180 * 181 * @return a set of attribute keys that may be empty. 182 */ 183 public Set<AttributedIterator.Attribute> getAllAttributeKeys() { 184 if (begin == 0 && end == attrString.text.length() 185 && attributesAllowed == null) { 186 return attrString.attributeMap.keySet(); 187 } 188 189 Set<AttributedIterator.Attribute> result = new HashSet<Attribute>( 190 (attrString.attributeMap.size() * 4 / 3) + 1); 191 Iterator<Map.Entry<Attribute, List<Range>>> it = attrString.attributeMap 192 .entrySet().iterator(); 193 while (it.hasNext()) { 194 Map.Entry<Attribute, List<Range>> entry = it.next(); 195 if (attributesAllowed == null 196 || attributesAllowed.contains(entry.getKey())) { 197 List<Range> ranges = entry.getValue(); 198 if (inRange(ranges)) { 199 result.add(entry.getKey()); 200 } 201 } 202 } 203 return result; 204 } 205 206 private Object currentValue(List<Range> ranges) { 207 Iterator<Range> it = ranges.iterator(); 208 while (it.hasNext()) { 209 Range range = it.next(); 210 if (offset >= range.start && offset < range.end) { 211 return inRange(range) ? range.value : null; 212 } 213 } 214 return null; 215 } 216 217 public Object getAttribute( 218 AttributedCharacterIterator.Attribute attribute) { 219 if (attributesAllowed != null 220 && !attributesAllowed.contains(attribute)) { 221 return null; 222 } 223 ArrayList<Range> ranges = (ArrayList<Range>) attrString.attributeMap 224 .get(attribute); 225 if (ranges == null) { 226 return null; 227 } 228 return currentValue(ranges); 229 } 230 231 public Map<Attribute, Object> getAttributes() { 232 Map<Attribute, Object> result = new HashMap<Attribute, Object>( 233 (attrString.attributeMap.size() * 4 / 3) + 1); 234 Iterator<Map.Entry<Attribute, List<Range>>> it = attrString.attributeMap 235 .entrySet().iterator(); 236 while (it.hasNext()) { 237 Map.Entry<Attribute, List<Range>> entry = it.next(); 238 if (attributesAllowed == null 239 || attributesAllowed.contains(entry.getKey())) { 240 Object value = currentValue(entry.getValue()); 241 if (value != null) { 242 result.put(entry.getKey(), value); 243 } 244 } 245 } 246 return result; 247 } 248 249 public int getRunLimit() { 250 return getRunLimit(getAllAttributeKeys()); 251 } 252 253 private int runLimit(List<Range> ranges) { 254 int result = end; 255 ListIterator<Range> it = ranges.listIterator(ranges.size()); 256 while (it.hasPrevious()) { 257 Range range = it.previous(); 258 if (range.end <= begin) { 259 break; 260 } 261 if (offset >= range.start && offset < range.end) { 262 return inRange(range) ? range.end : result; 263 } else if (offset >= range.end) { 264 break; 265 } 266 result = range.start; 267 } 268 return result; 269 } 270 271 public int getRunLimit(AttributedCharacterIterator.Attribute attribute) { 272 if (attributesAllowed != null 273 && !attributesAllowed.contains(attribute)) { 274 return end; 275 } 276 ArrayList<Range> ranges = (ArrayList<Range>) attrString.attributeMap 277 .get(attribute); 278 if (ranges == null) { 279 return end; 280 } 281 return runLimit(ranges); 282 } 283 284 public int getRunLimit(Set<? extends Attribute> attributes) { 285 int limit = end; 286 Iterator<? extends Attribute> it = attributes.iterator(); 287 while (it.hasNext()) { 288 AttributedCharacterIterator.Attribute attribute = it.next(); 289 int newLimit = getRunLimit(attribute); 290 if (newLimit < limit) { 291 limit = newLimit; 292 } 293 } 294 return limit; 295 } 296 297 public int getRunStart() { 298 return getRunStart(getAllAttributeKeys()); 299 } 300 301 private int runStart(List<Range> ranges) { 302 int result = begin; 303 Iterator<Range> it = ranges.iterator(); 304 while (it.hasNext()) { 305 Range range = it.next(); 306 if (range.start >= end) { 307 break; 308 } 309 if (offset >= range.start && offset < range.end) { 310 return inRange(range) ? range.start : result; 311 } else if (offset < range.start) { 312 break; 313 } 314 result = range.end; 315 } 316 return result; 317 } 318 319 public int getRunStart(AttributedCharacterIterator.Attribute attribute) { 320 if (attributesAllowed != null 321 && !attributesAllowed.contains(attribute)) { 322 return begin; 323 } 324 ArrayList<Range> ranges = (ArrayList<Range>) attrString.attributeMap 325 .get(attribute); 326 if (ranges == null) { 327 return begin; 328 } 329 return runStart(ranges); 330 } 331 332 public int getRunStart(Set<? extends Attribute> attributes) { 333 int start = begin; 334 Iterator<? extends Attribute> it = attributes.iterator(); 335 while (it.hasNext()) { 336 AttributedCharacterIterator.Attribute attribute = it.next(); 337 int newStart = getRunStart(attribute); 338 if (newStart > start) { 339 start = newStart; 340 } 341 } 342 return start; 343 } 344 345 public char last() { 346 if (begin == end) { 347 return DONE; 348 } 349 offset = end - 1; 350 return attrString.text.charAt(offset); 351 } 352 353 public char next() { 354 if (offset >= (end - 1)) { 355 offset = end; 356 return DONE; 357 } 358 return attrString.text.charAt(++offset); 359 } 360 361 public char previous() { 362 if (offset == begin) { 363 return DONE; 364 } 365 return attrString.text.charAt(--offset); 366 } 367 368 public char setIndex(int location) { 369 if (location < begin || location > end) { 370 throw new IllegalArgumentException(); 371 } 372 offset = location; 373 if (offset == end) { 374 return DONE; 375 } 376 return attrString.text.charAt(offset); 377 } 378 } 379 380 /** 381 * Constructs an {@code AttributedString} from an {@code 382 * AttributedCharacterIterator}, which represents attributed text. 383 * 384 * @param iterator 385 * the {@code AttributedCharacterIterator} that contains the text 386 * for this attributed string. 387 */ 388 public AttributedString(AttributedCharacterIterator iterator) { 389 if (iterator.getBeginIndex() > iterator.getEndIndex()) { 390 throw new IllegalArgumentException("Invalid substring range"); 391 } 392 StringBuilder buffer = new StringBuilder(); 393 for (int i = iterator.getBeginIndex(); i < iterator.getEndIndex(); i++) { 394 buffer.append(iterator.current()); 395 iterator.next(); 396 } 397 text = buffer.toString(); 398 Set<AttributedCharacterIterator.Attribute> attributes = iterator 399 .getAllAttributeKeys(); 400 if (attributes == null) { 401 return; 402 } 403 attributeMap = new HashMap<Attribute, List<Range>>( 404 (attributes.size() * 4 / 3) + 1); 405 406 Iterator<Attribute> it = attributes.iterator(); 407 while (it.hasNext()) { 408 AttributedCharacterIterator.Attribute attribute = it.next(); 409 iterator.setIndex(0); 410 while (iterator.current() != CharacterIterator.DONE) { 411 int start = iterator.getRunStart(attribute); 412 int limit = iterator.getRunLimit(attribute); 413 Object value = iterator.getAttribute(attribute); 414 if (value != null) { 415 addAttribute(attribute, value, start, limit); 416 } 417 iterator.setIndex(limit); 418 } 419 } 420 } 421 422 private AttributedString(AttributedCharacterIterator iterator, int start, 423 int end, Set<Attribute> attributes) { 424 if (start < iterator.getBeginIndex() || end > iterator.getEndIndex() 425 || start > end) { 426 throw new IllegalArgumentException(); 427 } 428 429 if (attributes == null) { 430 return; 431 } 432 433 StringBuilder buffer = new StringBuilder(); 434 iterator.setIndex(start); 435 while (iterator.getIndex() < end) { 436 buffer.append(iterator.current()); 437 iterator.next(); 438 } 439 text = buffer.toString(); 440 attributeMap = new HashMap<Attribute, List<Range>>( 441 (attributes.size() * 4 / 3) + 1); 442 443 Iterator<Attribute> it = attributes.iterator(); 444 while (it.hasNext()) { 445 AttributedCharacterIterator.Attribute attribute = it.next(); 446 iterator.setIndex(start); 447 while (iterator.getIndex() < end) { 448 Object value = iterator.getAttribute(attribute); 449 int runStart = iterator.getRunStart(attribute); 450 int limit = iterator.getRunLimit(attribute); 451 if ((value instanceof Annotation && runStart >= start && limit <= end) 452 || (value != null && !(value instanceof Annotation))) { 453 addAttribute(attribute, value, (runStart < start ? start 454 : runStart) 455 - start, (limit > end ? end : limit) - start); 456 } 457 iterator.setIndex(limit); 458 } 459 } 460 } 461 462 /** 463 * Constructs an {@code AttributedString} from a range of the text contained 464 * in the specified {@code AttributedCharacterIterator}, starting at {@code 465 * start} and ending at {@code end}. All attributes will be copied to this 466 * attributed string. 467 * 468 * @param iterator 469 * the {@code AttributedCharacterIterator} that contains the text 470 * for this attributed string. 471 * @param start 472 * the start index of the range of the copied text. 473 * @param end 474 * the end index of the range of the copied text. 475 * @throws IllegalArgumentException 476 * if {@code start} is less than first index of 477 * {@code iterator}, {@code end} is greater than the last 478 * index + 1 in {@code iterator} or if {@code start > end}. 479 */ 480 public AttributedString(AttributedCharacterIterator iterator, int start, 481 int end) { 482 this(iterator, start, end, iterator.getAllAttributeKeys()); 483 } 484 485 /** 486 * Constructs an {@code AttributedString} from a range of the text contained 487 * in the specified {@code AttributedCharacterIterator}, starting at {@code 488 * start}, ending at {@code end} and it will copy the attributes defined in 489 * the specified set. If the set is {@code null} then all attributes are 490 * copied. 491 * 492 * @param iterator 493 * the {@code AttributedCharacterIterator} that contains the text 494 * for this attributed string. 495 * @param start 496 * the start index of the range of the copied text. 497 * @param end 498 * the end index of the range of the copied text. 499 * @param attributes 500 * the set of attributes that will be copied, or all if it is 501 * {@code null}. 502 * @throws IllegalArgumentException 503 * if {@code start} is less than first index of 504 * {@code iterator}, {@code end} is greater than the last index + 505 * 1 in {@code iterator} or if {@code start > end}. 506 */ 507 public AttributedString(AttributedCharacterIterator iterator, int start, 508 int end, AttributedCharacterIterator.Attribute[] attributes) { 509 this(iterator, start, end, (attributes == null 510 ? new HashSet<Attribute>() 511 : new HashSet<Attribute>(Arrays.asList(attributes)))); 512 } 513 514 /** 515 * Creates an {@code AttributedString} from the given text. 516 * 517 * @param value 518 * the text to take as base for this attributed string. 519 */ 520 public AttributedString(String value) { 521 if (value == null) { 522 throw new NullPointerException("value == null"); 523 } 524 text = value; 525 attributeMap = new HashMap<Attribute, List<Range>>(11); 526 } 527 528 /** 529 * Creates an {@code AttributedString} from the given text and the 530 * attributes. The whole text has the given attributes applied. 531 * 532 * @param value 533 * the text to take as base for this attributed string. 534 * @param attributes 535 * the attributes that the text is associated with. 536 * @throws IllegalArgumentException 537 * if the length of {@code value} is 0 but the size of {@code 538 * attributes} is greater than 0. 539 * @throws NullPointerException 540 * if {@code value} is {@code null}. 541 */ 542 public AttributedString(String value, 543 Map<? extends AttributedCharacterIterator.Attribute, ?> attributes) { 544 if (value == null) { 545 throw new NullPointerException("value == null"); 546 } 547 if (value.length() == 0 && !attributes.isEmpty()) { 548 throw new IllegalArgumentException("Cannot add attributes to empty string"); 549 } 550 text = value; 551 attributeMap = new HashMap<Attribute, List<Range>>( 552 (attributes.size() * 4 / 3) + 1); 553 Iterator<?> it = attributes.entrySet().iterator(); 554 while (it.hasNext()) { 555 Map.Entry<?, ?> entry = (Map.Entry<?, ?>) it.next(); 556 ArrayList<Range> ranges = new ArrayList<Range>(1); 557 ranges.add(new Range(0, text.length(), entry.getValue())); 558 attributeMap.put((AttributedCharacterIterator.Attribute) entry 559 .getKey(), ranges); 560 } 561 } 562 563 /** 564 * Applies a given attribute to this string. 565 * 566 * @param attribute 567 * the attribute that will be applied to this string. 568 * @param value 569 * the value of the attribute that will be applied to this 570 * string. 571 * @throws IllegalArgumentException 572 * if the length of this attributed string is 0. 573 * @throws NullPointerException 574 * if {@code attribute} is {@code null}. 575 */ 576 public void addAttribute(AttributedCharacterIterator.Attribute attribute, Object value) { 577 if (attribute == null) { 578 throw new NullPointerException("attribute == null"); 579 } 580 if (text.length() == 0) { 581 throw new IllegalArgumentException(); 582 } 583 584 List<Range> ranges = attributeMap.get(attribute); 585 if (ranges == null) { 586 ranges = new ArrayList<Range>(1); 587 attributeMap.put(attribute, ranges); 588 } else { 589 ranges.clear(); 590 } 591 ranges.add(new Range(0, text.length(), value)); 592 } 593 594 /** 595 * Applies a given attribute to the given range of this string. 596 * 597 * @param attribute 598 * the attribute that will be applied to this string. 599 * @param value 600 * the value of the attribute that will be applied to this 601 * string. 602 * @param start 603 * the start of the range where the attribute will be applied. 604 * @param end 605 * the end of the range where the attribute will be applied. 606 * @throws IllegalArgumentException 607 * if {@code start < 0}, {@code end} is greater than the length 608 * of this string, or if {@code start >= end}. 609 * @throws NullPointerException 610 * if {@code attribute} is {@code null}. 611 */ 612 public void addAttribute(AttributedCharacterIterator.Attribute attribute, 613 Object value, int start, int end) { 614 if (attribute == null) { 615 throw new NullPointerException("attribute == null"); 616 } 617 if (start < 0 || end > text.length() || start >= end) { 618 throw new IllegalArgumentException(); 619 } 620 621 if (value == null) { 622 return; 623 } 624 625 List<Range> ranges = attributeMap.get(attribute); 626 if (ranges == null) { 627 ranges = new ArrayList<Range>(1); 628 ranges.add(new Range(start, end, value)); 629 attributeMap.put(attribute, ranges); 630 return; 631 } 632 ListIterator<Range> it = ranges.listIterator(); 633 while (it.hasNext()) { 634 Range range = it.next(); 635 if (end <= range.start) { 636 it.previous(); 637 break; 638 } else if (start < range.end 639 || (start == range.end && value.equals(range.value))) { 640 Range r1 = null, r3; 641 it.remove(); 642 r1 = new Range(range.start, start, range.value); 643 r3 = new Range(end, range.end, range.value); 644 645 while (end > range.end && it.hasNext()) { 646 range = it.next(); 647 if (end <= range.end) { 648 if (end > range.start 649 || (end == range.start && value.equals(range.value))) { 650 it.remove(); 651 r3 = new Range(end, range.end, range.value); 652 break; 653 } 654 } else { 655 it.remove(); 656 } 657 } 658 659 if (value.equals(r1.value)) { 660 if (value.equals(r3.value)) { 661 it.add(new Range(r1.start < start ? r1.start : start, 662 r3.end > end ? r3.end : end, r1.value)); 663 } else { 664 it.add(new Range(r1.start < start ? r1.start : start, 665 end, r1.value)); 666 if (r3.start < r3.end) { 667 it.add(r3); 668 } 669 } 670 } else { 671 if (value.equals(r3.value)) { 672 if (r1.start < r1.end) { 673 it.add(r1); 674 } 675 it.add(new Range(start, r3.end > end ? r3.end : end, 676 r3.value)); 677 } else { 678 if (r1.start < r1.end) { 679 it.add(r1); 680 } 681 it.add(new Range(start, end, value)); 682 if (r3.start < r3.end) { 683 it.add(r3); 684 } 685 } 686 } 687 return; 688 } 689 } 690 it.add(new Range(start, end, value)); 691 } 692 693 /** 694 * Applies a given set of attributes to the given range of the string. 695 * 696 * @param attributes 697 * the set of attributes that will be applied to this string. 698 * @param start 699 * the start of the range where the attribute will be applied. 700 * @param end 701 * the end of the range where the attribute will be applied. 702 * @throws IllegalArgumentException 703 * if {@code start < 0}, {@code end} is greater than the length 704 * of this string, or if {@code start >= end}. 705 */ 706 public void addAttributes( 707 Map<? extends AttributedCharacterIterator.Attribute, ?> attributes, 708 int start, int end) { 709 Iterator<?> it = attributes.entrySet().iterator(); 710 while (it.hasNext()) { 711 Map.Entry<?, ?> entry = (Map.Entry<?, ?>) it.next(); 712 addAttribute( 713 (AttributedCharacterIterator.Attribute) entry.getKey(), 714 entry.getValue(), start, end); 715 } 716 } 717 718 /** 719 * Returns an {@code AttributedCharacterIterator} that gives access to the 720 * complete content of this attributed string. 721 * 722 * @return the newly created {@code AttributedCharacterIterator}. 723 */ 724 public AttributedCharacterIterator getIterator() { 725 return new AttributedIterator(this); 726 } 727 728 /** 729 * Returns an {@code AttributedCharacterIterator} that gives access to the 730 * complete content of this attributed string. Only attributes contained in 731 * {@code attributes} are available from this iterator if they are defined 732 * for this text. 733 * 734 * @param attributes 735 * the array containing attributes that will be in the new 736 * iterator if they are defined for this text. 737 * @return the newly created {@code AttributedCharacterIterator}. 738 */ 739 public AttributedCharacterIterator getIterator( 740 AttributedCharacterIterator.Attribute[] attributes) { 741 return new AttributedIterator(this, attributes, 0, text.length()); 742 } 743 744 /** 745 * Returns an {@code AttributedCharacterIterator} that gives access to the 746 * contents of this attributed string starting at index {@code start} up to 747 * index {@code end}. Only attributes contained in {@code attributes} are 748 * available from this iterator if they are defined for this text. 749 * 750 * @param attributes 751 * the array containing attributes that will be in the new 752 * iterator if they are defined for this text. 753 * @param start 754 * the start index of the iterator on the underlying text. 755 * @param end 756 * the end index of the iterator on the underlying text. 757 * @return the newly created {@code AttributedCharacterIterator}. 758 */ 759 public AttributedCharacterIterator getIterator( 760 AttributedCharacterIterator.Attribute[] attributes, int start, 761 int end) { 762 return new AttributedIterator(this, attributes, start, end); 763 } 764} 765