001: /*
002: * (C) Copyright IBM Corp. 1998-2004. All Rights Reserved.
003: *
004: * The program is provided "as is" without any warranty express or
005: * implied, including the warranty of non-infringement and the implied
006: * warranties of merchantibility and fitness for a particular purpose.
007: * IBM will not be liable for any damages suffered by you as a result
008: * of using the Program. In no event will IBM be liable for any
009: * special, indirect or consequential damages or lost profits even if
010: * IBM has been advised of the possibility of their occurrence. IBM
011: * will not be liable for any third party claims against you.
012: */
013: package com.ibm.richtext.styledtext;
014:
015: import com.ibm.richtext.textlayout.attributes.AttributeMap;
016:
017: import java.io.Externalizable;
018: import java.io.ObjectInput;
019: import java.io.ObjectOutput;
020: import java.io.IOException;
021: import java.text.CharacterIterator;
022:
023: /**
024: * This class is an implementation of MText, a modifyable, styled text
025: * storage model. Additionally, it supports persistance through the
026: * Externalizable interface.
027: * @see MText
028: */
029:
030: /*
031: 10/28/96 {jf} - split the character and paragraph style access and setter function around...
032: just to keep things interesting.
033: 8/7/96 {jf} - moved paragraph break implementation from AbstractText into Style text.
034: - added countStyles, getStyles, and ReplaceStyles implementation.
035:
036: 8/14/96 sfb eliminated StyleSheetIterator
037:
038: 8/29/96 {jbr} changed iter-based replace method - doesn't call at() unless it is safe to do so
039: Also, added checkStartAndLimit for debugging
040:
041: 7/31/98 Switched from Style to AttributeMap
042:
043: */
044:
045: public final class StyledText extends MText implements Externalizable {
046: static final String COPYRIGHT = "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
047: private static final int CURRENT_VERSION = 1;
048: private static final long serialVersionUID = 22356934;
049:
050: /* unicode storage */
051: private MCharBuffer fCharBuffer;
052: /* character style storage */
053: private MStyleBuffer fStyleBuffer;
054: /* paragraph style storage */
055: private MParagraphBuffer fParagraphBuffer;
056:
057: private transient int fTimeStamp = 0;
058: private transient int[] fDamagedRange = { Integer.MAX_VALUE,
059: Integer.MIN_VALUE };
060:
061: private static class ForceModifier extends StyleModifier {
062:
063: private AttributeMap fStyle = AttributeMap.EMPTY_ATTRIBUTE_MAP;
064:
065: void setStyle(AttributeMap style) {
066:
067: fStyle = style;
068: }
069:
070: public AttributeMap modifyStyle(AttributeMap style) {
071:
072: return fStyle;
073: }
074: }
075:
076: // Keep this around foruse in replaceCharStylesWith. OK since
077: // this class isn't threadsafe anyway.
078: private transient ForceModifier forceModifier = null;
079:
080: //======================================================
081: // CONSTRUCTORS
082: //======================================================
083: /**
084: * Create an empty text object.
085: */
086: public StyledText() {
087: this (0);
088: }
089:
090: /**
091: * Create an empty text object ready to hold at least capacity chars.
092: * @param capacity the minimum capacity of the internal text buffer
093: */
094: public StyledText(int capacity) {
095: fCharBuffer = capacity > 0 ? new CharBuffer(capacity)
096: : new CharBuffer();
097: fStyleBuffer = new StyleBuffer(this ,
098: AttributeMap.EMPTY_ATTRIBUTE_MAP);
099: fParagraphBuffer = new ParagraphBuffer(fCharBuffer);
100: }
101:
102: /**
103: * Create a text object with the characters in the string,
104: * in the given style.
105: * @param string the initial contents
106: * @param initialStyle the style of the initial text
107: */
108: public StyledText(String string, AttributeMap initialStyle) {
109: fCharBuffer = new CharBuffer(string.length());
110: fCharBuffer.replace(0, 0, string, 0, string.length());
111:
112: fStyleBuffer = new StyleBuffer(this , initialStyle);
113: fParagraphBuffer = new ParagraphBuffer(fCharBuffer);
114: }
115:
116: /**
117: * Create a text object from the given source.
118: * @param source the text to copy
119: */
120: public StyledText(MConstText source) {
121: this ();
122: append(source);
123: }
124:
125: /**
126: * Create a text object from a subrange of the given source.
127: * @param source the text to copy from
128: * @param srcStart the index of the first character to copy
129: * @param srcLimit the index after the last character to copy
130: */
131: public StyledText(MConstText source, int srcStart, int srcLimit) {
132: this ();
133: replace(0, 0, source, srcStart, srcLimit);
134: }
135:
136: public void writeExternal(ObjectOutput out) throws IOException {
137:
138: out.writeInt(CURRENT_VERSION);
139: out.writeObject(fCharBuffer);
140: out.writeObject(fStyleBuffer);
141: out.writeObject(fParagraphBuffer);
142: }
143:
144: public void readExternal(ObjectInput in) throws IOException,
145: ClassNotFoundException {
146:
147: int version = in.readInt();
148: if (version != CURRENT_VERSION) {
149: throw new IOException("Invalid version of StyledText: "
150: + version);
151: }
152: fCharBuffer = (MCharBuffer) in.readObject();
153: fStyleBuffer = (MStyleBuffer) in.readObject();
154: fParagraphBuffer = (MParagraphBuffer) in.readObject();
155:
156: resetDamagedRange();
157: }
158:
159: //======================================================
160: // MConstText INTERFACES
161: //======================================================
162:
163: //--------------------------------------------------------
164: // character access
165: //--------------------------------------------------------
166: /**
167: * Return the character at offset <code>pos</code>.
168: * @param pos a valid offset into the text
169: * @return the character at offset <code>pos</code>
170: */
171: public char at(int pos) {
172: return fCharBuffer.at(pos);
173: }
174:
175: /**
176: * Copy the characters in the range [<code>start</code>, <code>limit</code>)
177: * into the array <code>dst</code>, beginning at <code>dstStart</code>.
178: * @param start offset of first character which will be copied into the array
179: * @param limit offset immediately after the last character which will be copied into the array
180: * @param dst array in which to copy characters. The length of <code>dst</code> must be at least
181: * (<code>dstStart + limit - start</code>).
182: */
183: public void extractChars(int start, int limit, char[] dst,
184: int dstStart) {
185: fCharBuffer.at(start, limit, dst, dstStart);
186: }
187:
188: //-------------------------------------------------------
189: // text model creation
190: //-------------------------------------------------------
191: /**
192: * Create an MConstText containing the characters and styles in the range
193: * [<code>start</code>, <code>limit</code>).
194: * @param start offset of first character in the new text
195: * @param limit offset immediately after the last character in the new text
196: * @return an MConstText object containing the characters and styles in the given range
197: */
198: public MConstText extract(int start, int limit) {
199: return extractWritable(start, limit);
200: }
201:
202: /**
203: * Create an MText containing the characters and styles in the range
204: * [<code>start</code>, <code>limit</code>).
205: * @param start offset of first character in the new text
206: * @param limit offset immediately after the last character in the new text
207: * @return an MConstText object containing the characters and styles in the given range
208: */
209: public MText extractWritable(int start, int limit) {
210: MText text = new StyledText();
211: text.replace(0, 0, this , start, limit);
212: text.resetDamagedRange();
213: return text;
214: }
215:
216: //--------------------------------------------------------
217: // size/capacity
218: //--------------------------------------------------------
219: /**
220: * Return the length of the MConstText object. The length is the number of characters in the text.
221: * @return the length of the MConstText object
222: */
223: public int length() {
224: return fCharBuffer.length();
225: }
226:
227: /**
228: * Create a <code>CharacterIterator</code> over the range [<code>start</code>, <code>limit</code>).
229: * @param start the beginning of the iterator's range
230: * @param limit the limit of the iterator's range
231: * @return a valid <code>CharacterIterator</code> over the specified range
232: * @see java.text.CharacterIterator
233: */
234: public CharacterIterator createCharacterIterator(int start,
235: int limit) {
236: return fCharBuffer.createCharacterIterator(start, limit);
237: }
238:
239: //--------------------------------------------------------
240: // character styles
241: //--------------------------------------------------------
242:
243: /**
244: * Return the index of the first character in the character style run
245: * containing pos. All characters in a style run have the same character
246: * style.
247: * @return the style at offset <code>pos</code>
248: */
249: public int characterStyleStart(int pos) {
250:
251: checkPos(pos, LESS_THAN_LENGTH);
252: return fStyleBuffer.styleStart(pos);
253: }
254:
255: /**
256: * Return the index after the last character in the character style run
257: * containing pos. All characters in a style run have the same character
258: * style.
259: * @return the style at offset <code>pos</code>
260: */
261: public int characterStyleLimit(int pos) {
262:
263: checkPos(pos, NOT_GREATER_THAN_LENGTH);
264: return fStyleBuffer.styleLimit(pos);
265: }
266:
267: /**
268: * Return the style applied to the character at offset <code>pos</code>.
269: * @param pos a valid offset into the text
270: * @return the style at offset <code>pos</code>
271: */
272: public AttributeMap characterStyleAt(int pos) {
273: checkPos(pos, NOT_GREATER_THAN_LENGTH);
274: return fStyleBuffer.styleAt(pos);
275: }
276:
277: //--------------------------------------------------------
278: // paragraph boundaries and styles
279: //--------------------------------------------------------
280: /**
281: * Return the start of the paragraph containing the character at offset <code>pos</code>.
282: * @param pos a valid offset into the text
283: * @return the start of the paragraph containing the character at offset <code>pos</code>
284: */
285: public int paragraphStart(int pos) {
286: checkPos(pos, NOT_GREATER_THAN_LENGTH);
287: return fParagraphBuffer.paragraphStart(pos);
288: }
289:
290: /**
291: * Return the limit of the paragraph containing the character at offset <code>pos</code>.
292: * @param pos a valid offset into the text
293: * @return the limit of the paragraph containing the character at offset <code>pos</code>
294: */
295: public int paragraphLimit(int pos) {
296: checkPos(pos, NOT_GREATER_THAN_LENGTH);
297: return fParagraphBuffer.paragraphLimit(pos);
298: }
299:
300: /**
301: * Return the paragraph style applied to the paragraph containing offset <code>pos</code>.
302: * @param pos a valid offset into the text
303: * @return the paragraph style in effect at <code>pos</code>
304: */
305: public AttributeMap paragraphStyleAt(int pos) {
306: checkPos(pos, NOT_GREATER_THAN_LENGTH);
307: return fParagraphBuffer.paragraphStyleAt(pos);
308: }
309:
310: /**
311: * Return the current time stamp. The time stamp is
312: * incremented whenever the contents of the MConstText changes.
313: * @return the current paragraph style time stamp
314: */
315: public int getTimeStamp() {
316:
317: return fTimeStamp;
318: }
319:
320: //======================================================
321: // MText INTERFACES
322: //======================================================
323: //--------------------------------------------------------
324: // character modfication functions
325: //--------------------------------------------------------
326:
327: private void updateDamagedRange(int deleteStart, int deleteLimit,
328: int insertLength) {
329:
330: fDamagedRange[0] = Math.min(fDamagedRange[0], deleteStart);
331:
332: if (fDamagedRange[1] >= deleteLimit) {
333: int lengthChange = insertLength
334: - (deleteLimit - deleteStart);
335: fDamagedRange[1] += lengthChange;
336: } else {
337: fDamagedRange[1] = deleteStart + insertLength;
338: }
339: }
340:
341: /**
342: * Replace the characters and styles in the range [<code>start</code>, <code>limit</code>) with the characters
343: * and styles in <code>srcText</code> in the range [<code>srcStart</code>, <code>srcLimit</code>). <code>srcText</code> is not
344: * modified.
345: * @param start the offset at which the replace operation begins
346: * @param limit the offset at which the replace operation ends. The character and style at
347: * <code>limit</code> is not modified.
348: * @param text the source for the new characters and styles
349: * @param srcStart the offset into <code>srcText</code> where new characters and styles will be obtained
350: * @param srcLimit the offset into <code>srcText</code> where the new characters and styles end
351: */
352: public void replace(int start, int limit, MConstText text,
353: int srcStart, int srcLimit) {
354: if (text == this ) {
355: text = new StyledText(text);
356: }
357:
358: if (start == limit && srcStart == srcLimit) {
359: return;
360: }
361:
362: checkStartLimit(start, limit);
363:
364: updateDamagedRange(start, limit, srcLimit - srcStart);
365:
366: fCharBuffer.replace(start, limit, text, srcStart, srcLimit);
367: fStyleBuffer.replace(start, limit, text, srcStart, srcLimit);
368: fParagraphBuffer.replace(start, limit, text, srcStart,
369: srcLimit, fDamagedRange);
370: fTimeStamp += 1;
371: }
372:
373: /**
374: * Replace the characters and styles in the range [<code>start</code>, <code>limit</code>) with the characters
375: * and styles in <code>srcText</code>. <code>srcText</code> is not
376: * modified.
377: * @param start the offset at which the replace operation begins
378: * @param limit the offset at which the replace operation ends. The character and style at
379: * <code>limit</code> is not modified.
380: * @param text the source for the new characters and styles
381: */
382: public void replace(int start, int limit, MConstText text) {
383:
384: replace(start, limit, text, 0, text.length());
385: }
386:
387: /**
388: * Replace the characters in the range [<code>start</code>, <code>limit</code>) with the characters
389: * in <code>srcChars</code> in the range [<code>srcStart</code>, <code>srcLimit</code>). New characters take on the style
390: * <code>charsStyle</code>.
391: * <code>srcChars</code> is not modified.
392: * @param start the offset at which the replace operation begins
393: * @param limit the offset at which the replace operation ends. The character at
394: * <code>limit</code> is not modified.
395: * @param srcChars the source for the new characters
396: * @param srcStart the offset into <code>srcChars</code> where new characters will be obtained
397: * @param srcLimit the offset into <code>srcChars</code> where the new characters end
398: * @param charsStyle the style of the new characters
399: */
400: public void replace(int start, int limit, char[] srcChars,
401: int srcStart, int srcLimit, AttributeMap charsStyle) {
402: checkStartLimit(start, limit);
403:
404: if (start == limit && srcStart == srcLimit) {
405: return;
406: }
407:
408: updateDamagedRange(start, limit, srcLimit - srcStart);
409:
410: fCharBuffer.replace(start, limit, srcChars, srcStart, srcLimit);
411:
412: replaceCharStylesWith(start, limit, start
413: + (srcLimit - srcStart), charsStyle);
414:
415: fParagraphBuffer.deleteText(start, limit, fDamagedRange);
416: fParagraphBuffer
417: .insertText(start, srcChars, srcStart, srcLimit);
418:
419: fTimeStamp += 1;
420: }
421:
422: private void replaceCharStylesWith(int start, int oldLimit,
423: int newLimit, AttributeMap style) {
424:
425: if (start < oldLimit) {
426: fStyleBuffer.deleteText(start, oldLimit);
427: }
428: if (start < newLimit) {
429: if (forceModifier == null) {
430: forceModifier = new ForceModifier();
431: }
432: forceModifier.setStyle(style);
433: fStyleBuffer.insertText(start, newLimit);
434: fStyleBuffer.modifyStyles(start, newLimit, forceModifier,
435: null);
436: }
437: }
438:
439: /**
440: * Replace the characters in the range [<code>start</code>, <code>limit</code>) with the character <code>srcChar</code>.
441: * The new character takes on the style <code>charStyle</code>
442: * @param start the offset at which the replace operation begins
443: * @param limit the offset at which the replace operation ends. The character at
444: * <code>limit</code> is not modified.
445: * @param srcChar the new character
446: * @param charStyle the style of the new character
447: */
448: public void replace(int start, int limit, char srcChar,
449: AttributeMap charStyle) {
450: checkStartLimit(start, limit);
451:
452: updateDamagedRange(start, limit, 1);
453:
454: fCharBuffer.replace(start, limit, srcChar);
455:
456: replaceCharStylesWith(start, limit, start + 1, charStyle);
457:
458: if (start < limit) {
459: fParagraphBuffer.deleteText(start, limit, fDamagedRange);
460: }
461:
462: fParagraphBuffer.insertText(start, srcChar);
463:
464: fTimeStamp += 1;
465: }
466:
467: /**
468: * Replace the entire contents of this MText (both characters and styles) with
469: * the contents of <code>srcText</code>.
470: * @param srcText the source for the new characters and styles
471: */
472: public void replaceAll(MConstText srcText) {
473: replace(0, length(), srcText, 0, srcText.length());
474: }
475:
476: /**
477: * Insert the contents of <code>srcText</code> (both characters and styles) into this
478: * MText at the position specified by <code>pos</code>.
479: * @param pos The character offset where the new text is to be inserted.
480: * @param srcText The text to insert.
481: */
482: public void insert(int pos, MConstText srcText) {
483: replace(pos, pos, srcText, 0, srcText.length());
484: }
485:
486: /**
487: * Append the contents of <code>srcText</code> (both characters and styles) to the
488: * end of this MText.
489: * @param srcText The text to append.
490: */
491: public void append(MConstText srcText) {
492: replace(length(), length(), srcText, 0, srcText.length());
493: }
494:
495: /**
496: * Delete the specified range of characters (and styles).
497: * @param start Offset of the first character to delete.
498: * @param limit Offset of the first character after the range to delete.
499: */
500: public void remove(int start, int limit) {
501: replace(start, limit, (char[]) null, 0, 0,
502: AttributeMap.EMPTY_ATTRIBUTE_MAP);
503: }
504:
505: /**
506: * Delete all characters and styles. Always increments time stamp.
507: */
508: public void remove() {
509: // rather than going through replace(), just reinitialize the StyledText,
510: // letting the old data structures fall on the floor
511: fCharBuffer = new CharBuffer();
512: fStyleBuffer = new StyleBuffer(this ,
513: AttributeMap.EMPTY_ATTRIBUTE_MAP);
514: fParagraphBuffer = new ParagraphBuffer(fCharBuffer);
515: fTimeStamp += 1;
516: fDamagedRange[0] = fDamagedRange[1] = 0;
517: }
518:
519: //--------------------------------------------------------
520: // storage management
521: //--------------------------------------------------------
522:
523: /**
524: * Minimize the amount of memory used by the MText object.
525: */
526: public void compress() {
527:
528: fCharBuffer.compress();
529: fStyleBuffer.compress();
530: fParagraphBuffer.compress();
531: }
532:
533: //--------------------------------------------------------
534: // style modification
535: //--------------------------------------------------------
536:
537: /**
538: * Set the style of all characters in the MText object to
539: * <code>AttributeMap.EMPTY_ATTRIBUTE_MAP</code>.
540: */
541: public void removeCharacterStyles() {
542:
543: fStyleBuffer = new StyleBuffer(this ,
544: AttributeMap.EMPTY_ATTRIBUTE_MAP);
545: fTimeStamp += 1;
546: fDamagedRange[0] = 0;
547: fDamagedRange[1] = length();
548: }
549:
550: /**
551: * Invoke the given modifier on all character styles from start to limit.
552: * @param modifier the modifier to apply to the range.
553: * @param start the start of the range of text to modify.
554: * @param limit the limit of the range of text to modify.
555: */
556: public void modifyCharacterStyles(int start, int limit,
557: StyleModifier modifier) {
558:
559: checkStartLimit(start, limit);
560: boolean modified = fStyleBuffer.modifyStyles(start, limit,
561: modifier, fDamagedRange);
562: if (modified) {
563: fTimeStamp += 1;
564: }
565: }
566:
567: /**
568: * Invoke the given modifier on all paragraph styles in paragraphs
569: * containing characters in the range [start, limit).
570: * @param modifier the modifier to apply to the range.
571: * @param start the start of the range of text to modify.
572: * @param limit the limit of the range of text to modify.
573: */
574: public void modifyParagraphStyles(int start, int limit,
575: StyleModifier modifier) {
576:
577: checkStartLimit(start, limit);
578: boolean modified = fParagraphBuffer.modifyParagraphStyles(
579: start, limit, modifier, fDamagedRange);
580: if (modified) {
581: fTimeStamp += 1;
582: }
583: }
584:
585: /**
586: * Reset the damaged range to an empty interval, and begin accumulating the damaged
587: * range. The damaged range includes every index where a character, character style,
588: * or paragraph style has changed.
589: * @see #damagedRangeStart
590: * @see #damagedRangeLimit
591: */
592: public void resetDamagedRange() {
593:
594: fDamagedRange[0] = Integer.MAX_VALUE;
595: fDamagedRange[1] = Integer.MIN_VALUE;
596: }
597:
598: /**
599: * Return the start of the damaged range.
600: * If the start is
601: * <code>Integer.MAX_VALUE</code> and the limit is
602: * <code>Integer.MIN_VALUE</code>, then the damaged range
603: * is empty.
604: * @return the start of the damaged range
605: * @see #damagedRangeLimit
606: * @see #resetDamagedRange
607: */
608: public int damagedRangeStart() {
609:
610: return fDamagedRange[0];
611: }
612:
613: /**
614: * Return the limit of the damaged range.
615: * If the start is
616: * <code>Integer.MAX_VALUE</code> and the limit is
617: * <code>Integer.MIN_VALUE</code>, then the damaged range
618: * is empty.
619: * @return the limit of the damaged range
620: * @see #damagedRangeStart
621: * @see #resetDamagedRange
622: */
623: public int damagedRangeLimit() {
624:
625: return fDamagedRange[1];
626: }
627:
628: public String toString() {
629: String result = "";
630: for (int i = 0; i < length(); i++) {
631: result += at(i);
632: }
633: return result;
634: }
635:
636: //======================================================
637: // IMPLEMENTATION
638: //======================================================
639:
640: /* check a range to see if it is well formed and within the bounds of the text */
641: private void checkStartLimit(int start, int limit) {
642: if (start > limit) {
643: //System.out.println("Start is less than limit. start:"+start+"; limit:"+limit);
644: throw new IllegalArgumentException(
645: "Start is greater than limit. start:" + start
646: + "; limit:" + limit);
647: }
648:
649: if (start < 0) {
650: //System.out.println("Start is negative. start:"+start);
651: throw new IllegalArgumentException(
652: "Start is negative. start:" + start);
653: }
654:
655: if (limit > length()) {
656: //System.out.println("Limit is greater than length. limit:"+limit);
657: throw new IllegalArgumentException(
658: "Limit is greater than length. limit:" + limit);
659: }
660: }
661:
662: private static final boolean LESS_THAN_LENGTH = false;
663: private static final boolean NOT_GREATER_THAN_LENGTH = true;
664:
665: private void checkPos(int pos, boolean endAllowed) {
666:
667: int lastValidPos = length();
668: if (endAllowed == LESS_THAN_LENGTH) {
669: --lastValidPos;
670: }
671:
672: if (pos < 0 || pos > lastValidPos) {
673: throw new IllegalArgumentException(
674: "Position is out of range.");
675: }
676: }
677: }
|