/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package java.text; import java.text.AttributedCharacterIterator.Attribute; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; /** * Holds a string with attributes describing the characters of * this string. */ public class AttributedString { String text; Map<AttributedCharacterIterator.Attribute, List<Range>> attributeMap; static class Range { int start; int end; Object value; Range(int s, int e, Object v) { start = s; end = e; value = v; } } static class AttributedIterator implements AttributedCharacterIterator { private int begin, end, offset; private AttributedString attrString; private HashSet<Attribute> attributesAllowed; AttributedIterator(AttributedString attrString) { this.attrString = attrString; begin = 0; end = attrString.text.length(); offset = 0; } AttributedIterator(AttributedString attrString, AttributedCharacterIterator.Attribute[] attributes, int begin, int end) { if (begin < 0 || end > attrString.text.length() || begin > end) { throw new IllegalArgumentException(); } this.begin = begin; this.end = end; offset = begin; this.attrString = attrString; if (attributes != null) { HashSet<Attribute> set = new HashSet<Attribute>( (attributes.length * 4 / 3) + 1); for (int i = attributes.length; --i >= 0;) { set.add(attributes[i]); } attributesAllowed = set; } } /** * Returns a new {@code AttributedIterator} with the same source string, * begin, end, and current index as this attributed iterator. * * @return a shallow copy of this attributed iterator. * @see java.lang.Cloneable */ @Override @SuppressWarnings("unchecked") public Object clone() { try { AttributedIterator clone = (AttributedIterator) super.clone(); if (attributesAllowed != null) { clone.attributesAllowed = (HashSet<Attribute>) attributesAllowed .clone(); } return clone; } catch (CloneNotSupportedException e) { throw new AssertionError(e); } } public char current() { if (offset == end) { return DONE; } return attrString.text.charAt(offset); } public char first() { if (begin == end) { return DONE; } offset = begin; return attrString.text.charAt(offset); } /** * Returns the begin index in the source string. * * @return the index of the first character to iterate. */ public int getBeginIndex() { return begin; } /** * Returns the end index in the source String. * * @return the index one past the last character to iterate. */ public int getEndIndex() { return end; } /** * Returns the current index in the source String. * * @return the current index. */ public int getIndex() { return offset; } private boolean inRange(Range range) { if (!(range.value instanceof Annotation)) { return true; } return range.start >= begin && range.start < end && range.end > begin && range.end <= end; } private boolean inRange(List<Range> ranges) { Iterator<Range> it = ranges.iterator(); while (it.hasNext()) { Range range = it.next(); if (range.start >= begin && range.start < end) { return !(range.value instanceof Annotation) || (range.end > begin && range.end <= end); } else if (range.end > begin && range.end <= end) { return !(range.value instanceof Annotation) || (range.start >= begin && range.start < end); } } return false; } /** * Returns a set of attributes present in the {@code AttributedString}. * An empty set returned indicates that no attributes where defined. * * @return a set of attribute keys that may be empty. */ public Set<AttributedIterator.Attribute> getAllAttributeKeys() { if (begin == 0 && end == attrString.text.length() && attributesAllowed == null) { return attrString.attributeMap.keySet(); } Set<AttributedIterator.Attribute> result = new HashSet<Attribute>( (attrString.attributeMap.size() * 4 / 3) + 1); Iterator<Map.Entry<Attribute, List<Range>>> it = attrString.attributeMap .entrySet().iterator(); while (it.hasNext()) { Map.Entry<Attribute, List<Range>> entry = it.next(); if (attributesAllowed == null || attributesAllowed.contains(entry.getKey())) { List<Range> ranges = entry.getValue(); if (inRange(ranges)) { result.add(entry.getKey()); } } } return result; } private Object currentValue(List<Range> ranges) { Iterator<Range> it = ranges.iterator(); while (it.hasNext()) { Range range = it.next(); if (offset >= range.start && offset < range.end) { return inRange(range) ? range.value : null; } } return null; } public Object getAttribute( AttributedCharacterIterator.Attribute attribute) { if (attributesAllowed != null && !attributesAllowed.contains(attribute)) { return null; } ArrayList<Range> ranges = (ArrayList<Range>) attrString.attributeMap .get(attribute); if (ranges == null) { return null; } return currentValue(ranges); } public Map<Attribute, Object> getAttributes() { Map<Attribute, Object> result = new HashMap<Attribute, Object>( (attrString.attributeMap.size() * 4 / 3) + 1); Iterator<Map.Entry<Attribute, List<Range>>> it = attrString.attributeMap .entrySet().iterator(); while (it.hasNext()) { Map.Entry<Attribute, List<Range>> entry = it.next(); if (attributesAllowed == null || attributesAllowed.contains(entry.getKey())) { Object value = currentValue(entry.getValue()); if (value != null) { result.put(entry.getKey(), value); } } } return result; } public int getRunLimit() { return getRunLimit(getAllAttributeKeys()); } private int runLimit(List<Range> ranges) { int result = end; ListIterator<Range> it = ranges.listIterator(ranges.size()); while (it.hasPrevious()) { Range range = it.previous(); if (range.end <= begin) { break; } if (offset >= range.start && offset < range.end) { return inRange(range) ? range.end : result; } else if (offset >= range.end) { break; } result = range.start; } return result; } public int getRunLimit(AttributedCharacterIterator.Attribute attribute) { if (attributesAllowed != null && !attributesAllowed.contains(attribute)) { return end; } ArrayList<Range> ranges = (ArrayList<Range>) attrString.attributeMap .get(attribute); if (ranges == null) { return end; } return runLimit(ranges); } public int getRunLimit(Set<? extends Attribute> attributes) { int limit = end; Iterator<? extends Attribute> it = attributes.iterator(); while (it.hasNext()) { AttributedCharacterIterator.Attribute attribute = it.next(); int newLimit = getRunLimit(attribute); if (newLimit < limit) { limit = newLimit; } } return limit; } public int getRunStart() { return getRunStart(getAllAttributeKeys()); } private int runStart(List<Range> ranges) { int result = begin; Iterator<Range> it = ranges.iterator(); while (it.hasNext()) { Range range = it.next(); if (range.start >= end) { break; } if (offset >= range.start && offset < range.end) { return inRange(range) ? range.start : result; } else if (offset < range.start) { break; } result = range.end; } return result; } public int getRunStart(AttributedCharacterIterator.Attribute attribute) { if (attributesAllowed != null && !attributesAllowed.contains(attribute)) { return begin; } ArrayList<Range> ranges = (ArrayList<Range>) attrString.attributeMap .get(attribute); if (ranges == null) { return begin; } return runStart(ranges); } public int getRunStart(Set<? extends Attribute> attributes) { int start = begin; Iterator<? extends Attribute> it = attributes.iterator(); while (it.hasNext()) { AttributedCharacterIterator.Attribute attribute = it.next(); int newStart = getRunStart(attribute); if (newStart > start) { start = newStart; } } return start; } public char last() { if (begin == end) { return DONE; } offset = end - 1; return attrString.text.charAt(offset); } public char next() { if (offset >= (end - 1)) { offset = end; return DONE; } return attrString.text.charAt(++offset); } public char previous() { if (offset == begin) { return DONE; } return attrString.text.charAt(--offset); } public char setIndex(int location) { if (location < begin || location > end) { throw new IllegalArgumentException(); } offset = location; if (offset == end) { return DONE; } return attrString.text.charAt(offset); } } /** * Constructs an {@code AttributedString} from an {@code * AttributedCharacterIterator}, which represents attributed text. * * @param iterator * the {@code AttributedCharacterIterator} that contains the text * for this attributed string. */ public AttributedString(AttributedCharacterIterator iterator) { if (iterator.getBeginIndex() > iterator.getEndIndex()) { throw new IllegalArgumentException("Invalid substring range"); } StringBuilder buffer = new StringBuilder(); for (int i = iterator.getBeginIndex(); i < iterator.getEndIndex(); i++) { buffer.append(iterator.current()); iterator.next(); } text = buffer.toString(); Set<AttributedCharacterIterator.Attribute> attributes = iterator .getAllAttributeKeys(); if (attributes == null) { return; } attributeMap = new HashMap<Attribute, List<Range>>( (attributes.size() * 4 / 3) + 1); Iterator<Attribute> it = attributes.iterator(); while (it.hasNext()) { AttributedCharacterIterator.Attribute attribute = it.next(); iterator.setIndex(0); while (iterator.current() != CharacterIterator.DONE) { int start = iterator.getRunStart(attribute); int limit = iterator.getRunLimit(attribute); Object value = iterator.getAttribute(attribute); if (value != null) { addAttribute(attribute, value, start, limit); } iterator.setIndex(limit); } } } private AttributedString(AttributedCharacterIterator iterator, int start, int end, Set<Attribute> attributes) { if (start < iterator.getBeginIndex() || end > iterator.getEndIndex() || start > end) { throw new IllegalArgumentException(); } if (attributes == null) { return; } StringBuilder buffer = new StringBuilder(); iterator.setIndex(start); while (iterator.getIndex() < end) { buffer.append(iterator.current()); iterator.next(); } text = buffer.toString(); attributeMap = new HashMap<Attribute, List<Range>>( (attributes.size() * 4 / 3) + 1); Iterator<Attribute> it = attributes.iterator(); while (it.hasNext()) { AttributedCharacterIterator.Attribute attribute = it.next(); iterator.setIndex(start); while (iterator.getIndex() < end) { Object value = iterator.getAttribute(attribute); int runStart = iterator.getRunStart(attribute); int limit = iterator.getRunLimit(attribute); if ((value instanceof Annotation && runStart >= start && limit <= end) || (value != null && !(value instanceof Annotation))) { addAttribute(attribute, value, (runStart < start ? start : runStart) - start, (limit > end ? end : limit) - start); } iterator.setIndex(limit); } } } /** * Constructs an {@code AttributedString} from a range of the text contained * in the specified {@code AttributedCharacterIterator}, starting at {@code * start} and ending at {@code end}. All attributes will be copied to this * attributed string. * * @param iterator * the {@code AttributedCharacterIterator} that contains the text * for this attributed string. * @param start * the start index of the range of the copied text. * @param end * the end index of the range of the copied text. * @throws IllegalArgumentException * if {@code start} is less than first index of * {@code iterator}, {@code end} is greater than the last * index + 1 in {@code iterator} or if {@code start > end}. */ public AttributedString(AttributedCharacterIterator iterator, int start, int end) { this(iterator, start, end, iterator.getAllAttributeKeys()); } /** * Constructs an {@code AttributedString} from a range of the text contained * in the specified {@code AttributedCharacterIterator}, starting at {@code * start}, ending at {@code end} and it will copy the attributes defined in * the specified set. If the set is {@code null} then all attributes are * copied. * * @param iterator * the {@code AttributedCharacterIterator} that contains the text * for this attributed string. * @param start * the start index of the range of the copied text. * @param end * the end index of the range of the copied text. * @param attributes * the set of attributes that will be copied, or all if it is * {@code null}. * @throws IllegalArgumentException * if {@code start} is less than first index of * {@code iterator}, {@code end} is greater than the last index + * 1 in {@code iterator} or if {@code start > end}. */ public AttributedString(AttributedCharacterIterator iterator, int start, int end, AttributedCharacterIterator.Attribute[] attributes) { this(iterator, start, end, (attributes == null ? new HashSet<Attribute>() : new HashSet<Attribute>(Arrays.asList(attributes)))); } /** * Creates an {@code AttributedString} from the given text. * * @param value * the text to take as base for this attributed string. */ public AttributedString(String value) { if (value == null) { throw new NullPointerException("value == null"); } text = value; attributeMap = new HashMap<Attribute, List<Range>>(11); } /** * Creates an {@code AttributedString} from the given text and the * attributes. The whole text has the given attributes applied. * * @param value * the text to take as base for this attributed string. * @param attributes * the attributes that the text is associated with. * @throws IllegalArgumentException * if the length of {@code value} is 0 but the size of {@code * attributes} is greater than 0. * @throws NullPointerException * if {@code value} is {@code null}. */ public AttributedString(String value, Map<? extends AttributedCharacterIterator.Attribute, ?> attributes) { if (value == null) { throw new NullPointerException("value == null"); } if (value.length() == 0 && !attributes.isEmpty()) { throw new IllegalArgumentException("Cannot add attributes to empty string"); } text = value; attributeMap = new HashMap<Attribute, List<Range>>( (attributes.size() * 4 / 3) + 1); Iterator<?> it = attributes.entrySet().iterator(); while (it.hasNext()) { Map.Entry<?, ?> entry = (Map.Entry<?, ?>) it.next(); ArrayList<Range> ranges = new ArrayList<Range>(1); ranges.add(new Range(0, text.length(), entry.getValue())); attributeMap.put((AttributedCharacterIterator.Attribute) entry .getKey(), ranges); } } /** * Applies a given attribute to this string. * * @param attribute * the attribute that will be applied to this string. * @param value * the value of the attribute that will be applied to this * string. * @throws IllegalArgumentException * if the length of this attributed string is 0. * @throws NullPointerException * if {@code attribute} is {@code null}. */ public void addAttribute(AttributedCharacterIterator.Attribute attribute, Object value) { if (attribute == null) { throw new NullPointerException("attribute == null"); } if (text.length() == 0) { throw new IllegalArgumentException(); } List<Range> ranges = attributeMap.get(attribute); if (ranges == null) { ranges = new ArrayList<Range>(1); attributeMap.put(attribute, ranges); } else { ranges.clear(); } ranges.add(new Range(0, text.length(), value)); } /** * Applies a given attribute to the given range of this string. * * @param attribute * the attribute that will be applied to this string. * @param value * the value of the attribute that will be applied to this * string. * @param start * the start of the range where the attribute will be applied. * @param end * the end of the range where the attribute will be applied. * @throws IllegalArgumentException * if {@code start < 0}, {@code end} is greater than the length * of this string, or if {@code start >= end}. * @throws NullPointerException * if {@code attribute} is {@code null}. */ public void addAttribute(AttributedCharacterIterator.Attribute attribute, Object value, int start, int end) { if (attribute == null) { throw new NullPointerException("attribute == null"); } if (start < 0 || end > text.length() || start >= end) { throw new IllegalArgumentException(); } if (value == null) { return; } List<Range> ranges = attributeMap.get(attribute); if (ranges == null) { ranges = new ArrayList<Range>(1); ranges.add(new Range(start, end, value)); attributeMap.put(attribute, ranges); return; } ListIterator<Range> it = ranges.listIterator(); while (it.hasNext()) { Range range = it.next(); if (end <= range.start) { it.previous(); break; } else if (start < range.end || (start == range.end && value.equals(range.value))) { Range r1 = null, r3; it.remove(); r1 = new Range(range.start, start, range.value); r3 = new Range(end, range.end, range.value); while (end > range.end && it.hasNext()) { range = it.next(); if (end <= range.end) { if (end > range.start || (end == range.start && value.equals(range.value))) { it.remove(); r3 = new Range(end, range.end, range.value); break; } } else { it.remove(); } } if (value.equals(r1.value)) { if (value.equals(r3.value)) { it.add(new Range(r1.start < start ? r1.start : start, r3.end > end ? r3.end : end, r1.value)); } else { it.add(new Range(r1.start < start ? r1.start : start, end, r1.value)); if (r3.start < r3.end) { it.add(r3); } } } else { if (value.equals(r3.value)) { if (r1.start < r1.end) { it.add(r1); } it.add(new Range(start, r3.end > end ? r3.end : end, r3.value)); } else { if (r1.start < r1.end) { it.add(r1); } it.add(new Range(start, end, value)); if (r3.start < r3.end) { it.add(r3); } } } return; } } it.add(new Range(start, end, value)); } /** * Applies a given set of attributes to the given range of the string. * * @param attributes * the set of attributes that will be applied to this string. * @param start * the start of the range where the attribute will be applied. * @param end * the end of the range where the attribute will be applied. * @throws IllegalArgumentException * if {@code start < 0}, {@code end} is greater than the length * of this string, or if {@code start >= end}. */ public void addAttributes( Map<? extends AttributedCharacterIterator.Attribute, ?> attributes, int start, int end) { Iterator<?> it = attributes.entrySet().iterator(); while (it.hasNext()) { Map.Entry<?, ?> entry = (Map.Entry<?, ?>) it.next(); addAttribute( (AttributedCharacterIterator.Attribute) entry.getKey(), entry.getValue(), start, end); } } /** * Returns an {@code AttributedCharacterIterator} that gives access to the * complete content of this attributed string. * * @return the newly created {@code AttributedCharacterIterator}. */ public AttributedCharacterIterator getIterator() { return new AttributedIterator(this); } /** * Returns an {@code AttributedCharacterIterator} that gives access to the * complete content of this attributed string. Only attributes contained in * {@code attributes} are available from this iterator if they are defined * for this text. * * @param attributes * the array containing attributes that will be in the new * iterator if they are defined for this text. * @return the newly created {@code AttributedCharacterIterator}. */ public AttributedCharacterIterator getIterator( AttributedCharacterIterator.Attribute[] attributes) { return new AttributedIterator(this, attributes, 0, text.length()); } /** * Returns an {@code AttributedCharacterIterator} that gives access to the * contents of this attributed string starting at index {@code start} up to * index {@code end}. Only attributes contained in {@code attributes} are * available from this iterator if they are defined for this text. * * @param attributes * the array containing attributes that will be in the new * iterator if they are defined for this text. * @param start * the start index of the iterator on the underlying text. * @param end * the end index of the iterator on the underlying text. * @return the newly created {@code AttributedCharacterIterator}. */ public AttributedCharacterIterator getIterator( AttributedCharacterIterator.Attribute[] attributes, int start, int end) { return new AttributedIterator(this, attributes, start, end); } }