1. /*
  2. * @(#)GapContent.java 1.21 01/12/03
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.text;
  8. import java.util.Vector;
  9. import java.io.IOException;
  10. import java.io.ObjectInputStream;
  11. import java.io.Serializable;
  12. import javax.swing.undo.AbstractUndoableEdit;
  13. import javax.swing.undo.CannotRedoException;
  14. import javax.swing.undo.CannotUndoException;
  15. import javax.swing.undo.UndoableEdit;
  16. import javax.swing.SwingUtilities;
  17. import java.lang.ref.WeakReference;
  18. import java.lang.ref.ReferenceQueue;
  19. /**
  20. * An implementation of the AbstractDocument.Content interface
  21. * implemented using a gapped buffer similar to that used by emacs.
  22. * The underlying storage is a array of unicode characters with
  23. * a gap somewhere. The gap is moved to the location of changes
  24. * to take advantage of common behavior where most changes are
  25. * in the same location. Changes that occur at a gap boundary are
  26. * generally cheap and moving the gap is generally cheaper than
  27. * moving the array contents directly to accomodate the change.
  28. * <p>
  29. * The positions tracking change are also generally cheap to
  30. * maintain. The Position implementations (marks) store the array
  31. * index and can easily calculate the sequential position from
  32. * the current gap location. Changes only require update to the
  33. * the marks between the old and new gap boundaries when the gap
  34. * is moved, so generally updating the marks is pretty cheap.
  35. * The marks are stored sorted so they can be located quickly
  36. * with a binary search. This increases the cost of adding a
  37. * mark, and decreases the cost of keeping the mark updated.
  38. *
  39. * @author Timothy Prinzing
  40. * @version 1.21 12/03/01
  41. */
  42. public class GapContent extends GapVector implements AbstractDocument.Content, Serializable {
  43. /**
  44. * Creates a new GapContent object. Initial size defaults to 10.
  45. */
  46. public GapContent() {
  47. this(10);
  48. }
  49. /**
  50. * Creates a new GapContent object, with the initial
  51. * size specified. The initial size will not be allowed
  52. * to go below 2, to give room for the implied break and
  53. * the gap.
  54. *
  55. * @param initialLength the initial size
  56. */
  57. public GapContent(int initialLength) {
  58. super(Math.max(initialLength,2));
  59. char[] implied = new char[1];
  60. implied[0] = '\n';
  61. replace(0, 0, implied, implied.length);
  62. marks = new MarkVector();
  63. search = new MarkData(0);
  64. queue = new ReferenceQueue();
  65. }
  66. /**
  67. * Allocate an array to store items of the type
  68. * appropriate (which is determined by the subclass).
  69. */
  70. protected Object allocateArray(int len) {
  71. return new char[len];
  72. }
  73. /**
  74. * Get the length of the allocated array.
  75. */
  76. protected int getArrayLength() {
  77. char[] carray = (char[]) getArray();
  78. return carray.length;
  79. }
  80. // --- AbstractDocument.Content methods -------------------------
  81. /**
  82. * Returns the length of the content.
  83. *
  84. * @return the length >= 1
  85. * @see AbstractDocument.Content#length
  86. */
  87. public int length() {
  88. int len = getArrayLength() - (getGapEnd() - getGapStart());
  89. return len;
  90. }
  91. /**
  92. * Inserts a string into the content.
  93. *
  94. * @param where the starting position >= 0, < length()
  95. * @param str the non-null string to insert
  96. * @return an UndoableEdit object for undoing
  97. * @exception BadLocationException if the specified position is invalid
  98. * @see AbstractDocument.Content#insertString
  99. */
  100. public UndoableEdit insertString(int where, String str) throws BadLocationException {
  101. if (where > length() || where < 0) {
  102. throw new BadLocationException("Invalid insert", length());
  103. }
  104. char[] chars = str.toCharArray();
  105. replace(where, 0, chars, chars.length);
  106. return new InsertUndo(where, str.length());
  107. }
  108. /**
  109. * Removes part of the content.
  110. *
  111. * @param where the starting position >= 0, where + nitems < length()
  112. * @param nitems the number of characters to remove >= 0
  113. * @return an UndoableEdit object for undoing
  114. * @exception BadLocationException if the specified position is invalid
  115. * @see AbstractDocument.Content#remove
  116. */
  117. public UndoableEdit remove(int where, int nitems) throws BadLocationException {
  118. if (where + nitems >= length()) {
  119. throw new BadLocationException("Invalid remove", length() + 1);
  120. }
  121. String removedString = getString(where, nitems);
  122. UndoableEdit edit = new RemoveUndo(where, removedString);
  123. replace(where, nitems, empty, 0);
  124. return edit;
  125. }
  126. /**
  127. * Retrieves a portion of the content.
  128. *
  129. * @param where the starting position >= 0
  130. * @param len the length to retrieve >= 0
  131. * @return a string representing the content
  132. * @exception BadLocationException if the specified position is invalid
  133. * @see AbstractDocument.Content#getString
  134. */
  135. public String getString(int where, int len) throws BadLocationException {
  136. Segment s = new Segment();
  137. getChars(where, len, s);
  138. return new String(s.array, s.offset, s.count);
  139. }
  140. /**
  141. * Retrieves a portion of the content. If the desired content spans
  142. * the gap, we copy the content. If the desired content does not
  143. * span the gap, the actual store is returned to avoid the copy since
  144. * it is contiguous.
  145. *
  146. * @param where the starting position >= 0, where + len <= length()
  147. * @param len the number of characters to retrieve >= 0
  148. * @param chars the Segment object to return the characters in
  149. * @exception BadLocationException if the specified position is invalid
  150. * @see AbstractDocument.Content#getChars
  151. */
  152. public void getChars(int where, int len, Segment chars) throws BadLocationException {
  153. int end = where + len;
  154. if (where < 0 || end < 0) {
  155. throw new BadLocationException("Invalid location", -1);
  156. }
  157. if (end > length() || where > length()) {
  158. throw new BadLocationException("Invalid location", length() + 1);
  159. }
  160. int g0 = getGapStart();
  161. int g1 = getGapEnd();
  162. char[] array = (char[]) getArray();
  163. if ((where + len) <= g0) {
  164. // below gap
  165. chars.array = array;
  166. chars.offset = where;
  167. } else if (where >= g0) {
  168. // above gap
  169. chars.array = array;
  170. chars.offset = g1 + where - g0;
  171. } else {
  172. // spans the gap
  173. int before = g0 - where;
  174. if (chars.isPartialReturn()) {
  175. // partial return allowed, return amount before the gap
  176. chars.array = array;
  177. chars.offset = where;
  178. chars.count = before;
  179. return;
  180. }
  181. // partial return not allowed, must copy
  182. chars.array = new char[len];
  183. chars.offset = 0;
  184. System.arraycopy(array, where, chars.array, 0, before);
  185. System.arraycopy(array, g1, chars.array, before, len - before);
  186. }
  187. chars.count = len;
  188. }
  189. /**
  190. * Creates a position within the content that will
  191. * track change as the content is mutated.
  192. *
  193. * @param offset the offset to track >= 0
  194. * @return the position
  195. * @exception BadLocationException if the specified position is invalid
  196. */
  197. public Position createPosition(int offset) throws BadLocationException {
  198. while ( queue.poll() != null ) {
  199. unusedMarks++;
  200. }
  201. if (unusedMarks > Math.max(5, (marks.size() / 10))) {
  202. removeUnusedMarks();
  203. }
  204. int g0 = getGapStart();
  205. int g1 = getGapEnd();
  206. int index = (offset < g0) ? offset : offset + (g1 - g0);
  207. search.index = index;
  208. int sortIndex = findSortIndex(search);
  209. MarkData m;
  210. StickyPosition position;
  211. if (sortIndex < marks.size()
  212. && (m = marks.elementAt(sortIndex)).index == index
  213. && (position = m.getPosition()) != null) {
  214. //position references the correct StickyPostition
  215. } else {
  216. position = new StickyPosition();
  217. m = new MarkData(index,position,queue);
  218. position.setMark(m);
  219. marks.insertElementAt(m, sortIndex);
  220. }
  221. return position;
  222. }
  223. /**
  224. * Holds the data for a mark... separately from
  225. * the real mark so that the real mark (Position
  226. * that the caller of createPosition holds) can be
  227. * collected if there are no more references to
  228. * it. The update table holds only a reference
  229. * to this data.
  230. */
  231. final class MarkData extends WeakReference {
  232. MarkData(int index) {
  233. super(null);
  234. this.index = index;
  235. }
  236. MarkData(int index, StickyPosition position, ReferenceQueue queue) {
  237. super(position, queue);
  238. this.index = index;
  239. }
  240. /**
  241. * Fetch the location in the contiguous sequence
  242. * being modeled. The index in the gap array
  243. * is held by the mark, so it is adjusted according
  244. * to it's relationship to the gap.
  245. */
  246. public final int getOffset() {
  247. int g0 = getGapStart();
  248. int g1 = getGapEnd();
  249. int offs = (index < g0) ? index : index - (g1 - g0);
  250. return Math.max(offs, 0);
  251. }
  252. StickyPosition getPosition() {
  253. return (StickyPosition)get();
  254. }
  255. int index;
  256. }
  257. final class StickyPosition implements Position {
  258. StickyPosition() {
  259. }
  260. void setMark(MarkData mark) {
  261. this.mark = mark;
  262. }
  263. public final int getOffset() {
  264. return mark.getOffset();
  265. }
  266. public String toString() {
  267. return Integer.toString(getOffset());
  268. }
  269. MarkData mark;
  270. }
  271. // --- variables --------------------------------------
  272. private static final char[] empty = new char[0];
  273. private transient MarkVector marks;
  274. /**
  275. * Record used for searching for the place to
  276. * start updating mark indexs when the gap
  277. * boundaries are moved.
  278. */
  279. private transient MarkData search;
  280. /**
  281. * The number of unused mark entries
  282. */
  283. private transient int unusedMarks = 0;
  284. private transient ReferenceQueue queue;
  285. final static int GROWTH_SIZE = 1024 * 512;
  286. // --- gap management -------------------------------
  287. /**
  288. * Make the gap bigger, moving any necessary data and updating
  289. * the appropriate marks
  290. */
  291. protected void shiftEnd(int newSize) {
  292. int oldGapEnd = getGapEnd();
  293. super.shiftEnd(newSize);
  294. // Adjust marks.
  295. int dg = getGapEnd() - oldGapEnd;
  296. int adjustIndex = findMarkAdjustIndex(oldGapEnd);
  297. int n = marks.size();
  298. for (int i = adjustIndex; i < n; i++) {
  299. MarkData mark = marks.elementAt(i);
  300. mark.index += dg;
  301. }
  302. }
  303. /**
  304. * Overridden to make growth policy less agressive for large
  305. * text amount.
  306. */
  307. int getNewArraySize(int reqSize) {
  308. if (reqSize < GROWTH_SIZE) {
  309. return super.getNewArraySize(reqSize);
  310. } else {
  311. return reqSize + GROWTH_SIZE;
  312. }
  313. }
  314. /**
  315. * Move the start of the gap to a new location,
  316. * without changing the size of the gap. This
  317. * moves the data in the array and updates the
  318. * marks accordingly.
  319. */
  320. protected void shiftGap(int newGapStart) {
  321. int oldGapStart = getGapStart();
  322. int dg = newGapStart - oldGapStart;
  323. int oldGapEnd = getGapEnd();
  324. int newGapEnd = oldGapEnd + dg;
  325. int gapSize = oldGapEnd - oldGapStart;
  326. // shift gap in the character array
  327. super.shiftGap(newGapStart);
  328. // update the marks
  329. if (dg > 0) {
  330. // Move gap up, move data and marks down.
  331. int adjustIndex = findMarkAdjustIndex(oldGapStart);
  332. int n = marks.size();
  333. for (int i = adjustIndex; i < n; i++) {
  334. MarkData mark = marks.elementAt(i);
  335. if (mark.index >= newGapEnd) {
  336. break;
  337. }
  338. mark.index -= gapSize;
  339. }
  340. } else if (dg < 0) {
  341. // Move gap down, move data and marks up.
  342. int adjustIndex = findMarkAdjustIndex(newGapStart);
  343. int n = marks.size();
  344. for (int i = adjustIndex; i < n; i++) {
  345. MarkData mark = marks.elementAt(i);
  346. if (mark.index >= oldGapEnd) {
  347. break;
  348. }
  349. mark.index += gapSize;
  350. }
  351. }
  352. resetMarksAtZero();
  353. }
  354. /**
  355. * Resets all the marks that have an offset of 0 to have an index of
  356. * zero as well.
  357. */
  358. protected void resetMarksAtZero() {
  359. if (marks != null && getGapStart() == 0) {
  360. int g1 = getGapEnd();
  361. for (int counter = 0, maxCounter = marks.size();
  362. counter < maxCounter; counter++) {
  363. MarkData mark = marks.elementAt(counter);
  364. if (mark.index <= g1) {
  365. mark.index = 0;
  366. }
  367. else {
  368. break;
  369. }
  370. }
  371. }
  372. }
  373. /**
  374. * Adjust the gap end downward. This doesn't move
  375. * any data, but it does update any marks affected
  376. * by the boundary change. All marks from the old
  377. * gap start down to the new gap start are squeezed
  378. * to the end of the gap (their location has been
  379. * removed).
  380. */
  381. protected void shiftGapStartDown(int newGapStart) {
  382. // Push aside all marks from oldGapStart down to newGapStart.
  383. int adjustIndex = findMarkAdjustIndex(newGapStart);
  384. int n = marks.size();
  385. int g0 = getGapStart();
  386. int g1 = getGapEnd();
  387. for (int i = adjustIndex; i < n; i++) {
  388. MarkData mark = marks.elementAt(i);
  389. if (mark.index > g0) {
  390. // no more marks to adjust
  391. break;
  392. }
  393. mark.index = g1;
  394. }
  395. // shift the gap in the character array
  396. super.shiftGapStartDown(newGapStart);
  397. resetMarksAtZero();
  398. }
  399. /**
  400. * Adjust the gap end upward. This doesn't move
  401. * any data, but it does update any marks affected
  402. * by the boundary change. All marks from the old
  403. * gap end up to the new gap end are squeezed
  404. * to the end of the gap (their location has been
  405. * removed).
  406. */
  407. protected void shiftGapEndUp(int newGapEnd) {
  408. int adjustIndex = findMarkAdjustIndex(getGapEnd());
  409. int n = marks.size();
  410. for (int i = adjustIndex; i < n; i++) {
  411. MarkData mark = marks.elementAt(i);
  412. if (mark.index >= newGapEnd) {
  413. break;
  414. }
  415. mark.index = newGapEnd;
  416. }
  417. // shift the gap in the character array
  418. super.shiftGapEndUp(newGapEnd);
  419. resetMarksAtZero();
  420. }
  421. /**
  422. * Compares two marks.
  423. *
  424. * @param o1 the first object
  425. * @param o2 the second object
  426. * @return < 0 if o1 < o2, 0 if the same, > 0 if o1 > o2
  427. */
  428. final int compare(MarkData o1, MarkData o2) {
  429. if (o1.index < o2.index) {
  430. return -1;
  431. } else if (o1.index > o2.index) {
  432. return 1;
  433. } else {
  434. return 0;
  435. }
  436. }
  437. /**
  438. * Finds the index to start mark adjustments given
  439. * some search index.
  440. */
  441. final int findMarkAdjustIndex(int searchIndex) {
  442. search.index = Math.max(searchIndex, 1);
  443. int index = findSortIndex(search);
  444. // return the first in the series
  445. // (ie. there may be duplicates).
  446. for (int i = index - 1; i >= 0; i--) {
  447. MarkData d = marks.elementAt(i);
  448. if (d.index != search.index) {
  449. break;
  450. }
  451. index -= 1;
  452. }
  453. return index;
  454. }
  455. /**
  456. * Finds the index of where to insert a new mark.
  457. *
  458. * @param o the mark to insert
  459. * @return the index
  460. */
  461. final int findSortIndex(MarkData o) {
  462. int lower = 0;
  463. int upper = marks.size() - 1;
  464. int mid = 0;
  465. if (upper == -1) {
  466. return 0;
  467. }
  468. int cmp = 0;
  469. MarkData last = marks.elementAt(upper);
  470. cmp = compare(o, last);
  471. if (cmp > 0)
  472. return upper + 1;
  473. while (lower <= upper) {
  474. mid = lower + ((upper - lower) / 2);
  475. MarkData entry = marks.elementAt(mid);
  476. cmp = compare(o, entry);
  477. if (cmp == 0) {
  478. // found a match
  479. return mid;
  480. } else if (cmp < 0) {
  481. upper = mid - 1;
  482. } else {
  483. lower = mid + 1;
  484. }
  485. }
  486. // didn't find it, but we indicate the index of where it would belong.
  487. return (cmp < 0) ? mid : mid + 1;
  488. }
  489. /**
  490. * Remove all unused marks out of the sorted collection
  491. * of marks.
  492. */
  493. final void removeUnusedMarks() {
  494. int n = marks.size();
  495. MarkVector cleaned = new MarkVector(n);
  496. for (int i = 0; i < n; i++) {
  497. MarkData mark = marks.elementAt(i);
  498. if (mark.get() != null) {
  499. cleaned.addElement(mark);
  500. }
  501. }
  502. marks = cleaned;
  503. unusedMarks = 0;
  504. }
  505. static class MarkVector extends GapVector {
  506. MarkVector() {
  507. super();
  508. }
  509. MarkVector(int size) {
  510. super(size);
  511. }
  512. /**
  513. * Allocate an array to store items of the type
  514. * appropriate (which is determined by the subclass).
  515. */
  516. protected Object allocateArray(int len) {
  517. return new MarkData[len];
  518. }
  519. /**
  520. * Get the length of the allocated array
  521. */
  522. protected int getArrayLength() {
  523. MarkData[] marks = (MarkData[]) getArray();
  524. return marks.length;
  525. }
  526. /**
  527. * Returns the number of marks currently held
  528. */
  529. public int size() {
  530. int len = getArrayLength() - (getGapEnd() - getGapStart());
  531. return len;
  532. }
  533. /**
  534. * Inserts a mark into the vector
  535. */
  536. public void insertElementAt(MarkData m, int index) {
  537. oneMark[0] = m;
  538. replace(index, 0, oneMark, 1);
  539. }
  540. /**
  541. * Add a mark to the end
  542. */
  543. public void addElement(MarkData m) {
  544. insertElementAt(m, size());
  545. }
  546. /**
  547. * Fetches the mark at the given index
  548. */
  549. public MarkData elementAt(int index) {
  550. int g0 = getGapStart();
  551. int g1 = getGapEnd();
  552. MarkData[] array = (MarkData[]) getArray();
  553. if (index < g0) {
  554. // below gap
  555. return array[index];
  556. } else {
  557. // above gap
  558. index += g1 - g0;
  559. return array[index];
  560. }
  561. }
  562. /**
  563. * Replaces the elements in the specified range with the passed
  564. * in objects. This will NOT adjust the gap. The passed in indices
  565. * do not account for the gap, they are the same as would be used
  566. * int <code>elementAt</code>.
  567. */
  568. protected void replaceRange(int start, int end, Object[] marks) {
  569. int g0 = getGapStart();
  570. int g1 = getGapEnd();
  571. int index = start;
  572. int newIndex = 0;
  573. Object[] array = (Object[]) getArray();
  574. if (start >= g0) {
  575. // Completely passed gap
  576. index += (g1 - g0);
  577. end += (g1 - g0);
  578. }
  579. else if (end >= g0) {
  580. // straddles gap
  581. end += (g1 - g0);
  582. while (index < g0) {
  583. array[index++] = marks[newIndex++];
  584. }
  585. index = g1;
  586. }
  587. else {
  588. // below gap
  589. while (index < end) {
  590. array[index++] = marks[newIndex++];
  591. }
  592. }
  593. while (index < end) {
  594. array[index++] = marks[newIndex++];
  595. }
  596. }
  597. MarkData[] oneMark = new MarkData[1];
  598. }
  599. // --- serialization -------------------------------------
  600. private void readObject(ObjectInputStream s)
  601. throws ClassNotFoundException, IOException {
  602. s.defaultReadObject();
  603. marks = new MarkVector();
  604. search = new MarkData(0);
  605. queue = new ReferenceQueue();
  606. }
  607. // --- undo support --------------------------------------
  608. /**
  609. * Returns a Vector containing instances of UndoPosRef for the
  610. * Positions in the range
  611. * <code>offset</code> to <code>offset</code> + <code>length</code>.
  612. * If <code>v</code> is not null the matching Positions are placed in
  613. * there. The vector with the resulting Positions are returned.
  614. *
  615. * @param v the Vector to use, with a new one created on null
  616. * @param offset the starting offset >= 0
  617. * @param length the length >= 0
  618. * @return the set of instances
  619. */
  620. protected Vector getPositionsInRange(Vector v, int offset, int length) {
  621. int endOffset = offset + length;
  622. int startIndex;
  623. int endIndex;
  624. int g0 = getGapStart();
  625. int g1 = getGapEnd();
  626. // Find the index of the marks.
  627. if (offset < g0) {
  628. if (offset == 0) {
  629. // findMarkAdjustIndex start at 1!
  630. startIndex = 0;
  631. }
  632. else {
  633. startIndex = findMarkAdjustIndex(offset);
  634. }
  635. if (endOffset >= g0) {
  636. endIndex = findMarkAdjustIndex(endOffset + (g1 - g0) + 1);
  637. }
  638. else {
  639. endIndex = findMarkAdjustIndex(endOffset + 1);
  640. }
  641. }
  642. else {
  643. startIndex = findMarkAdjustIndex(offset + (g1 - g0));
  644. endIndex = findMarkAdjustIndex(endOffset + (g1 - g0) + 1);
  645. }
  646. Vector placeIn = (v == null) ? new Vector(Math.max(1, endIndex -
  647. startIndex)) : v;
  648. for (int counter = startIndex; counter < endIndex; counter++) {
  649. placeIn.addElement(new UndoPosRef(marks.elementAt(counter)));
  650. }
  651. return placeIn;
  652. }
  653. /**
  654. * Resets the location for all the UndoPosRef instances
  655. * in <code>positions</code>.
  656. * <p>
  657. * This is meant for internal usage, and is generally not of interest
  658. * to subclasses.
  659. *
  660. * @param positions the UndoPosRef instances to reset
  661. */
  662. protected void updateUndoPositions(Vector positions, int offset,
  663. int length) {
  664. // Find the indexs of the end points.
  665. int endOffset = offset + length;
  666. int g1 = getGapEnd();
  667. int startIndex;
  668. int endIndex = findMarkAdjustIndex(g1 + 1);
  669. if (offset != 0) {
  670. startIndex = findMarkAdjustIndex(g1);
  671. }
  672. else {
  673. startIndex = 0;
  674. }
  675. // Reset the location of the refenences.
  676. for(int counter = positions.size() - 1; counter >= 0; counter--) {
  677. UndoPosRef ref = (UndoPosRef)positions.elementAt(counter);
  678. ref.resetLocation(endOffset, g1);
  679. }
  680. // We have to resort the marks in the range startIndex to endIndex.
  681. // We can take advantage of the fact that it will be in
  682. // increasing order, accept there will be a bunch of MarkData's with
  683. // the index g1 (or 0 if offset == 0) interspersed throughout.
  684. if (startIndex < endIndex) {
  685. Object[] sorted = new Object[endIndex - startIndex];
  686. int addIndex = 0;
  687. int counter;
  688. if (offset == 0) {
  689. // If the offset is 0, the positions won't have incremented,
  690. // have to do the reverse thing.
  691. // Find the elements in startIndex whose index is 0
  692. for (counter = startIndex; counter < endIndex; counter++) {
  693. MarkData mark = marks.elementAt(counter);
  694. if (mark.index == 0) {
  695. sorted[addIndex++] = mark;
  696. }
  697. }
  698. for (counter = startIndex; counter < endIndex; counter++) {
  699. MarkData mark = marks.elementAt(counter);
  700. if (mark.index != 0) {
  701. sorted[addIndex++] = mark;
  702. }
  703. }
  704. }
  705. else {
  706. for (counter = startIndex; counter < endIndex; counter++) {
  707. MarkData mark = marks.elementAt(counter);
  708. if (mark.index != g1) {
  709. sorted[addIndex++] = mark;
  710. }
  711. }
  712. for (counter = startIndex; counter < endIndex; counter++) {
  713. MarkData mark = marks.elementAt(counter);
  714. if (mark.index == g1) {
  715. sorted[addIndex++] = mark;
  716. }
  717. }
  718. }
  719. // And replace
  720. marks.replaceRange(startIndex, endIndex, sorted);
  721. }
  722. }
  723. /**
  724. * Used to hold a reference to a Mark that is being reset as the
  725. * result of removing from the content.
  726. */
  727. final class UndoPosRef {
  728. UndoPosRef(MarkData rec) {
  729. this.rec = rec;
  730. this.undoLocation = rec.getOffset();
  731. }
  732. /**
  733. * Resets the location of the Position to the offset when the
  734. * receiver was instantiated.
  735. *
  736. * @param endOffset end location of inserted string.
  737. * @param g1 resulting end of gap.
  738. */
  739. protected void resetLocation(int endOffset, int g1) {
  740. if (undoLocation != endOffset) {
  741. this.rec.index = undoLocation;
  742. }
  743. else {
  744. this.rec.index = g1;
  745. }
  746. }
  747. /** Previous Offset of rec. */
  748. protected int undoLocation;
  749. /** Mark to reset offset. */
  750. protected MarkData rec;
  751. } // End of GapContent.UndoPosRef
  752. /**
  753. * UnoableEdit created for inserts.
  754. */
  755. class InsertUndo extends AbstractUndoableEdit {
  756. protected InsertUndo(int offset, int length) {
  757. super();
  758. this.offset = offset;
  759. this.length = length;
  760. }
  761. public void undo() throws CannotUndoException {
  762. super.undo();
  763. try {
  764. // Get the Positions in the range being removed.
  765. posRefs = getPositionsInRange(null, offset, length);
  766. string = getString(offset, length);
  767. remove(offset, length);
  768. } catch (BadLocationException bl) {
  769. throw new CannotUndoException();
  770. }
  771. }
  772. public void redo() throws CannotRedoException {
  773. super.redo();
  774. try {
  775. insertString(offset, string);
  776. string = null;
  777. // Update the Positions that were in the range removed.
  778. if(posRefs != null) {
  779. updateUndoPositions(posRefs, offset, length);
  780. posRefs = null;
  781. }
  782. } catch (BadLocationException bl) {
  783. throw new CannotRedoException();
  784. }
  785. }
  786. /** Where string was inserted. */
  787. protected int offset;
  788. /** Length of string inserted. */
  789. protected int length;
  790. /** The string that was inserted. This will only be valid after an
  791. * undo. */
  792. protected String string;
  793. /** An array of instances of UndoPosRef for the Positions in the
  794. * range that was removed, valid after undo. */
  795. protected Vector posRefs;
  796. } // GapContent.InsertUndo
  797. /**
  798. * UndoableEdit created for removes.
  799. */
  800. class RemoveUndo extends AbstractUndoableEdit {
  801. protected RemoveUndo(int offset, String string) {
  802. super();
  803. this.offset = offset;
  804. this.string = string;
  805. this.length = string.length();
  806. posRefs = getPositionsInRange(null, offset, length);
  807. }
  808. public void undo() throws CannotUndoException {
  809. super.undo();
  810. try {
  811. insertString(offset, string);
  812. // Update the Positions that were in the range removed.
  813. if(posRefs != null) {
  814. updateUndoPositions(posRefs, offset, length);
  815. posRefs = null;
  816. }
  817. string = null;
  818. } catch (BadLocationException bl) {
  819. throw new CannotUndoException();
  820. }
  821. }
  822. public void redo() throws CannotRedoException {
  823. super.redo();
  824. try {
  825. string = getString(offset, length);
  826. // Get the Positions in the range being removed.
  827. posRefs = getPositionsInRange(null, offset, length);
  828. remove(offset, length);
  829. } catch (BadLocationException bl) {
  830. throw new CannotRedoException();
  831. }
  832. }
  833. /** Where the string was removed from. */
  834. protected int offset;
  835. /** Length of string removed. */
  836. protected int length;
  837. /** The string that was removed. This is valid when redo is valid. */
  838. protected String string;
  839. /** An array of instances of UndoPosRef for the Positions in the
  840. * range that was removed, valid before undo. */
  841. protected Vector posRefs;
  842. } // GapContent.RemoveUndo
  843. }