blob: 7de6f579a211ff0894979aad4f51703e778f6a15 [file] [log] [blame]
/*
* (C) Copyright IBM Corp. 1998-2004. All Rights Reserved.
*
* The program is provided "as is" without any warranty express or
* implied, including the warranty of non-infringement and the implied
* warranties of merchantibility and fitness for a particular purpose.
* IBM will not be liable for any damages suffered by you as a result
* of using the Program. In no event will IBM be liable for any
* special, indirect or consequential damages or lost profits even if
* IBM has been advised of the possibility of their occurrence. IBM
* will not be liable for any third party claims against you.
*/
package com.ibm.richtext.styledtext;
import com.ibm.richtext.textlayout.attributes.AttributeMap;
import java.io.Externalizable;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.IOException;
import java.text.CharacterIterator;
/**
* This class is an implementation of MText, a modifyable, styled text
* storage model. Additionally, it supports persistance through the
* Externalizable interface.
* @see MText
*/
/*
10/28/96 {jf} - split the character and paragraph style access and setter function around...
just to keep things interesting.
8/7/96 {jf} - moved paragraph break implementation from AbstractText into Style text.
- added countStyles, getStyles, and ReplaceStyles implementation.
8/14/96 sfb eliminated StyleSheetIterator
8/29/96 {jbr} changed iter-based replace method - doesn't call at() unless it is safe to do so
Also, added checkStartAndLimit for debugging
7/31/98 Switched from Style to AttributeMap
*/
public final class StyledText extends MText implements Externalizable
{
static final String COPYRIGHT =
"(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
private static final int CURRENT_VERSION = 1;
private static final long serialVersionUID = 22356934;
/* unicode storage */
private MCharBuffer fCharBuffer;
/* character style storage */
private MStyleBuffer fStyleBuffer;
/* paragraph style storage */
private MParagraphBuffer fParagraphBuffer;
private transient int fTimeStamp = 0;
private transient int[] fDamagedRange = { Integer.MAX_VALUE,
Integer.MIN_VALUE };
private static class ForceModifier extends StyleModifier {
private AttributeMap fStyle = AttributeMap.EMPTY_ATTRIBUTE_MAP;
void setStyle(AttributeMap style) {
fStyle = style;
}
public AttributeMap modifyStyle(AttributeMap style) {
return fStyle;
}
}
// Keep this around foruse in replaceCharStylesWith. OK since
// this class isn't threadsafe anyway.
private transient ForceModifier forceModifier = null;
//======================================================
// CONSTRUCTORS
//======================================================
/**
* Create an empty text object.
*/
public StyledText()
{
this(0);
}
/**
* Create an empty text object ready to hold at least capacity chars.
* @param capacity the minimum capacity of the internal text buffer
*/
public StyledText(int capacity)
{
fCharBuffer = capacity>0? new CharBuffer(capacity) : new CharBuffer();
fStyleBuffer = new StyleBuffer(this, AttributeMap.EMPTY_ATTRIBUTE_MAP);
fParagraphBuffer = new ParagraphBuffer(fCharBuffer);
}
/**
* Create a text object with the characters in the string,
* in the given style.
* @param string the initial contents
* @param initialStyle the style of the initial text
*/
public StyledText(String string, AttributeMap initialStyle)
{
fCharBuffer = new CharBuffer(string.length());
fCharBuffer.replace(0, 0, string, 0, string.length());
fStyleBuffer = new StyleBuffer(this, initialStyle);
fParagraphBuffer = new ParagraphBuffer(fCharBuffer);
}
/**
* Create a text object from the given source.
* @param source the text to copy
*/
public StyledText(MConstText source) {
this();
append(source);
}
/**
* Create a text object from a subrange of the given source.
* @param source the text to copy from
* @param srcStart the index of the first character to copy
* @param srcLimit the index after the last character to copy
*/
public StyledText(MConstText source, int srcStart, int srcLimit) {
this();
replace(0, 0, source, srcStart, srcLimit);
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(CURRENT_VERSION);
out.writeObject(fCharBuffer);
out.writeObject(fStyleBuffer);
out.writeObject(fParagraphBuffer);
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
int version = in.readInt();
if (version != CURRENT_VERSION) {
throw new IOException("Invalid version of StyledText: " + version);
}
fCharBuffer = (MCharBuffer) in.readObject();
fStyleBuffer = (MStyleBuffer) in.readObject();
fParagraphBuffer = (MParagraphBuffer) in.readObject();
resetDamagedRange();
}
//======================================================
// MConstText INTERFACES
//======================================================
//--------------------------------------------------------
// character access
//--------------------------------------------------------
/**
* Return the character at offset <code>pos</code>.
* @param pos a valid offset into the text
* @return the character at offset <code>pos</code>
*/
public char at(int pos)
{
return fCharBuffer.at(pos);
}
/**
* Copy the characters in the range [<code>start</code>, <code>limit</code>)
* into the array <code>dst</code>, beginning at <code>dstStart</code>.
* @param start offset of first character which will be copied into the array
* @param limit offset immediately after the last character which will be copied into the array
* @param dst array in which to copy characters. The length of <code>dst</code> must be at least
* (<code>dstStart + limit - start</code>).
*/
public void extractChars(int start, int limit, char[] dst, int dstStart)
{
fCharBuffer.at(start, limit, dst, dstStart);
}
//-------------------------------------------------------
// text model creation
//-------------------------------------------------------
/**
* Create an MConstText containing the characters and styles in the range
* [<code>start</code>, <code>limit</code>).
* @param start offset of first character in the new text
* @param limit offset immediately after the last character in the new text
* @return an MConstText object containing the characters and styles in the given range
*/
public MConstText extract(int start, int limit)
{
return extractWritable(start, limit);
}
/**
* Create an MText containing the characters and styles in the range
* [<code>start</code>, <code>limit</code>).
* @param start offset of first character in the new text
* @param limit offset immediately after the last character in the new text
* @return an MConstText object containing the characters and styles in the given range
*/
public MText extractWritable(int start, int limit)
{
MText text = new StyledText();
text.replace(0, 0, this, start, limit);
text.resetDamagedRange();
return text;
}
//--------------------------------------------------------
// size/capacity
//--------------------------------------------------------
/**
* Return the length of the MConstText object. The length is the number of characters in the text.
* @return the length of the MConstText object
*/
public int length()
{
return fCharBuffer.length();
}
/**
* Create a <code>CharacterIterator</code> over the range [<code>start</code>, <code>limit</code>).
* @param start the beginning of the iterator's range
* @param limit the limit of the iterator's range
* @return a valid <code>CharacterIterator</code> over the specified range
* @see java.text.CharacterIterator
*/
public CharacterIterator createCharacterIterator(int start, int limit)
{
return fCharBuffer.createCharacterIterator(start, limit);
}
//--------------------------------------------------------
// character styles
//--------------------------------------------------------
/**
* Return the index of the first character in the character style run
* containing pos. All characters in a style run have the same character
* style.
* @return the style at offset <code>pos</code>
*/
public int characterStyleStart(int pos) {
checkPos(pos, LESS_THAN_LENGTH);
return fStyleBuffer.styleStart(pos);
}
/**
* Return the index after the last character in the character style run
* containing pos. All characters in a style run have the same character
* style.
* @return the style at offset <code>pos</code>
*/
public int characterStyleLimit(int pos) {
checkPos(pos, NOT_GREATER_THAN_LENGTH);
return fStyleBuffer.styleLimit(pos);
}
/**
* Return the style applied to the character at offset <code>pos</code>.
* @param pos a valid offset into the text
* @return the style at offset <code>pos</code>
*/
public AttributeMap characterStyleAt(int pos)
{
checkPos(pos, NOT_GREATER_THAN_LENGTH);
return fStyleBuffer.styleAt(pos);
}
//--------------------------------------------------------
// paragraph boundaries and styles
//--------------------------------------------------------
/**
* Return the start of the paragraph containing the character at offset <code>pos</code>.
* @param pos a valid offset into the text
* @return the start of the paragraph containing the character at offset <code>pos</code>
*/
public int paragraphStart(int pos)
{
checkPos(pos, NOT_GREATER_THAN_LENGTH);
return fParagraphBuffer.paragraphStart(pos);
}
/**
* Return the limit of the paragraph containing the character at offset <code>pos</code>.
* @param pos a valid offset into the text
* @return the limit of the paragraph containing the character at offset <code>pos</code>
*/
public int paragraphLimit(int pos)
{
checkPos(pos, NOT_GREATER_THAN_LENGTH);
return fParagraphBuffer.paragraphLimit(pos);
}
/**
* Return the paragraph style applied to the paragraph containing offset <code>pos</code>.
* @param pos a valid offset into the text
* @return the paragraph style in effect at <code>pos</code>
*/
public AttributeMap paragraphStyleAt(int pos)
{
checkPos(pos, NOT_GREATER_THAN_LENGTH);
return fParagraphBuffer.paragraphStyleAt(pos);
}
/**
* Return the current time stamp. The time stamp is
* incremented whenever the contents of the MConstText changes.
* @return the current paragraph style time stamp
*/
public int getTimeStamp() {
return fTimeStamp;
}
//======================================================
// MText INTERFACES
//======================================================
//--------------------------------------------------------
// character modfication functions
//--------------------------------------------------------
private void updateDamagedRange(int deleteStart,
int deleteLimit,
int insertLength) {
fDamagedRange[0] = Math.min(fDamagedRange[0], deleteStart);
if (fDamagedRange[1] >= deleteLimit) {
int lengthChange = insertLength - (deleteLimit-deleteStart);
fDamagedRange[1] += lengthChange;
}
else {
fDamagedRange[1] = deleteStart + insertLength;
}
}
/**
* Replace the characters and styles in the range [<code>start</code>, <code>limit</code>) with the characters
* and styles in <code>srcText</code> in the range [<code>srcStart</code>, <code>srcLimit</code>). <code>srcText</code> is not
* modified.
* @param start the offset at which the replace operation begins
* @param limit the offset at which the replace operation ends. The character and style at
* <code>limit</code> is not modified.
* @param text the source for the new characters and styles
* @param srcStart the offset into <code>srcText</code> where new characters and styles will be obtained
* @param srcLimit the offset into <code>srcText</code> where the new characters and styles end
*/
public void replace(int start, int limit, MConstText text, int srcStart, int srcLimit)
{
if (text == this) {
text = new StyledText(text);
}
if (start == limit && srcStart == srcLimit) {
return;
}
checkStartLimit(start, limit);
updateDamagedRange(start, limit, srcLimit-srcStart);
fCharBuffer.replace(start, limit, text, srcStart, srcLimit);
fStyleBuffer.replace(start, limit, text, srcStart, srcLimit);
fParagraphBuffer.replace(start, limit, text, srcStart, srcLimit, fDamagedRange);
fTimeStamp += 1;
}
/**
* Replace the characters and styles in the range [<code>start</code>, <code>limit</code>) with the characters
* and styles in <code>srcText</code>. <code>srcText</code> is not
* modified.
* @param start the offset at which the replace operation begins
* @param limit the offset at which the replace operation ends. The character and style at
* <code>limit</code> is not modified.
* @param text the source for the new characters and styles
*/
public void replace(int start, int limit, MConstText text) {
replace(start, limit, text, 0, text.length());
}
/**
* Replace the characters in the range [<code>start</code>, <code>limit</code>) with the characters
* in <code>srcChars</code> in the range [<code>srcStart</code>, <code>srcLimit</code>). New characters take on the style
* <code>charsStyle</code>.
* <code>srcChars</code> is not modified.
* @param start the offset at which the replace operation begins
* @param limit the offset at which the replace operation ends. The character at
* <code>limit</code> is not modified.
* @param srcChars the source for the new characters
* @param srcStart the offset into <code>srcChars</code> where new characters will be obtained
* @param srcLimit the offset into <code>srcChars</code> where the new characters end
* @param charsStyle the style of the new characters
*/
public void replace(int start, int limit, char[] srcChars, int srcStart, int srcLimit, AttributeMap charsStyle)
{
checkStartLimit(start, limit);
if (start == limit && srcStart == srcLimit) {
return;
}
updateDamagedRange(start, limit, srcLimit-srcStart);
fCharBuffer.replace(start, limit, srcChars, srcStart, srcLimit);
replaceCharStylesWith(start, limit, start + (srcLimit-srcStart), charsStyle);
fParagraphBuffer.deleteText(start, limit, fDamagedRange);
fParagraphBuffer.insertText(start, srcChars, srcStart, srcLimit);
fTimeStamp += 1;
}
private void replaceCharStylesWith(int start, int oldLimit, int newLimit, AttributeMap style) {
if (start < oldLimit) {
fStyleBuffer.deleteText(start, oldLimit);
}
if (start < newLimit) {
if (forceModifier == null) {
forceModifier = new ForceModifier();
}
forceModifier.setStyle(style);
fStyleBuffer.insertText(start, newLimit);
fStyleBuffer.modifyStyles(start, newLimit, forceModifier, null);
}
}
/**
* Replace the characters in the range [<code>start</code>, <code>limit</code>) with the character <code>srcChar</code>.
* The new character takes on the style <code>charStyle</code>
* @param start the offset at which the replace operation begins
* @param limit the offset at which the replace operation ends. The character at
* <code>limit</code> is not modified.
* @param srcChar the new character
* @param charStyle the style of the new character
*/
public void replace(int start, int limit, char srcChar, AttributeMap charStyle)
{
checkStartLimit(start, limit);
updateDamagedRange(start, limit, 1);
fCharBuffer.replace(start, limit, srcChar);
replaceCharStylesWith(start, limit, start + 1, charStyle);
if (start < limit) {
fParagraphBuffer.deleteText(start, limit, fDamagedRange);
}
fParagraphBuffer.insertText(start, srcChar);
fTimeStamp += 1;
}
/**
* Replace the entire contents of this MText (both characters and styles) with
* the contents of <code>srcText</code>.
* @param srcText the source for the new characters and styles
*/
public void replaceAll(MConstText srcText)
{
replace(0, length(), srcText, 0, srcText.length());
}
/**
* Insert the contents of <code>srcText</code> (both characters and styles) into this
* MText at the position specified by <code>pos</code>.
* @param pos The character offset where the new text is to be inserted.
* @param srcText The text to insert.
*/
public void insert(int pos, MConstText srcText)
{
replace(pos, pos, srcText, 0, srcText.length());
}
/**
* Append the contents of <code>srcText</code> (both characters and styles) to the
* end of this MText.
* @param srcText The text to append.
*/
public void append(MConstText srcText)
{
replace(length(), length(), srcText, 0, srcText.length());
}
/**
* Delete the specified range of characters (and styles).
* @param start Offset of the first character to delete.
* @param limit Offset of the first character after the range to delete.
*/
public void remove(int start, int limit)
{
replace(start, limit, (char[])null, 0, 0, AttributeMap.EMPTY_ATTRIBUTE_MAP);
}
/**
* Delete all characters and styles. Always increments time stamp.
*/
public void remove()
{
// rather than going through replace(), just reinitialize the StyledText,
// letting the old data structures fall on the floor
fCharBuffer = new CharBuffer();
fStyleBuffer = new StyleBuffer(this, AttributeMap.EMPTY_ATTRIBUTE_MAP);
fParagraphBuffer = new ParagraphBuffer(fCharBuffer);
fTimeStamp += 1;
fDamagedRange[0] = fDamagedRange[1] = 0;
}
//--------------------------------------------------------
// storage management
//--------------------------------------------------------
/**
* Minimize the amount of memory used by the MText object.
*/
public void compress() {
fCharBuffer.compress();
fStyleBuffer.compress();
fParagraphBuffer.compress();
}
//--------------------------------------------------------
// style modification
//--------------------------------------------------------
/**
* Set the style of all characters in the MText object to
* <code>AttributeMap.EMPTY_ATTRIBUTE_MAP</code>.
*/
public void removeCharacterStyles() {
fStyleBuffer = new StyleBuffer(this, AttributeMap.EMPTY_ATTRIBUTE_MAP);
fTimeStamp += 1;
fDamagedRange[0] = 0;
fDamagedRange[1] = length();
}
/**
* Invoke the given modifier on all character styles from start to limit.
* @param modifier the modifier to apply to the range.
* @param start the start of the range of text to modify.
* @param limit the limit of the range of text to modify.
*/
public void modifyCharacterStyles(int start, int limit, StyleModifier modifier) {
checkStartLimit(start, limit);
boolean modified = fStyleBuffer.modifyStyles(start,
limit,
modifier,
fDamagedRange);
if (modified) {
fTimeStamp += 1;
}
}
/**
* Invoke the given modifier on all paragraph styles in paragraphs
* containing characters in the range [start, limit).
* @param modifier the modifier to apply to the range.
* @param start the start of the range of text to modify.
* @param limit the limit of the range of text to modify.
*/
public void modifyParagraphStyles(int start, int limit, StyleModifier modifier) {
checkStartLimit(start, limit);
boolean modified = fParagraphBuffer.modifyParagraphStyles(start,
limit,
modifier,
fDamagedRange);
if (modified) {
fTimeStamp += 1;
}
}
/**
* Reset the damaged range to an empty interval, and begin accumulating the damaged
* range. The damaged range includes every index where a character, character style,
* or paragraph style has changed.
* @see #damagedRangeStart
* @see #damagedRangeLimit
*/
public void resetDamagedRange() {
fDamagedRange[0] = Integer.MAX_VALUE;
fDamagedRange[1] = Integer.MIN_VALUE;
}
/**
* Return the start of the damaged range.
* If the start is
* <code>Integer.MAX_VALUE</code> and the limit is
* <code>Integer.MIN_VALUE</code>, then the damaged range
* is empty.
* @return the start of the damaged range
* @see #damagedRangeLimit
* @see #resetDamagedRange
*/
public int damagedRangeStart() {
return fDamagedRange[0];
}
/**
* Return the limit of the damaged range.
* If the start is
* <code>Integer.MAX_VALUE</code> and the limit is
* <code>Integer.MIN_VALUE</code>, then the damaged range
* is empty.
* @return the limit of the damaged range
* @see #damagedRangeStart
* @see #resetDamagedRange
*/
public int damagedRangeLimit() {
return fDamagedRange[1];
}
public String toString()
{
String result ="";
for (int i = 0; i < length(); i++) {
result += at(i);
}
return result;
}
//======================================================
// IMPLEMENTATION
//======================================================
/* check a range to see if it is well formed and within the bounds of the text */
private void checkStartLimit(int start, int limit)
{
if (start > limit) {
//System.out.println("Start is less than limit. start:"+start+"; limit:"+limit);
throw new IllegalArgumentException("Start is greater than limit. start:"+start+"; limit:"+limit);
}
if (start < 0) {
//System.out.println("Start is negative. start:"+start);
throw new IllegalArgumentException("Start is negative. start:"+start);
}
if (limit > length()) {
//System.out.println("Limit is greater than length. limit:"+limit);
throw new IllegalArgumentException("Limit is greater than length. limit:"+limit);
}
}
private static final boolean LESS_THAN_LENGTH = false;
private static final boolean NOT_GREATER_THAN_LENGTH = true;
private void checkPos(int pos, boolean endAllowed) {
int lastValidPos = length();
if (endAllowed == LESS_THAN_LENGTH) {
--lastValidPos;
}
if (pos < 0 || pos > lastValidPos) {
throw new IllegalArgumentException("Position is out of range.");
}
}
}