0001: /*
0002: * @(#)AttributedString.java 1.36 06/10/10
0003: *
0004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: *
0026: */
0027:
0028: package java.text;
0029:
0030: import java.util.*;
0031: import java.text.AttributedCharacterIterator.Attribute;
0032:
0033: /**
0034: * An AttributedString holds text and related attribute information. It
0035: * may be used as the actual data storage in some cases where a text
0036: * reader wants to access attributed text through the AttributedCharacterIterator
0037: * interface.
0038: *
0039: * @see AttributedCharacterIterator
0040: * @see Annotation
0041: * @since 1.2
0042: */
0043:
0044: public class AttributedString {
0045:
0046: // since there are no vectors of int, we have to use arrays.
0047: // We allocate them in chunks of 10 elements so we don't have to allocate all the time.
0048: private static final int ARRAY_SIZE_INCREMENT = 10;
0049:
0050: // field holding the text
0051: String text;
0052:
0053: // fields holding run attribute information
0054: // run attributes are organized by run
0055: int runArraySize; // current size of the arrays
0056: int runCount; // actual number of runs, <= runArraySize
0057: int runStarts[]; // start index for each run
0058: Vector runAttributes[]; // vector of attribute keys for each run
0059: Vector runAttributeValues[]; // parallel vector of attribute values for each run
0060:
0061: /**
0062: * Constructs an AttributedString instance with the given
0063: * AttributedCharacterIterators.
0064: *
0065: * @param iterators AttributedCharacterIterators to construct
0066: * AttributedString from.
0067: * @throws NullPointerException if iterators is null
0068: */
0069: AttributedString(AttributedCharacterIterator[] iterators) {
0070: if (iterators == null) {
0071: throw new NullPointerException("Iterators must not be null");
0072: }
0073: if (iterators.length == 0) {
0074: text = "";
0075: } else {
0076: // Build the String contents
0077: StringBuffer buffer = new StringBuffer();
0078: for (int counter = 0; counter < iterators.length; counter++) {
0079: appendContents(buffer, iterators[counter]);
0080: }
0081:
0082: text = buffer.toString();
0083:
0084: if (text.length() > 0) {
0085: // Determine the runs, creating a new run when the attributes
0086: // differ.
0087: int offset = 0;
0088: Map last = null;
0089:
0090: for (int counter = 0; counter < iterators.length; counter++) {
0091: AttributedCharacterIterator iterator = iterators[counter];
0092: int start = iterator.getBeginIndex();
0093: int end = iterator.getEndIndex();
0094: int index = start;
0095:
0096: while (index < end) {
0097: iterator.setIndex(index);
0098:
0099: Map attrs = iterator.getAttributes();
0100:
0101: if (mapsDiffer(last, attrs)) {
0102: setAttributes(attrs, index - start + offset);
0103: }
0104: last = attrs;
0105: index = iterator.getRunLimit();
0106: }
0107: offset += (end - start);
0108: }
0109: }
0110: }
0111: }
0112:
0113: /**
0114: * Constructs an AttributedString instance with the given text.
0115: * @param text The text for this attributed string.
0116: */
0117: public AttributedString(String text) {
0118: if (text == null) {
0119: throw new NullPointerException();
0120: }
0121: this .text = text;
0122: }
0123:
0124: /**
0125: * Constructs an AttributedString instance with the given text and attributes.
0126: * @param text The text for this attributed string.
0127: * @param attributes The attributes that apply to the entire string.
0128: * @exception IllegalArgumentException if the text has length 0
0129: * and the attributes parameter is not an empty Map (attributes
0130: * cannot be applied to a 0-length range).
0131: */
0132: public AttributedString(String text, Map attributes) {
0133: if (text == null || attributes == null) {
0134: throw new NullPointerException();
0135: }
0136: this .text = text;
0137:
0138: if (text.length() == 0) {
0139: if (attributes.isEmpty())
0140: return;
0141: throw new IllegalArgumentException(
0142: "Can't add attribute to 0-length text");
0143: }
0144:
0145: int attributeCount = attributes.size();
0146: if (attributeCount > 0) {
0147: createRunAttributeDataVectors();
0148: Vector newRunAttributes = new Vector(attributeCount);
0149: Vector newRunAttributeValues = new Vector(attributeCount);
0150: runAttributes[0] = newRunAttributes;
0151: runAttributeValues[0] = newRunAttributeValues;
0152: Iterator iterator = attributes.entrySet().iterator();
0153: while (iterator.hasNext()) {
0154: Map.Entry entry = (Map.Entry) iterator.next();
0155: newRunAttributes.addElement(entry.getKey());
0156: newRunAttributeValues.addElement(entry.getValue());
0157: }
0158: }
0159: }
0160:
0161: /**
0162: * Constructs an AttributedString instance with the given attributed
0163: * text represented by AttributedCharacterIterator.
0164: * @param text The text for this attributed string.
0165: */
0166: public AttributedString(AttributedCharacterIterator text) {
0167: // If performance is critical, this constructor should be
0168: // implemented here rather than invoking the constructor for a
0169: // subrange. We can avoid some range checking in the loops.
0170: this (text, text.getBeginIndex(), text.getEndIndex(), null);
0171: }
0172:
0173: /**
0174: * Constructs an AttributedString instance with the subrange of
0175: * the given attributed text represented by
0176: * AttributedCharacterIterator. If the given range produces an
0177: * empty text, all attributes will be discarded. Note that any
0178: * attributes wrapped by an Annotation object are discarded for a
0179: * subrange of the original attribute range.
0180: *
0181: * @param text The text for this attributed string.
0182: * @param beginIndex Index of the first character of the range.
0183: * @param endIndex Index of the character following the last character
0184: * of the range.
0185: * @exception IllegalArgumentException if the subrange given by
0186: * beginIndex and endIndex is out of the text range.
0187: * @see java.text.Annotation
0188: */
0189: public AttributedString(AttributedCharacterIterator text,
0190: int beginIndex, int endIndex) {
0191: this (text, beginIndex, endIndex, null);
0192: }
0193:
0194: /**
0195: * Constructs an AttributedString instance with the subrange of
0196: * the given attributed text represented by
0197: * AttributedCharacterIterator. Only attributes that match the
0198: * given attributes will be incorporated into the instance. If the
0199: * given range produces an empty text, all attributes will be
0200: * discarded. Note that any attributes wrapped by an Annotation
0201: * object are discarded for a subrange of the original attribute
0202: * range.
0203: *
0204: * @param text The text for this attributed string.
0205: * @param beginIndex Index of the first character of the range.
0206: * @param endIndex Index of the character following the last character
0207: * of the range.
0208: * @param attributes Specifies attributes to be extracted
0209: * from the text. If null is specified, all available attributes will
0210: * be used.
0211: * @exception IllegalArgumentException if the subrange given by
0212: * beginIndex and endIndex is out of the text range.
0213: * @see java.text.Annotation
0214: */
0215: public AttributedString(AttributedCharacterIterator text,
0216: int beginIndex, int endIndex, Attribute[] attributes) {
0217: if (text == null) {
0218: throw new NullPointerException();
0219: }
0220:
0221: // Validate the given subrange
0222: int textBeginIndex = text.getBeginIndex();
0223: int textEndIndex = text.getEndIndex();
0224: if (beginIndex < textBeginIndex || endIndex > textEndIndex
0225: || beginIndex > endIndex)
0226: throw new IllegalArgumentException(
0227: "Invalid substring range");
0228:
0229: // Copy the given string
0230: StringBuffer textBuffer = new StringBuffer();
0231: text.setIndex(beginIndex);
0232: for (char c = text.current(); text.getIndex() < endIndex; c = text
0233: .next())
0234: textBuffer.append(c);
0235: this .text = textBuffer.toString();
0236:
0237: if (beginIndex == endIndex)
0238: return;
0239:
0240: // Select attribute keys to be taken care of
0241: HashSet keys = new HashSet();
0242: if (attributes == null) {
0243: keys.addAll(text.getAllAttributeKeys());
0244: } else {
0245: for (int i = 0; i < attributes.length; i++)
0246: keys.add(attributes[i]);
0247: keys.retainAll(text.getAllAttributeKeys());
0248: }
0249: if (keys.isEmpty())
0250: return;
0251:
0252: // Get and set attribute runs for each attribute name. Need to
0253: // scan from the top of the text so that we can discard any
0254: // Annotation that is no longer applied to a subset text segment.
0255: Iterator itr = keys.iterator();
0256: while (itr.hasNext()) {
0257: Attribute attributeKey = (Attribute) itr.next();
0258: text.setIndex(textBeginIndex);
0259: while (text.getIndex() < endIndex) {
0260: int start = text.getRunStart(attributeKey);
0261: int limit = text.getRunLimit(attributeKey);
0262: Object value = text.getAttribute(attributeKey);
0263:
0264: if (value != null) {
0265: if (value instanceof Annotation) {
0266: if (start >= beginIndex && limit <= endIndex) {
0267: addAttribute(attributeKey, value, start
0268: - beginIndex, limit - beginIndex);
0269: } else {
0270: if (limit > endIndex)
0271: break;
0272: }
0273: } else {
0274: // if the run is beyond the given (subset) range, we
0275: // don't need to process further.
0276: if (start >= endIndex)
0277: break;
0278: if (limit > beginIndex) {
0279: // attribute is applied to any subrange
0280: if (start < beginIndex)
0281: start = beginIndex;
0282: if (limit > endIndex)
0283: limit = endIndex;
0284: if (start != limit) {
0285: addAttribute(attributeKey, value, start
0286: - beginIndex, limit
0287: - beginIndex);
0288: }
0289: }
0290: }
0291: }
0292: text.setIndex(limit);
0293: }
0294: }
0295: }
0296:
0297: /**
0298: * Adds an attribute to the entire string.
0299: * @param attribute the attribute key
0300: * @param value the value of the attribute; may be null
0301: * @exception IllegalArgumentException if the AttributedString has length 0
0302: * (attributes cannot be applied to a 0-length range).
0303: */
0304: public void addAttribute(Attribute attribute, Object value) {
0305:
0306: if (attribute == null) {
0307: throw new NullPointerException();
0308: }
0309:
0310: int len = length();
0311: if (len == 0) {
0312: throw new IllegalArgumentException(
0313: "Can't add attribute to 0-length text");
0314: }
0315:
0316: addAttributeImpl(attribute, value, 0, len);
0317: }
0318:
0319: /**
0320: * Adds an attribute to a subrange of the string.
0321: * @param attribute the attribute key
0322: * @param value The value of the attribute. May be null.
0323: * @param beginIndex Index of the first character of the range.
0324: * @param endIndex Index of the character following the last character of the range.
0325: * @exception IllegalArgumentException if beginIndex is less then 0, endIndex is
0326: * greater than the length of the string, or beginIndex and endIndex together don't
0327: * define a non-empty subrange of the string.
0328: */
0329: public void addAttribute(Attribute attribute, Object value,
0330: int beginIndex, int endIndex) {
0331:
0332: if (attribute == null) {
0333: throw new NullPointerException();
0334: }
0335:
0336: if (beginIndex < 0 || endIndex > length()
0337: || beginIndex >= endIndex) {
0338: throw new IllegalArgumentException(
0339: "Invalid substring range");
0340: }
0341:
0342: addAttributeImpl(attribute, value, beginIndex, endIndex);
0343: }
0344:
0345: /**
0346: * Adds a set of attributes to a subrange of the string.
0347: * @param attributes The attributes to be added to the string.
0348: * @param beginIndex Index of the first character of the range.
0349: * @param endIndex Index of the character following the last
0350: * character of the range.
0351: * @exception IllegalArgumentException if beginIndex is less then
0352: * 0, endIndex is greater than the length of the string, or
0353: * beginIndex and endIndex together don't define a non-empty
0354: * subrange of the string and the attributes parameter is not an
0355: * empty Map.
0356: */
0357: public void addAttributes(Map attributes, int beginIndex,
0358: int endIndex) {
0359: if (attributes == null) {
0360: throw new NullPointerException();
0361: }
0362:
0363: if (beginIndex < 0 || endIndex > length()
0364: || beginIndex > endIndex) {
0365: throw new IllegalArgumentException(
0366: "Invalid substring range");
0367: }
0368: if (beginIndex == endIndex) {
0369: if (attributes.isEmpty())
0370: return;
0371: throw new IllegalArgumentException(
0372: "Can't add attribute to 0-length text");
0373: }
0374:
0375: // make sure we have run attribute data vectors
0376: if (runCount == 0) {
0377: createRunAttributeDataVectors();
0378: }
0379:
0380: // break up runs if necessary
0381: int beginRunIndex = ensureRunBreak(beginIndex);
0382: int endRunIndex = ensureRunBreak(endIndex);
0383:
0384: Iterator iterator = attributes.entrySet().iterator();
0385: while (iterator.hasNext()) {
0386: Map.Entry entry = (Map.Entry) iterator.next();
0387: addAttributeRunData((Attribute) entry.getKey(), entry
0388: .getValue(), beginRunIndex, endRunIndex);
0389: }
0390: }
0391:
0392: private synchronized void addAttributeImpl(Attribute attribute,
0393: Object value, int beginIndex, int endIndex) {
0394:
0395: // make sure we have run attribute data vectors
0396: if (runCount == 0) {
0397: createRunAttributeDataVectors();
0398: }
0399:
0400: // break up runs if necessary
0401: int beginRunIndex = ensureRunBreak(beginIndex);
0402: int endRunIndex = ensureRunBreak(endIndex);
0403:
0404: addAttributeRunData(attribute, value, beginRunIndex,
0405: endRunIndex);
0406: }
0407:
0408: private final void createRunAttributeDataVectors() {
0409: // use temporary variables so things remain consistent in case of an exception
0410: int newRunStarts[] = new int[ARRAY_SIZE_INCREMENT];
0411: Vector newRunAttributes[] = new Vector[ARRAY_SIZE_INCREMENT];
0412: Vector newRunAttributeValues[] = new Vector[ARRAY_SIZE_INCREMENT];
0413: runStarts = newRunStarts;
0414: runAttributes = newRunAttributes;
0415: runAttributeValues = newRunAttributeValues;
0416: runArraySize = ARRAY_SIZE_INCREMENT;
0417: runCount = 1; // assume initial run starting at index 0
0418: }
0419:
0420: // ensure there's a run break at offset, return the index of the run
0421: private final int ensureRunBreak(int offset) {
0422: return ensureRunBreak(offset, true);
0423: }
0424:
0425: /**
0426: * Ensures there is a run break at offset, returning the index of
0427: * the run. If this results in splitting a run, two things can happen:
0428: * <ul>
0429: * <li>If copyAttrs is true, the attributes from the existing run
0430: * will be placed in both of the newly created runs.
0431: * <li>If copyAttrs is false, the attributes from the existing run
0432: * will NOT be copied to the run to the right (>= offset) of the break,
0433: * but will exist on the run to the left (< offset).
0434: * </ul>
0435: */
0436: private final int ensureRunBreak(int offset, boolean copyAttrs) {
0437: if (offset == length()) {
0438: return runCount;
0439: }
0440:
0441: // search for the run index where this offset should be
0442: int runIndex = 0;
0443: while (runIndex < runCount && runStarts[runIndex] < offset) {
0444: runIndex++;
0445: }
0446:
0447: // if the offset is at a run start already, we're done
0448: if (runIndex < runCount && runStarts[runIndex] == offset) {
0449: return runIndex;
0450: }
0451:
0452: // we'll have to break up a run
0453: // first, make sure we have enough space in our arrays
0454: if (runCount == runArraySize) {
0455: int newArraySize = runArraySize + ARRAY_SIZE_INCREMENT;
0456: int newRunStarts[] = new int[newArraySize];
0457: Vector newRunAttributes[] = new Vector[newArraySize];
0458: Vector newRunAttributeValues[] = new Vector[newArraySize];
0459: for (int i = 0; i < runArraySize; i++) {
0460: newRunStarts[i] = runStarts[i];
0461: newRunAttributes[i] = runAttributes[i];
0462: newRunAttributeValues[i] = runAttributeValues[i];
0463: }
0464: runStarts = newRunStarts;
0465: runAttributes = newRunAttributes;
0466: runAttributeValues = newRunAttributeValues;
0467: runArraySize = newArraySize;
0468: }
0469:
0470: // make copies of the attribute information of the old run that the new one used to be part of
0471: // use temporary variables so things remain consistent in case of an exception
0472: Vector newRunAttributes = null;
0473: Vector newRunAttributeValues = null;
0474:
0475: if (copyAttrs) {
0476: Vector oldRunAttributes = runAttributes[runIndex - 1];
0477: Vector oldRunAttributeValues = runAttributeValues[runIndex - 1];
0478: if (oldRunAttributes != null) {
0479: newRunAttributes = (Vector) oldRunAttributes.clone();
0480: }
0481: if (oldRunAttributeValues != null) {
0482: newRunAttributeValues = (Vector) oldRunAttributeValues
0483: .clone();
0484: }
0485: }
0486:
0487: // now actually break up the run
0488: runCount++;
0489: for (int i = runCount - 1; i > runIndex; i--) {
0490: runStarts[i] = runStarts[i - 1];
0491: runAttributes[i] = runAttributes[i - 1];
0492: runAttributeValues[i] = runAttributeValues[i - 1];
0493: }
0494: runStarts[runIndex] = offset;
0495: runAttributes[runIndex] = newRunAttributes;
0496: runAttributeValues[runIndex] = newRunAttributeValues;
0497:
0498: return runIndex;
0499: }
0500:
0501: // add the attribute attribute/value to all runs where beginRunIndex <= runIndex < endRunIndex
0502: private void addAttributeRunData(Attribute attribute, Object value,
0503: int beginRunIndex, int endRunIndex) {
0504:
0505: for (int i = beginRunIndex; i < endRunIndex; i++) {
0506: int keyValueIndex = -1; // index of key and value in our vectors; assume we don't have an entry yet
0507: if (runAttributes[i] == null) {
0508: Vector newRunAttributes = new Vector();
0509: Vector newRunAttributeValues = new Vector();
0510: runAttributes[i] = newRunAttributes;
0511: runAttributeValues[i] = newRunAttributeValues;
0512: } else {
0513: // check whether we have an entry already
0514: keyValueIndex = runAttributes[i].indexOf(attribute);
0515: }
0516:
0517: if (keyValueIndex == -1) {
0518: // create new entry
0519: int oldSize = runAttributes[i].size();
0520: runAttributes[i].addElement(attribute);
0521: try {
0522: runAttributeValues[i].addElement(value);
0523: } catch (Exception e) {
0524: runAttributes[i].setSize(oldSize);
0525: runAttributeValues[i].setSize(oldSize);
0526: }
0527: } else {
0528: // update existing entry
0529: runAttributeValues[i].set(keyValueIndex, value);
0530: }
0531: }
0532: }
0533:
0534: /**
0535: * Creates an AttributedCharacterIterator instance that provides access to the entire contents of
0536: * this string.
0537: *
0538: * @return An iterator providing access to the text and its attributes.
0539: */
0540: public AttributedCharacterIterator getIterator() {
0541: return getIterator(null, 0, length());
0542: }
0543:
0544: /**
0545: * Creates an AttributedCharacterIterator instance that provides access to
0546: * selected contents of this string.
0547: * Information about attributes not listed in attributes that the
0548: * implementor may have need not be made accessible through the iterator.
0549: * If the list is null, all available attribute information should be made
0550: * accessible.
0551: *
0552: * @param attributes a list of attributes that the client is interested in
0553: * @return an iterator providing access to the text and its attributes
0554: */
0555: public AttributedCharacterIterator getIterator(
0556: Attribute[] attributes) {
0557: return getIterator(attributes, 0, length());
0558: }
0559:
0560: /**
0561: * Creates an AttributedCharacterIterator instance that provides access to
0562: * selected contents of this string.
0563: * Information about attributes not listed in attributes that the
0564: * implementor may have need not be made accessible through the iterator.
0565: * If the list is null, all available attribute information should be made
0566: * accessible.
0567: *
0568: * @param attributes a list of attributes that the client is interested in
0569: * @param beginIndex the index of the first character
0570: * @param endIndex the index of the character following the last character
0571: * @return an iterator providing access to the text and its attributes
0572: * @exception IllegalArgumentException if beginIndex is less then 0,
0573: * endIndex is greater than the length of the string, or beginIndex is
0574: * greater than endIndex.
0575: */
0576: public AttributedCharacterIterator getIterator(
0577: Attribute[] attributes, int beginIndex, int endIndex) {
0578: return new AttributedStringIterator(attributes, beginIndex,
0579: endIndex);
0580: }
0581:
0582: // all (with the exception of length) reading operations are private,
0583: // since AttributedString instances are accessed through iterators.
0584:
0585: // length is package private so that CharacterIteratorFieldDelegate can
0586: // access it without creating an AttributedCharacterIterator.
0587: int length() {
0588: return text.length();
0589: }
0590:
0591: private char charAt(int index) {
0592: return text.charAt(index);
0593: }
0594:
0595: private synchronized Object getAttribute(Attribute attribute,
0596: int runIndex) {
0597: Vector currentRunAttributes = runAttributes[runIndex];
0598: Vector currentRunAttributeValues = runAttributeValues[runIndex];
0599: if (currentRunAttributes == null) {
0600: return null;
0601: }
0602: int attributeIndex = currentRunAttributes.indexOf(attribute);
0603: if (attributeIndex != -1) {
0604: return currentRunAttributeValues.elementAt(attributeIndex);
0605: } else {
0606: return null;
0607: }
0608: }
0609:
0610: // gets an attribute value, but returns an annotation only if it's range does not extend outside the range beginIndex..endIndex
0611: private Object getAttributeCheckRange(Attribute attribute,
0612: int runIndex, int beginIndex, int endIndex) {
0613: Object value = getAttribute(attribute, runIndex);
0614: if (value instanceof Annotation) {
0615: // need to check whether the annotation's range extends outside the iterator's range
0616: if (beginIndex > 0) {
0617: int currIndex = runIndex;
0618: int runStart = runStarts[currIndex];
0619: while (runStart >= beginIndex
0620: && valuesMatch(value, getAttribute(attribute,
0621: currIndex - 1))) {
0622: currIndex--;
0623: runStart = runStarts[currIndex];
0624: }
0625: if (runStart < beginIndex) {
0626: // annotation's range starts before iterator's range
0627: return null;
0628: }
0629: }
0630: int textLength = length();
0631: if (endIndex < textLength) {
0632: int currIndex = runIndex;
0633: int runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1]
0634: : textLength;
0635: while (runLimit <= endIndex
0636: && valuesMatch(value, getAttribute(attribute,
0637: currIndex + 1))) {
0638: currIndex++;
0639: runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1]
0640: : textLength;
0641: }
0642: if (runLimit > endIndex) {
0643: // annotation's range ends after iterator's range
0644: return null;
0645: }
0646: }
0647: // annotation's range is subrange of iterator's range,
0648: // so we can return the value
0649: }
0650: return value;
0651: }
0652:
0653: // returns whether all specified attributes have equal values in the runs with the given indices
0654: private boolean attributeValuesMatch(Set attributes, int runIndex1,
0655: int runIndex2) {
0656: Iterator iterator = attributes.iterator();
0657: while (iterator.hasNext()) {
0658: Attribute key = (Attribute) iterator.next();
0659: if (!valuesMatch(getAttribute(key, runIndex1),
0660: getAttribute(key, runIndex2))) {
0661: return false;
0662: }
0663: }
0664: return true;
0665: }
0666:
0667: // returns whether the two objects are either both null or equal
0668: private final static boolean valuesMatch(Object value1,
0669: Object value2) {
0670: if (value1 == null) {
0671: return value2 == null;
0672: } else {
0673: return value1.equals(value2);
0674: }
0675: }
0676:
0677: /**
0678: * Appends the contents of the CharacterIterator iterator into the
0679: * StringBuffer buf.
0680: */
0681: private final void appendContents(StringBuffer buf,
0682: CharacterIterator iterator) {
0683: int index = iterator.getBeginIndex();
0684: int end = iterator.getEndIndex();
0685:
0686: while (index < end) {
0687: iterator.setIndex(index++);
0688: buf.append(iterator.current());
0689: }
0690: }
0691:
0692: /**
0693: * Sets the attributes for the range from offset to the the next run break
0694: * (typically the end of the text) to the ones specified in attrs.
0695: * This is only meant to be called from the constructor!
0696: */
0697: private void setAttributes(Map attrs, int offset) {
0698: if (runCount == 0) {
0699: createRunAttributeDataVectors();
0700: }
0701:
0702: int index = ensureRunBreak(offset, false);
0703: int size;
0704:
0705: if (attrs != null && (size = attrs.size()) > 0) {
0706: Vector runAttrs = new Vector(size);
0707: Vector runValues = new Vector(size);
0708: Iterator iterator = attrs.entrySet().iterator();
0709:
0710: while (iterator.hasNext()) {
0711: Map.Entry entry = (Map.Entry) iterator.next();
0712:
0713: runAttrs.add(entry.getKey());
0714: runValues.add(entry.getValue());
0715: }
0716: runAttributes[index] = runAttrs;
0717: runAttributeValues[index] = runValues;
0718: }
0719: }
0720:
0721: /**
0722: * Returns true if the attributes specified in last and attrs differ.
0723: */
0724: private static boolean mapsDiffer(Map last, Map attrs) {
0725: if (last == null) {
0726: return (attrs != null && attrs.size() > 0);
0727: }
0728: return (!last.equals(attrs));
0729: }
0730:
0731: // the iterator class associated with this string class
0732:
0733: final private class AttributedStringIterator implements
0734: AttributedCharacterIterator {
0735:
0736: // note on synchronization:
0737: // we don't synchronize on the iterator, assuming that an iterator is only used in one thread.
0738: // we do synchronize access to the AttributedString however, since it's more likely to be shared between threads.
0739:
0740: // start and end index for our iteration
0741: private int beginIndex;
0742: private int endIndex;
0743:
0744: // attributes that our client is interested in
0745: private Attribute[] relevantAttributes;
0746:
0747: // the current index for our iteration
0748: // invariant: beginIndex <= currentIndex <= endIndex
0749: private int currentIndex;
0750:
0751: // information about the run that includes currentIndex
0752: private int currentRunIndex;
0753: private int currentRunStart;
0754: private int currentRunLimit;
0755:
0756: // constructor
0757: AttributedStringIterator(Attribute[] attributes,
0758: int beginIndex, int endIndex) {
0759:
0760: if (beginIndex < 0 || beginIndex > endIndex
0761: || endIndex > length()) {
0762: throw new IllegalArgumentException(
0763: "Invalid substring range");
0764: }
0765:
0766: this .beginIndex = beginIndex;
0767: this .endIndex = endIndex;
0768: this .currentIndex = beginIndex;
0769: updateRunInfo();
0770: if (attributes != null) {
0771: relevantAttributes = (Attribute[]) attributes.clone();
0772: }
0773: }
0774:
0775: // Object methods. See documentation in that class.
0776:
0777: public boolean equals(Object obj) {
0778: if (this == obj) {
0779: return true;
0780: }
0781: if (!(obj instanceof AttributedStringIterator)) {
0782: return false;
0783: }
0784:
0785: AttributedStringIterator that = (AttributedStringIterator) obj;
0786:
0787: if (AttributedString.this != that.getString())
0788: return false;
0789: if (currentIndex != that.currentIndex
0790: || beginIndex != that.beginIndex
0791: || endIndex != that.endIndex)
0792: return false;
0793: return true;
0794: }
0795:
0796: public int hashCode() {
0797: return text.hashCode() ^ currentIndex ^ beginIndex
0798: ^ endIndex;
0799: }
0800:
0801: public Object clone() {
0802: try {
0803: AttributedStringIterator other = (AttributedStringIterator) super
0804: .clone();
0805: return other;
0806: } catch (CloneNotSupportedException e) {
0807: throw new InternalError();
0808: }
0809: }
0810:
0811: // CharacterIterator methods. See documentation in that interface.
0812:
0813: public char first() {
0814: return internalSetIndex(beginIndex);
0815: }
0816:
0817: public char last() {
0818: if (endIndex == beginIndex) {
0819: return internalSetIndex(endIndex);
0820: } else {
0821: return internalSetIndex(endIndex - 1);
0822: }
0823: }
0824:
0825: public char current() {
0826: if (currentIndex == endIndex) {
0827: return DONE;
0828: } else {
0829: return charAt(currentIndex);
0830: }
0831: }
0832:
0833: public char next() {
0834: if (currentIndex < endIndex) {
0835: return internalSetIndex(currentIndex + 1);
0836: } else {
0837: return DONE;
0838: }
0839: }
0840:
0841: public char previous() {
0842: if (currentIndex > beginIndex) {
0843: return internalSetIndex(currentIndex - 1);
0844: } else {
0845: return DONE;
0846: }
0847: }
0848:
0849: public char setIndex(int position) {
0850: if (position < beginIndex || position > endIndex)
0851: throw new IllegalArgumentException("Invalid index");
0852: return internalSetIndex(position);
0853: }
0854:
0855: public int getBeginIndex() {
0856: return beginIndex;
0857: }
0858:
0859: public int getEndIndex() {
0860: return endIndex;
0861: }
0862:
0863: public int getIndex() {
0864: return currentIndex;
0865: }
0866:
0867: // AttributedCharacterIterator methods. See documentation in that interface.
0868:
0869: public int getRunStart() {
0870: return currentRunStart;
0871: }
0872:
0873: public int getRunStart(Attribute attribute) {
0874: if (currentRunStart == beginIndex || currentRunIndex == -1) {
0875: return currentRunStart;
0876: } else {
0877: Object value = getAttribute(attribute);
0878: int runStart = currentRunStart;
0879: int runIndex = currentRunIndex;
0880: while (runStart > beginIndex
0881: && valuesMatch(value, AttributedString.this
0882: .getAttribute(attribute, runIndex - 1))) {
0883: runIndex--;
0884: runStart = runStarts[runIndex];
0885: }
0886: if (runStart < beginIndex) {
0887: runStart = beginIndex;
0888: }
0889: return runStart;
0890: }
0891: }
0892:
0893: public int getRunStart(Set attributes) {
0894: if (currentRunStart == beginIndex || currentRunIndex == -1) {
0895: return currentRunStart;
0896: } else {
0897: int runStart = currentRunStart;
0898: int runIndex = currentRunIndex;
0899: while (runStart > beginIndex
0900: && AttributedString.this .attributeValuesMatch(
0901: attributes, currentRunIndex,
0902: runIndex - 1)) {
0903: runIndex--;
0904: runStart = runStarts[runIndex];
0905: }
0906: if (runStart < beginIndex) {
0907: runStart = beginIndex;
0908: }
0909: return runStart;
0910: }
0911: }
0912:
0913: public int getRunLimit() {
0914: return currentRunLimit;
0915: }
0916:
0917: public int getRunLimit(Attribute attribute) {
0918: if (currentRunLimit == endIndex || currentRunIndex == -1) {
0919: return currentRunLimit;
0920: } else {
0921: Object value = getAttribute(attribute);
0922: int runLimit = currentRunLimit;
0923: int runIndex = currentRunIndex;
0924: while (runLimit < endIndex
0925: && valuesMatch(value, AttributedString.this
0926: .getAttribute(attribute, runIndex + 1))) {
0927: runIndex++;
0928: runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1]
0929: : endIndex;
0930: }
0931: if (runLimit > endIndex) {
0932: runLimit = endIndex;
0933: }
0934: return runLimit;
0935: }
0936: }
0937:
0938: public int getRunLimit(Set attributes) {
0939: if (currentRunLimit == endIndex || currentRunIndex == -1) {
0940: return currentRunLimit;
0941: } else {
0942: int runLimit = currentRunLimit;
0943: int runIndex = currentRunIndex;
0944: while (runLimit < endIndex
0945: && AttributedString.this .attributeValuesMatch(
0946: attributes, currentRunIndex,
0947: runIndex + 1)) {
0948: runIndex++;
0949: runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1]
0950: : endIndex;
0951: }
0952: if (runLimit > endIndex) {
0953: runLimit = endIndex;
0954: }
0955: return runLimit;
0956: }
0957: }
0958:
0959: public Map getAttributes() {
0960: if (runAttributes == null || currentRunIndex == -1
0961: || runAttributes[currentRunIndex] == null) {
0962: // ??? would be nice to return null, but current spec doesn't allow it
0963: // returning Hashtable saves AttributeMap from dealing with emptiness
0964: return new Hashtable();
0965: }
0966: return new AttributeMap(currentRunIndex, beginIndex,
0967: endIndex);
0968: }
0969:
0970: public Set getAllAttributeKeys() {
0971: // ??? This should screen out attribute keys that aren't relevant to the client
0972: if (runAttributes == null) {
0973: // ??? would be nice to return null, but current spec doesn't allow it
0974: // returning HashSet saves us from dealing with emptiness
0975: return new HashSet();
0976: }
0977: synchronized (AttributedString.this ) {
0978: // ??? should try to create this only once, then update if necessary,
0979: // and give callers read-only view
0980: Set keys = new HashSet();
0981: int i = 0;
0982: while (i < runCount) {
0983: if (runStarts[i] < endIndex
0984: && (i == runCount - 1 || runStarts[i + 1] > beginIndex)) {
0985: Vector currentRunAttributes = runAttributes[i];
0986: if (currentRunAttributes != null) {
0987: int j = currentRunAttributes.size();
0988: while (j-- > 0) {
0989: keys.add(currentRunAttributes.get(j));
0990: }
0991: }
0992: }
0993: i++;
0994: }
0995: return keys;
0996: }
0997: }
0998:
0999: public Object getAttribute(Attribute attribute) {
1000: int runIndex = currentRunIndex;
1001: if (runIndex < 0) {
1002: return null;
1003: }
1004: return AttributedString.this .getAttributeCheckRange(
1005: attribute, runIndex, beginIndex, endIndex);
1006: }
1007:
1008: // internally used methods
1009:
1010: private AttributedString getString() {
1011: return AttributedString.this ;
1012: }
1013:
1014: // set the current index, update information about the current run if necessary,
1015: // return the character at the current index
1016: private char internalSetIndex(int position) {
1017: currentIndex = position;
1018: if (position < currentRunStart
1019: || position >= currentRunLimit) {
1020: updateRunInfo();
1021: }
1022: if (currentIndex == endIndex) {
1023: return DONE;
1024: } else {
1025: return charAt(position);
1026: }
1027: }
1028:
1029: // update the information about the current run
1030: private void updateRunInfo() {
1031: if (currentIndex == endIndex) {
1032: currentRunStart = currentRunLimit = endIndex;
1033: currentRunIndex = -1;
1034: } else {
1035: synchronized (AttributedString.this ) {
1036: int runIndex = -1;
1037: while (runIndex < runCount - 1
1038: && runStarts[runIndex + 1] <= currentIndex)
1039: runIndex++;
1040: currentRunIndex = runIndex;
1041: if (runIndex >= 0) {
1042: currentRunStart = runStarts[runIndex];
1043: if (currentRunStart < beginIndex)
1044: currentRunStart = beginIndex;
1045: } else {
1046: currentRunStart = beginIndex;
1047: }
1048: if (runIndex < runCount - 1) {
1049: currentRunLimit = runStarts[runIndex + 1];
1050: if (currentRunLimit > endIndex)
1051: currentRunLimit = endIndex;
1052: } else {
1053: currentRunLimit = endIndex;
1054: }
1055: }
1056: }
1057: }
1058:
1059: }
1060:
1061: // the map class associated with this string class, giving access to the attributes of one run
1062:
1063: final private class AttributeMap extends AbstractMap {
1064:
1065: int runIndex;
1066: int beginIndex;
1067: int endIndex;
1068:
1069: AttributeMap(int runIndex, int beginIndex, int endIndex) {
1070: this .runIndex = runIndex;
1071: this .beginIndex = beginIndex;
1072: this .endIndex = endIndex;
1073: }
1074:
1075: public Set entrySet() {
1076: HashSet set = new HashSet();
1077: synchronized (AttributedString.this ) {
1078: int size = runAttributes[runIndex].size();
1079: for (int i = 0; i < size; i++) {
1080: Attribute key = (Attribute) runAttributes[runIndex]
1081: .get(i);
1082: Object value = runAttributeValues[runIndex].get(i);
1083: if (value instanceof Annotation) {
1084: value = AttributedString.this
1085: .getAttributeCheckRange(key, runIndex,
1086: beginIndex, endIndex);
1087: if (value == null) {
1088: continue;
1089: }
1090: }
1091: Map.Entry entry = new AttributeEntry(key, value);
1092: set.add(entry);
1093: }
1094: }
1095: return set;
1096: }
1097:
1098: public Object get(Object key) {
1099: return AttributedString.this .getAttributeCheckRange(
1100: (Attribute) key, runIndex, beginIndex, endIndex);
1101: }
1102: }
1103: }
1104:
1105: class AttributeEntry implements Map.Entry {
1106:
1107: private Attribute key;
1108: private Object value;
1109:
1110: AttributeEntry(Attribute key, Object value) {
1111: this .key = key;
1112: this .value = value;
1113: }
1114:
1115: public boolean equals(Object o) {
1116: if (!(o instanceof AttributeEntry)) {
1117: return false;
1118: }
1119: AttributeEntry other = (AttributeEntry) o;
1120: return other.key.equals(key)
1121: && (value == null ? other.value == null : other.value
1122: .equals(value));
1123: }
1124:
1125: public Object getKey() {
1126: return key;
1127: }
1128:
1129: public Object getValue() {
1130: return value;
1131: }
1132:
1133: public Object setValue(Object newValue) {
1134: throw new UnsupportedOperationException();
1135: }
1136:
1137: public int hashCode() {
1138: return key.hashCode() ^ (value == null ? 0 : value.hashCode());
1139: }
1140:
1141: public String toString() {
1142: return key.toString() + "=" + value.toString();
1143: }
1144: }
|