Path: blob/master/src/java.base/share/classes/java/text/AttributedString.java
41152 views
/*1* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.text;2627import java.util.*;28import java.text.AttributedCharacterIterator.Attribute;2930/**31* An AttributedString holds text and related attribute information. It32* may be used as the actual data storage in some cases where a text33* reader wants to access attributed text through the AttributedCharacterIterator34* interface.35*36* <p>37* An attribute is a key/value pair, identified by the key. No two38* attributes on a given character can have the same key.39*40* <p>The values for an attribute are immutable, or must not be mutated41* by clients or storage. They are always passed by reference, and not42* cloned.43*44* @see AttributedCharacterIterator45* @see Annotation46* @since 1.247*/4849public class AttributedString {50// field holding the text51String text;5253// Fields holding run attribute information.54// Run attributes are organized by run.55// Arrays are always of equal lengths (the current capacity).56// Since there are no vectors of int, we have to use arrays.57private static final int INITIAL_CAPACITY = 10;58int runCount; // actual number of runs, <= current capacity59int[] runStarts; // start index for each run60Vector<Attribute>[] runAttributes; // vector of attribute keys for each run61Vector<Object>[] runAttributeValues; // parallel vector of attribute values for each run6263/**64* Constructs an AttributedString instance with the given65* AttributedCharacterIterators.66*67* @param iterators AttributedCharacterIterators to construct68* AttributedString from.69* @throws NullPointerException if iterators is null70*/71AttributedString(AttributedCharacterIterator[] iterators) {72if (iterators == null) {73throw new NullPointerException("Iterators must not be null");74}75if (iterators.length == 0) {76text = "";77}78else {79// Build the String contents80StringBuffer buffer = new StringBuffer();81for (int counter = 0; counter < iterators.length; counter++) {82appendContents(buffer, iterators[counter]);83}8485text = buffer.toString();8687if (!text.isEmpty()) {88// Determine the runs, creating a new run when the attributes89// differ.90int offset = 0;91Map<Attribute,Object> last = null;9293for (int counter = 0; counter < iterators.length; counter++) {94AttributedCharacterIterator iterator = iterators[counter];95int start = iterator.getBeginIndex();96int end = iterator.getEndIndex();97int index = start;9899while (index < end) {100iterator.setIndex(index);101102Map<Attribute,Object> attrs = iterator.getAttributes();103104if (mapsDiffer(last, attrs)) {105setAttributes(attrs, index - start + offset);106}107last = attrs;108index = iterator.getRunLimit();109}110offset += (end - start);111}112}113}114}115116/**117* Constructs an AttributedString instance with the given text.118* @param text The text for this attributed string.119* @throws NullPointerException if {@code text} is null.120*/121public AttributedString(String text) {122if (text == null) {123throw new NullPointerException();124}125this.text = text;126}127128/**129* Constructs an AttributedString instance with the given text and attributes.130* @param text The text for this attributed string.131* @param attributes The attributes that apply to the entire string.132* @throws NullPointerException if {@code text} or133* {@code attributes} is null.134* @throws IllegalArgumentException if the text has length 0135* and the attributes parameter is not an empty Map (attributes136* cannot be applied to a 0-length range).137*/138public AttributedString(String text,139Map<? extends Attribute, ?> attributes)140{141if (text == null || attributes == null) {142throw new NullPointerException();143}144this.text = text;145146if (text.isEmpty()) {147if (attributes.isEmpty())148return;149throw new IllegalArgumentException("Can't add attribute to 0-length text");150}151152int attributeCount = attributes.size();153if (attributeCount > 0) {154createRunAttributeDataVectors();155Vector<Attribute> newRunAttributes = new Vector<>(attributeCount);156Vector<Object> newRunAttributeValues = new Vector<>(attributeCount);157runAttributes[0] = newRunAttributes;158runAttributeValues[0] = newRunAttributeValues;159160Iterator<? extends Map.Entry<? extends Attribute, ?>> iterator = attributes.entrySet().iterator();161while (iterator.hasNext()) {162Map.Entry<? extends Attribute, ?> entry = iterator.next();163newRunAttributes.addElement(entry.getKey());164newRunAttributeValues.addElement(entry.getValue());165}166}167}168169/**170* Constructs an AttributedString instance with the given attributed171* text represented by AttributedCharacterIterator.172* @param text The text for this attributed string.173* @throws NullPointerException if {@code text} is null.174*/175public AttributedString(AttributedCharacterIterator text) {176// If performance is critical, this constructor should be177// implemented here rather than invoking the constructor for a178// subrange. We can avoid some range checking in the loops.179this(text, text.getBeginIndex(), text.getEndIndex(), null);180}181182/**183* Constructs an AttributedString instance with the subrange of184* the given attributed text represented by185* AttributedCharacterIterator. If the given range produces an186* empty text, all attributes will be discarded. Note that any187* attributes wrapped by an Annotation object are discarded for a188* subrange of the original attribute range.189*190* @param text The text for this attributed string.191* @param beginIndex Index of the first character of the range.192* @param endIndex Index of the character following the last character193* of the range.194* @throws NullPointerException if {@code text} is null.195* @throws IllegalArgumentException if the subrange given by196* beginIndex and endIndex is out of the text range.197* @see java.text.Annotation198*/199public AttributedString(AttributedCharacterIterator text,200int beginIndex,201int endIndex) {202this(text, beginIndex, endIndex, null);203}204205/**206* Constructs an AttributedString instance with the subrange of207* the given attributed text represented by208* AttributedCharacterIterator. Only attributes that match the209* given attributes will be incorporated into the instance. If the210* given range produces an empty text, all attributes will be211* discarded. Note that any attributes wrapped by an Annotation212* object are discarded for a subrange of the original attribute213* range.214*215* @param text The text for this attributed string.216* @param beginIndex Index of the first character of the range.217* @param endIndex Index of the character following the last character218* of the range.219* @param attributes Specifies attributes to be extracted220* from the text. If null is specified, all available attributes will221* be used.222* @throws NullPointerException if {@code text} is null.223* @throws IllegalArgumentException if the subrange given by224* beginIndex and endIndex is out of the text range.225* @see java.text.Annotation226*/227public AttributedString(AttributedCharacterIterator text,228int beginIndex,229int endIndex,230Attribute[] attributes) {231if (text == null) {232throw new NullPointerException();233}234235// Validate the given subrange236int textBeginIndex = text.getBeginIndex();237int textEndIndex = text.getEndIndex();238if (beginIndex < textBeginIndex || endIndex > textEndIndex || beginIndex > endIndex)239throw new IllegalArgumentException("Invalid substring range");240241// Copy the given string242StringBuilder textBuilder = new StringBuilder();243text.setIndex(beginIndex);244for (char c = text.current(); text.getIndex() < endIndex; c = text.next())245textBuilder.append(c);246this.text = textBuilder.toString();247248if (beginIndex == endIndex)249return;250251// Select attribute keys to be taken care of252HashSet<Attribute> keys = new HashSet<>();253if (attributes == null) {254keys.addAll(text.getAllAttributeKeys());255} else {256for (int i = 0; i < attributes.length; i++)257keys.add(attributes[i]);258keys.retainAll(text.getAllAttributeKeys());259}260if (keys.isEmpty())261return;262263// Get and set attribute runs for each attribute name. Need to264// scan from the top of the text so that we can discard any265// Annotation that is no longer applied to a subset text segment.266Iterator<Attribute> itr = keys.iterator();267while (itr.hasNext()) {268Attribute attributeKey = itr.next();269text.setIndex(textBeginIndex);270while (text.getIndex() < endIndex) {271int start = text.getRunStart(attributeKey);272int limit = text.getRunLimit(attributeKey);273Object value = text.getAttribute(attributeKey);274275if (value != null) {276if (value instanceof Annotation) {277if (start >= beginIndex && limit <= endIndex) {278addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex);279} else {280if (limit > endIndex)281break;282}283} else {284// if the run is beyond the given (subset) range, we285// don't need to process further.286if (start >= endIndex)287break;288if (limit > beginIndex) {289// attribute is applied to any subrange290if (start < beginIndex)291start = beginIndex;292if (limit > endIndex)293limit = endIndex;294if (start != limit) {295addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex);296}297}298}299}300text.setIndex(limit);301}302}303}304305/**306* Adds an attribute to the entire string.307* @param attribute the attribute key308* @param value the value of the attribute; may be null309* @throws NullPointerException if {@code attribute} is null.310* @throws IllegalArgumentException if the AttributedString has length 0311* (attributes cannot be applied to a 0-length range).312*/313public void addAttribute(Attribute attribute, Object value) {314315if (attribute == null) {316throw new NullPointerException();317}318319int len = length();320if (len == 0) {321throw new IllegalArgumentException("Can't add attribute to 0-length text");322}323324addAttributeImpl(attribute, value, 0, len);325}326327/**328* Adds an attribute to a subrange of the string.329* @param attribute the attribute key330* @param value The value of the attribute. May be null.331* @param beginIndex Index of the first character of the range.332* @param endIndex Index of the character following the last character of the range.333* @throws NullPointerException if {@code attribute} is null.334* @throws IllegalArgumentException if beginIndex is less than 0, endIndex is335* greater than the length of the string, or beginIndex and endIndex together don't336* define a non-empty subrange of the string.337*/338public void addAttribute(Attribute attribute, Object value,339int beginIndex, int endIndex) {340341if (attribute == null) {342throw new NullPointerException();343}344345if (beginIndex < 0 || endIndex > length() || beginIndex >= endIndex) {346throw new IllegalArgumentException("Invalid substring range");347}348349addAttributeImpl(attribute, value, beginIndex, endIndex);350}351352/**353* Adds a set of attributes to a subrange of the string.354* @param attributes The attributes to be added to the string.355* @param beginIndex Index of the first character of the range.356* @param endIndex Index of the character following the last357* character of the range.358* @throws NullPointerException if {@code attributes} is null.359* @throws IllegalArgumentException if beginIndex is less than360* 0, endIndex is greater than the length of the string, or361* beginIndex and endIndex together don't define a non-empty362* subrange of the string and the attributes parameter is not an363* empty Map.364*/365public void addAttributes(Map<? extends Attribute, ?> attributes,366int beginIndex, int endIndex)367{368if (attributes == null) {369throw new NullPointerException();370}371372if (beginIndex < 0 || endIndex > length() || beginIndex > endIndex) {373throw new IllegalArgumentException("Invalid substring range");374}375if (beginIndex == endIndex) {376if (attributes.isEmpty())377return;378throw new IllegalArgumentException("Can't add attribute to 0-length text");379}380381// make sure we have run attribute data vectors382if (runCount == 0) {383createRunAttributeDataVectors();384}385386// break up runs if necessary387int beginRunIndex = ensureRunBreak(beginIndex);388int endRunIndex = ensureRunBreak(endIndex);389390Iterator<? extends Map.Entry<? extends Attribute, ?>> iterator =391attributes.entrySet().iterator();392while (iterator.hasNext()) {393Map.Entry<? extends Attribute, ?> entry = iterator.next();394addAttributeRunData(entry.getKey(), entry.getValue(), beginRunIndex, endRunIndex);395}396}397398private synchronized void addAttributeImpl(Attribute attribute, Object value,399int beginIndex, int endIndex) {400401// make sure we have run attribute data vectors402if (runCount == 0) {403createRunAttributeDataVectors();404}405406// break up runs if necessary407int beginRunIndex = ensureRunBreak(beginIndex);408int endRunIndex = ensureRunBreak(endIndex);409410addAttributeRunData(attribute, value, beginRunIndex, endRunIndex);411}412413private final void createRunAttributeDataVectors() {414// use temporary variables so things remain consistent in case of an exception415int[] newRunStarts = new int[INITIAL_CAPACITY];416417@SuppressWarnings("unchecked")418Vector<Attribute>[] newRunAttributes = (Vector<Attribute>[]) new Vector<?>[INITIAL_CAPACITY];419420@SuppressWarnings("unchecked")421Vector<Object>[] newRunAttributeValues = (Vector<Object>[]) new Vector<?>[INITIAL_CAPACITY];422423runStarts = newRunStarts;424runAttributes = newRunAttributes;425runAttributeValues = newRunAttributeValues;426runCount = 1; // assume initial run starting at index 0427}428429// ensure there's a run break at offset, return the index of the run430private final int ensureRunBreak(int offset) {431return ensureRunBreak(offset, true);432}433434/**435* Ensures there is a run break at offset, returning the index of436* the run. If this results in splitting a run, two things can happen:437* <ul>438* <li>If copyAttrs is true, the attributes from the existing run439* will be placed in both of the newly created runs.440* <li>If copyAttrs is false, the attributes from the existing run441* will NOT be copied to the run to the right (>= offset) of the break,442* but will exist on the run to the left (< offset).443* </ul>444*/445private final int ensureRunBreak(int offset, boolean copyAttrs) {446if (offset == length()) {447return runCount;448}449450// search for the run index where this offset should be451int runIndex = 0;452while (runIndex < runCount && runStarts[runIndex] < offset) {453runIndex++;454}455456// if the offset is at a run start already, we're done457if (runIndex < runCount && runStarts[runIndex] == offset) {458return runIndex;459}460461// we'll have to break up a run462// first, make sure we have enough space in our arrays463int currentCapacity = runStarts.length;464if (runCount == currentCapacity) {465// We need to resize - we grow capacity by 25%.466int newCapacity = currentCapacity + (currentCapacity >> 2);467468// use temporary variables so things remain consistent in case of an exception469int[] newRunStarts =470Arrays.copyOf(runStarts, newCapacity);471Vector<Attribute>[] newRunAttributes =472Arrays.copyOf(runAttributes, newCapacity);473Vector<Object>[] newRunAttributeValues =474Arrays.copyOf(runAttributeValues, newCapacity);475476runStarts = newRunStarts;477runAttributes = newRunAttributes;478runAttributeValues = newRunAttributeValues;479}480481// make copies of the attribute information of the old run that the new one used to be part of482// use temporary variables so things remain consistent in case of an exception483Vector<Attribute> newRunAttributes = null;484Vector<Object> newRunAttributeValues = null;485486if (copyAttrs) {487Vector<Attribute> oldRunAttributes = runAttributes[runIndex - 1];488Vector<Object> oldRunAttributeValues = runAttributeValues[runIndex - 1];489if (oldRunAttributes != null) {490newRunAttributes = new Vector<>(oldRunAttributes);491}492if (oldRunAttributeValues != null) {493newRunAttributeValues = new Vector<>(oldRunAttributeValues);494}495}496497// now actually break up the run498runCount++;499for (int i = runCount - 1; i > runIndex; i--) {500runStarts[i] = runStarts[i - 1];501runAttributes[i] = runAttributes[i - 1];502runAttributeValues[i] = runAttributeValues[i - 1];503}504runStarts[runIndex] = offset;505runAttributes[runIndex] = newRunAttributes;506runAttributeValues[runIndex] = newRunAttributeValues;507508return runIndex;509}510511// add the attribute attribute/value to all runs where beginRunIndex <= runIndex < endRunIndex512private void addAttributeRunData(Attribute attribute, Object value,513int beginRunIndex, int endRunIndex) {514515for (int i = beginRunIndex; i < endRunIndex; i++) {516int keyValueIndex = -1; // index of key and value in our vectors; assume we don't have an entry yet517if (runAttributes[i] == null) {518Vector<Attribute> newRunAttributes = new Vector<>();519Vector<Object> newRunAttributeValues = new Vector<>();520runAttributes[i] = newRunAttributes;521runAttributeValues[i] = newRunAttributeValues;522} else {523// check whether we have an entry already524keyValueIndex = runAttributes[i].indexOf(attribute);525}526527if (keyValueIndex == -1) {528// create new entry529int oldSize = runAttributes[i].size();530runAttributes[i].addElement(attribute);531try {532runAttributeValues[i].addElement(value);533}534catch (Exception e) {535runAttributes[i].setSize(oldSize);536runAttributeValues[i].setSize(oldSize);537}538} else {539// update existing entry540runAttributeValues[i].set(keyValueIndex, value);541}542}543}544545/**546* Creates an AttributedCharacterIterator instance that provides access to the entire contents of547* this string.548*549* @return An iterator providing access to the text and its attributes.550*/551public AttributedCharacterIterator getIterator() {552return getIterator(null, 0, length());553}554555/**556* Creates an AttributedCharacterIterator instance that provides access to557* selected contents of this string.558* Information about attributes not listed in attributes that the559* implementor may have need not be made accessible through the iterator.560* If the list is null, all available attribute information should be made561* accessible.562*563* @param attributes a list of attributes that the client is interested in564* @return an iterator providing access to the entire text and its selected attributes565*/566public AttributedCharacterIterator getIterator(Attribute[] attributes) {567return getIterator(attributes, 0, length());568}569570/**571* Creates an AttributedCharacterIterator instance that provides access to572* selected contents of this string.573* Information about attributes not listed in attributes that the574* implementor may have need not be made accessible through the iterator.575* If the list is null, all available attribute information should be made576* accessible.577*578* @param attributes a list of attributes that the client is interested in579* @param beginIndex the index of the first character580* @param endIndex the index of the character following the last character581* @return an iterator providing access to the text and its attributes582* @throws IllegalArgumentException if beginIndex is less than 0,583* endIndex is greater than the length of the string, or beginIndex is584* greater than endIndex.585*/586public AttributedCharacterIterator getIterator(Attribute[] attributes, int beginIndex, int endIndex) {587return new AttributedStringIterator(attributes, beginIndex, endIndex);588}589590// all (with the exception of length) reading operations are private,591// since AttributedString instances are accessed through iterators.592593// length is package private so that CharacterIteratorFieldDelegate can594// access it without creating an AttributedCharacterIterator.595int length() {596return text.length();597}598599private char charAt(int index) {600return text.charAt(index);601}602603private synchronized Object getAttribute(Attribute attribute, int runIndex) {604Vector<Attribute> currentRunAttributes = runAttributes[runIndex];605Vector<Object> currentRunAttributeValues = runAttributeValues[runIndex];606if (currentRunAttributes == null) {607return null;608}609int attributeIndex = currentRunAttributes.indexOf(attribute);610if (attributeIndex != -1) {611return currentRunAttributeValues.elementAt(attributeIndex);612}613else {614return null;615}616}617618// gets an attribute value, but returns an annotation only if it's range does not extend outside the range beginIndex..endIndex619private Object getAttributeCheckRange(Attribute attribute, int runIndex, int beginIndex, int endIndex) {620Object value = getAttribute(attribute, runIndex);621if (value instanceof Annotation) {622// need to check whether the annotation's range extends outside the iterator's range623if (beginIndex > 0) {624int currIndex = runIndex;625int runStart = runStarts[currIndex];626while (runStart >= beginIndex &&627valuesMatch(value, getAttribute(attribute, currIndex - 1))) {628currIndex--;629runStart = runStarts[currIndex];630}631if (runStart < beginIndex) {632// annotation's range starts before iterator's range633return null;634}635}636int textLength = length();637if (endIndex < textLength) {638int currIndex = runIndex;639int runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength;640while (runLimit <= endIndex &&641valuesMatch(value, getAttribute(attribute, currIndex + 1))) {642currIndex++;643runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength;644}645if (runLimit > endIndex) {646// annotation's range ends after iterator's range647return null;648}649}650// annotation's range is subrange of iterator's range,651// so we can return the value652}653return value;654}655656// returns whether all specified attributes have equal values in the runs with the given indices657private boolean attributeValuesMatch(Set<? extends Attribute> attributes, int runIndex1, int runIndex2) {658Iterator<? extends Attribute> iterator = attributes.iterator();659while (iterator.hasNext()) {660Attribute key = iterator.next();661if (!valuesMatch(getAttribute(key, runIndex1), getAttribute(key, runIndex2))) {662return false;663}664}665return true;666}667668// returns whether the two objects are either both null or equal669private static final boolean valuesMatch(Object value1, Object value2) {670if (value1 == null) {671return value2 == null;672} else {673return value1.equals(value2);674}675}676677/**678* Appends the contents of the CharacterIterator iterator into the679* StringBuffer buf.680*/681private final void appendContents(StringBuffer buf,682CharacterIterator iterator) {683int index = iterator.getBeginIndex();684int end = iterator.getEndIndex();685686while (index < end) {687iterator.setIndex(index++);688buf.append(iterator.current());689}690}691692/**693* Sets the attributes for the range from offset to the next run break694* (typically the end of the text) to the ones specified in attrs.695* This is only meant to be called from the constructor!696*/697private void setAttributes(Map<Attribute, Object> attrs, int offset) {698if (runCount == 0) {699createRunAttributeDataVectors();700}701702int index = ensureRunBreak(offset, false);703int size;704705if (attrs != null && (size = attrs.size()) > 0) {706Vector<Attribute> runAttrs = new Vector<>(size);707Vector<Object> runValues = new Vector<>(size);708Iterator<Map.Entry<Attribute, Object>> iterator = attrs.entrySet().iterator();709710while (iterator.hasNext()) {711Map.Entry<Attribute, Object> entry = iterator.next();712713runAttrs.add(entry.getKey());714runValues.add(entry.getValue());715}716runAttributes[index] = runAttrs;717runAttributeValues[index] = runValues;718}719}720721/**722* Returns true if the attributes specified in last and attrs differ.723*/724private static <K,V> boolean mapsDiffer(Map<K, V> last, Map<K, V> attrs) {725if (last == null) {726return (attrs != null && attrs.size() > 0);727}728return (!last.equals(attrs));729}730731732// the iterator class associated with this string class733734private final class AttributedStringIterator implements AttributedCharacterIterator {735736// note on synchronization:737// we don't synchronize on the iterator, assuming that an iterator is only used in one thread.738// we do synchronize access to the AttributedString however, since it's more likely to be shared between threads.739740// start and end index for our iteration741private int beginIndex;742private int endIndex;743744// attributes that our client is interested in745private Attribute[] relevantAttributes;746747// the current index for our iteration748// invariant: beginIndex <= currentIndex <= endIndex749private int currentIndex;750751// information about the run that includes currentIndex752private int currentRunIndex;753private int currentRunStart;754private int currentRunLimit;755756// constructor757AttributedStringIterator(Attribute[] attributes, int beginIndex, int endIndex) {758759if (beginIndex < 0 || beginIndex > endIndex || endIndex > length()) {760throw new IllegalArgumentException("Invalid substring range");761}762763this.beginIndex = beginIndex;764this.endIndex = endIndex;765this.currentIndex = beginIndex;766updateRunInfo();767if (attributes != null) {768relevantAttributes = attributes.clone();769}770}771772// Object methods. See documentation in that class.773774public boolean equals(Object obj) {775if (this == obj) {776return true;777}778if (!(obj instanceof AttributedStringIterator that)) {779return false;780}781782if (AttributedString.this != that.getString())783return false;784if (currentIndex != that.currentIndex || beginIndex != that.beginIndex || endIndex != that.endIndex)785return false;786return true;787}788789public int hashCode() {790return text.hashCode() ^ currentIndex ^ beginIndex ^ endIndex;791}792793public Object clone() {794try {795AttributedStringIterator other = (AttributedStringIterator) super.clone();796return other;797}798catch (CloneNotSupportedException e) {799throw new InternalError(e);800}801}802803// CharacterIterator methods. See documentation in that interface.804805public char first() {806return internalSetIndex(beginIndex);807}808809public char last() {810if (endIndex == beginIndex) {811return internalSetIndex(endIndex);812} else {813return internalSetIndex(endIndex - 1);814}815}816817public char current() {818if (currentIndex == endIndex) {819return DONE;820} else {821return charAt(currentIndex);822}823}824825public char next() {826if (currentIndex < endIndex) {827return internalSetIndex(currentIndex + 1);828}829else {830return DONE;831}832}833834public char previous() {835if (currentIndex > beginIndex) {836return internalSetIndex(currentIndex - 1);837}838else {839return DONE;840}841}842843public char setIndex(int position) {844if (position < beginIndex || position > endIndex)845throw new IllegalArgumentException("Invalid index");846return internalSetIndex(position);847}848849public int getBeginIndex() {850return beginIndex;851}852853public int getEndIndex() {854return endIndex;855}856857public int getIndex() {858return currentIndex;859}860861// AttributedCharacterIterator methods. See documentation in that interface.862863public int getRunStart() {864return currentRunStart;865}866867public int getRunStart(Attribute attribute) {868if (currentRunStart == beginIndex || currentRunIndex == -1) {869return currentRunStart;870} else {871Object value = getAttribute(attribute);872int runStart = currentRunStart;873int runIndex = currentRunIndex;874while (runStart > beginIndex &&875valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex - 1))) {876runIndex--;877runStart = runStarts[runIndex];878}879if (runStart < beginIndex) {880runStart = beginIndex;881}882return runStart;883}884}885886public int getRunStart(Set<? extends Attribute> attributes) {887if (currentRunStart == beginIndex || currentRunIndex == -1) {888return currentRunStart;889} else {890int runStart = currentRunStart;891int runIndex = currentRunIndex;892while (runStart > beginIndex &&893AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex - 1)) {894runIndex--;895runStart = runStarts[runIndex];896}897if (runStart < beginIndex) {898runStart = beginIndex;899}900return runStart;901}902}903904public int getRunLimit() {905return currentRunLimit;906}907908public int getRunLimit(Attribute attribute) {909if (currentRunLimit == endIndex || currentRunIndex == -1) {910return currentRunLimit;911} else {912Object value = getAttribute(attribute);913int runLimit = currentRunLimit;914int runIndex = currentRunIndex;915while (runLimit < endIndex &&916valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex + 1))) {917runIndex++;918runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex;919}920if (runLimit > endIndex) {921runLimit = endIndex;922}923return runLimit;924}925}926927public int getRunLimit(Set<? extends Attribute> attributes) {928if (currentRunLimit == endIndex || currentRunIndex == -1) {929return currentRunLimit;930} else {931int runLimit = currentRunLimit;932int runIndex = currentRunIndex;933while (runLimit < endIndex &&934AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex + 1)) {935runIndex++;936runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex;937}938if (runLimit > endIndex) {939runLimit = endIndex;940}941return runLimit;942}943}944945public Map<Attribute,Object> getAttributes() {946if (runAttributes == null || currentRunIndex == -1 || runAttributes[currentRunIndex] == null) {947// ??? would be nice to return null, but current spec doesn't allow it948// returning Hashtable saves AttributeMap from dealing with emptiness949return new Hashtable<>();950}951return new AttributeMap(currentRunIndex, beginIndex, endIndex);952}953954public Set<Attribute> getAllAttributeKeys() {955// ??? This should screen out attribute keys that aren't relevant to the client956if (runAttributes == null) {957// ??? would be nice to return null, but current spec doesn't allow it958// returning HashSet saves us from dealing with emptiness959return new HashSet<>();960}961synchronized (AttributedString.this) {962// ??? should try to create this only once, then update if necessary,963// and give callers read-only view964Set<Attribute> keys = new HashSet<>();965int i = 0;966while (i < runCount) {967if (runStarts[i] < endIndex && (i == runCount - 1 || runStarts[i + 1] > beginIndex)) {968Vector<Attribute> currentRunAttributes = runAttributes[i];969if (currentRunAttributes != null) {970int j = currentRunAttributes.size();971while (j-- > 0) {972keys.add(currentRunAttributes.get(j));973}974}975}976i++;977}978return keys;979}980}981982public Object getAttribute(Attribute attribute) {983int runIndex = currentRunIndex;984if (runIndex < 0) {985return null;986}987return AttributedString.this.getAttributeCheckRange(attribute, runIndex, beginIndex, endIndex);988}989990// internally used methods991992private AttributedString getString() {993return AttributedString.this;994}995996// set the current index, update information about the current run if necessary,997// return the character at the current index998private char internalSetIndex(int position) {999currentIndex = position;1000if (position < currentRunStart || position >= currentRunLimit) {1001updateRunInfo();1002}1003if (currentIndex == endIndex) {1004return DONE;1005} else {1006return charAt(position);1007}1008}10091010// update the information about the current run1011private void updateRunInfo() {1012if (currentIndex == endIndex) {1013currentRunStart = currentRunLimit = endIndex;1014currentRunIndex = -1;1015} else {1016synchronized (AttributedString.this) {1017int runIndex = -1;1018while (runIndex < runCount - 1 && runStarts[runIndex + 1] <= currentIndex)1019runIndex++;1020currentRunIndex = runIndex;1021if (runIndex >= 0) {1022currentRunStart = runStarts[runIndex];1023if (currentRunStart < beginIndex)1024currentRunStart = beginIndex;1025}1026else {1027currentRunStart = beginIndex;1028}1029if (runIndex < runCount - 1) {1030currentRunLimit = runStarts[runIndex + 1];1031if (currentRunLimit > endIndex)1032currentRunLimit = endIndex;1033}1034else {1035currentRunLimit = endIndex;1036}1037}1038}1039}10401041}10421043// the map class associated with this string class, giving access to the attributes of one run10441045private final class AttributeMap extends AbstractMap<Attribute,Object> {10461047int runIndex;1048int beginIndex;1049int endIndex;10501051AttributeMap(int runIndex, int beginIndex, int endIndex) {1052this.runIndex = runIndex;1053this.beginIndex = beginIndex;1054this.endIndex = endIndex;1055}10561057public Set<Map.Entry<Attribute, Object>> entrySet() {1058HashSet<Map.Entry<Attribute, Object>> set = new HashSet<>();1059synchronized (AttributedString.this) {1060int size = runAttributes[runIndex].size();1061for (int i = 0; i < size; i++) {1062Attribute key = runAttributes[runIndex].get(i);1063Object value = runAttributeValues[runIndex].get(i);1064if (value instanceof Annotation) {1065value = AttributedString.this.getAttributeCheckRange(key,1066runIndex, beginIndex, endIndex);1067if (value == null) {1068continue;1069}1070}10711072Map.Entry<Attribute, Object> entry = new AttributeEntry(key, value);1073set.add(entry);1074}1075}1076return set;1077}10781079public Object get(Object key) {1080return AttributedString.this.getAttributeCheckRange((Attribute) key, runIndex, beginIndex, endIndex);1081}1082}1083}10841085class AttributeEntry implements Map.Entry<Attribute,Object> {10861087private Attribute key;1088private Object value;10891090AttributeEntry(Attribute key, Object value) {1091this.key = key;1092this.value = value;1093}10941095public boolean equals(Object o) {1096if (!(o instanceof AttributeEntry other)) {1097return false;1098}1099return other.key.equals(key) && Objects.equals(other.value, value);1100}11011102public Attribute getKey() {1103return key;1104}11051106public Object getValue() {1107return value;1108}11091110public Object setValue(Object newValue) {1111throw new UnsupportedOperationException();1112}11131114public int hashCode() {1115return key.hashCode() ^ (value==null ? 0 : value.hashCode());1116}11171118public String toString() {1119return key.toString()+"="+value.toString();1120}1121}112211231124