1. /*
  2. * @(#)StringContent.java 1.36 01/11/29
  3. *
  4. * Copyright 2002 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.Serializable;
  10. import javax.swing.undo.*;
  11. import javax.swing.SwingUtilities;
  12. /**
  13. * An implementation of the AbstractDocument.Content interface that is
  14. * a brute force implementation that is useful for relatively small
  15. * documents and/or debugging. It manages the character content
  16. * as a simple character array. It is also quite inefficient.
  17. * <p>
  18. * It is generally recommended that the gap buffer or piece table
  19. * implementations be used instead. This buffer does not scale up
  20. * to large sizes.
  21. * <p>
  22. * <strong>Warning:</strong>
  23. * Serialized objects of this class will not be compatible with
  24. * future Swing releases. The current serialization support is appropriate
  25. * for short term storage or RMI between applications running the same
  26. * version of Swing. A future release of Swing will provide support for
  27. * long term persistence.
  28. *
  29. * @author Timothy Prinzing
  30. * @version 1.36 11/29/01
  31. */
  32. public final class StringContent implements AbstractDocument.Content, Serializable {
  33. /**
  34. * Creates a new StringContent object. Initial size defaults to 10.
  35. */
  36. public StringContent() {
  37. this(10);
  38. }
  39. /**
  40. * Creates a new StringContent object, with the initial
  41. * size specified. If the length is < 1, a size of 1 is used.
  42. *
  43. * @param initialLength the initial size
  44. */
  45. public StringContent(int initialLength) {
  46. if (initialLength < 1) {
  47. initialLength = 1;
  48. }
  49. data = new char[initialLength];
  50. data[0] = '\n';
  51. count = 1;
  52. }
  53. /**
  54. * Returns the length of the content.
  55. *
  56. * @return the length >= 1
  57. * @see AbstractDocument.Content#length
  58. */
  59. public int length() {
  60. return count;
  61. }
  62. /**
  63. * Inserts a string into the content.
  64. *
  65. * @param where the starting position >= 0 && < length()
  66. * @param str the non-null string to insert
  67. * @return an UndoableEdit object for undoing
  68. * @exception BadLocationException if the specified position is invalid
  69. * @see AbstractDocument.Content#insertString
  70. */
  71. public UndoableEdit insertString(int where, String str) throws BadLocationException {
  72. if (where >= count) {
  73. throw new BadLocationException("Invalid location", count);
  74. }
  75. char[] chars = str.toCharArray();
  76. replace(where, 0, chars, 0, chars.length);
  77. if (marks != null) {
  78. updateMarksForInsert(where, str.length());
  79. }
  80. return new InsertUndo(where, str.length());
  81. }
  82. /**
  83. * Removes part of the content. where + nitems must be < length().
  84. *
  85. * @param where the starting position >= 0
  86. * @param nitems the number of characters to remove >= 0
  87. * @return an UndoableEdit object for undoing
  88. * @exception BadLocationException if the specified position is invalid
  89. * @see AbstractDocument.Content#remove
  90. */
  91. public UndoableEdit remove(int where, int nitems) throws BadLocationException {
  92. if (where + nitems >= count) {
  93. throw new BadLocationException("Invalid range", count);
  94. }
  95. String removedString = getString(where, nitems);
  96. UndoableEdit edit = new RemoveUndo(where, removedString);
  97. replace(where, nitems, empty, 0, 0);
  98. if (marks != null) {
  99. updateMarksForRemove(where, nitems);
  100. }
  101. return edit;
  102. }
  103. /**
  104. * Retrieves a portion of the content. where + len must be <= length().
  105. *
  106. * @param where the starting position >= 0
  107. * @param len the length to retrieve >= 0
  108. * @return a string representing the content; may be empty
  109. * @exception BadLocationException if the specified position is invalid
  110. * @see AbstractDocument.Content#getString
  111. */
  112. public String getString(int where, int len) throws BadLocationException {
  113. if (where + len > count) {
  114. throw new BadLocationException("Invalid range", count);
  115. }
  116. return new String(data, where, len);
  117. }
  118. /**
  119. * Retrieves a portion of the content. where + len must be <= length()
  120. *
  121. * @param where the starting position >= 0
  122. * @param len the number of characters to retrieve >= 0
  123. * @param chars the Segment object to return the characters in
  124. * @exception BadLocationException if the specified position is invalid
  125. * @see AbstractDocument.Content#getChars
  126. */
  127. public void getChars(int where, int len, Segment chars) throws BadLocationException {
  128. if (where + len > count) {
  129. throw new BadLocationException("Invalid location", count);
  130. }
  131. chars.array = data;
  132. chars.offset = where;
  133. chars.count = len;
  134. }
  135. /**
  136. * Creates a position within the content that will
  137. * track change as the content is mutated.
  138. *
  139. * @param offset the offset to create a position for >= 0
  140. * @return the position
  141. * @exception BadLocationException if the specified position is invalid
  142. */
  143. public Position createPosition(int offset) throws BadLocationException {
  144. // some small documents won't have any sticky positions
  145. // at all, so the buffer is created lazily.
  146. if (marks == null) {
  147. marks = new Vector();
  148. }
  149. return new StickyPosition(offset);
  150. }
  151. // --- local methods ---------------------------------------
  152. /**
  153. * Replaces some of the characters in the array
  154. * @param offset offset into the array to start the replace
  155. * @param length number of characters to remove
  156. * @param replArray replacement array
  157. * @param replOffset offset into the replacement array
  158. * @param replLength number of character to use from the
  159. * replacement array.
  160. */
  161. void replace(int offset, int length,
  162. char[] replArray, int replOffset, int replLength) {
  163. int delta = replLength - length;
  164. int src = offset + length;
  165. int nmove = count - src;
  166. int dest = src + delta;
  167. if ((count + delta) >= data.length) {
  168. // need to grow the array
  169. int newLength = Math.max(2*data.length, count + delta);
  170. char[] newData = new char[newLength];
  171. System.arraycopy(data, 0, newData, 0, offset);
  172. System.arraycopy(replArray, replOffset, newData, offset, replLength);
  173. System.arraycopy(data, src, newData, dest, nmove);
  174. data = newData;
  175. } else {
  176. // patch the existing array
  177. System.arraycopy(data, src, data, dest, nmove);
  178. System.arraycopy(replArray, replOffset, data, offset, replLength);
  179. }
  180. count = count + delta;
  181. }
  182. void resize(int ncount) {
  183. char[] ndata = new char[ncount];
  184. System.arraycopy(data, 0, ndata, 0, Math.min(ncount, count));
  185. data = ndata;
  186. }
  187. synchronized void updateMarksForInsert(int offset, int length) {
  188. if (offset == 0) {
  189. // zero is a special case where we update only
  190. // marks after it.
  191. offset = 1;
  192. }
  193. int n = marks.size();
  194. for (int i = 0; i < n; i++) {
  195. PosRec mark = (PosRec) marks.elementAt(i);
  196. if (mark.unused) {
  197. // this record is no longer used, get rid of it
  198. marks.removeElementAt(i);
  199. i -= 1;
  200. n -= 1;
  201. } else if (mark.offset >= offset) {
  202. mark.offset += length;
  203. }
  204. }
  205. }
  206. synchronized void updateMarksForRemove(int offset, int length) {
  207. int n = marks.size();
  208. for (int i = 0; i < n; i++) {
  209. PosRec mark = (PosRec) marks.elementAt(i);
  210. if (mark.unused) {
  211. // this record is no longer used, get rid of it
  212. marks.removeElementAt(i);
  213. i -= 1;
  214. n -= 1;
  215. } else if (mark.offset >= (offset + length)) {
  216. mark.offset -= length;
  217. } else if (mark.offset >= offset) {
  218. mark.offset = offset;
  219. }
  220. }
  221. }
  222. /**
  223. * Returns a Vector containing instances of UndoPosRef for the
  224. * Positions in the range
  225. * <code>offset</code> to <code>offset</code> + <code>length</code>.
  226. * If <code>v</code> is not null the matching Positions are placed in
  227. * there. The vector with the resulting Positions are returned.
  228. *
  229. * @param v the Vector to use, with a new one created on null
  230. * @param offset the starting offset >= 0
  231. * @param length the length >= 0
  232. * @return the set of instances
  233. */
  234. protected Vector getPositionsInRange(Vector v, int offset,
  235. int length) {
  236. int n = marks.size();
  237. int end = offset + length;
  238. Vector placeIn = (v == null) ? new Vector() : v;
  239. for (int i = 0; i < n; i++) {
  240. PosRec mark = (PosRec) marks.elementAt(i);
  241. if (mark.unused) {
  242. // this record is no longer used, get rid of it
  243. marks.removeElementAt(i);
  244. i -= 1;
  245. n -= 1;
  246. } else if(mark.offset >= offset && mark.offset <= end)
  247. placeIn.addElement(new UndoPosRef(mark));
  248. }
  249. return placeIn;
  250. }
  251. /**
  252. * Resets the location for all the UndoPosRef instances
  253. * in <code>positions</code>.
  254. *
  255. * @param positions the positions of the instances
  256. */
  257. protected void updateUndoPositions(Vector positions) {
  258. for(int counter = positions.size() - 1; counter >= 0; counter--) {
  259. UndoPosRef ref = (UndoPosRef)positions.elementAt(counter);
  260. // Check if the Position is still valid.
  261. if(ref.rec.unused) {
  262. positions.removeElementAt(counter);
  263. }
  264. else
  265. ref.resetLocation();
  266. }
  267. }
  268. private static final char[] empty = new char[0];
  269. private char[] data;
  270. private int count;
  271. transient Vector marks;
  272. /**
  273. * holds the data for a mark... seperately from
  274. * the real mark so that the real mark can be
  275. * collected if there are no more references to
  276. * it.... the update table holds only a reference
  277. * to this grungey thing.
  278. */
  279. final class PosRec {
  280. PosRec(int offset) {
  281. this.offset = offset;
  282. }
  283. int offset;
  284. boolean unused;
  285. }
  286. /**
  287. * This really wants to be a weak reference but
  288. * in 1.1 we don't have a 100% pure solution for
  289. * this... so this class trys to hack a solution
  290. * to causing the marks to be collected.
  291. */
  292. final class StickyPosition implements Position {
  293. StickyPosition(int offset) {
  294. rec = new PosRec(offset);
  295. marks.addElement(rec);
  296. }
  297. public int getOffset() {
  298. return rec.offset;
  299. }
  300. protected void finalize() throws Throwable {
  301. // schedule the record to be removed later
  302. // on another thread.
  303. rec.unused = true;
  304. }
  305. public String toString() {
  306. return Integer.toString(getOffset());
  307. }
  308. PosRec rec;
  309. }
  310. /**
  311. * Used to hold a reference to a Position that is being reset as the
  312. * result of removing from the content.
  313. */
  314. final class UndoPosRef {
  315. UndoPosRef(PosRec rec) {
  316. this.rec = rec;
  317. this.undoLocation = rec.offset;
  318. }
  319. /**
  320. * Resets the location of the Position to the offset when the
  321. * receiver was instantiated.
  322. */
  323. protected void resetLocation() {
  324. rec.offset = undoLocation;
  325. }
  326. /** Location to reset to when resetLocatino is invoked. */
  327. protected int undoLocation;
  328. /** Position to reset offset. */
  329. protected PosRec rec;
  330. }
  331. /**
  332. * UnoableEdit created for inserts.
  333. */
  334. class InsertUndo extends AbstractUndoableEdit {
  335. protected InsertUndo(int offset, int length) {
  336. super();
  337. this.offset = offset;
  338. this.length = length;
  339. }
  340. public void undo() throws CannotUndoException {
  341. super.undo();
  342. try {
  343. synchronized(StringContent.this) {
  344. // Get the Positions in the range being removed.
  345. if(marks != null)
  346. posRefs = getPositionsInRange(null, offset, length);
  347. string = getString(offset, length);
  348. remove(offset, length);
  349. }
  350. } catch (BadLocationException bl) {
  351. throw new CannotUndoException();
  352. }
  353. }
  354. public void redo() throws CannotRedoException {
  355. super.redo();
  356. try {
  357. synchronized(StringContent.this) {
  358. insertString(offset, string);
  359. string = null;
  360. // Update the Positions that were in the range removed.
  361. if(posRefs != null) {
  362. updateUndoPositions(posRefs);
  363. posRefs = null;
  364. }
  365. }
  366. } catch (BadLocationException bl) {
  367. throw new CannotRedoException();
  368. }
  369. }
  370. // Where the string goes.
  371. protected int offset;
  372. // Length of the string.
  373. protected int length;
  374. // The string that was inserted. To cut down on space needed this
  375. // will only be valid after an undo.
  376. protected String string;
  377. // An array of instances of UndoPosRef for the Positions in the
  378. // range that was removed, valid after undo.
  379. protected Vector posRefs;
  380. }
  381. /**
  382. * UndoableEdit created for removes.
  383. */
  384. class RemoveUndo extends AbstractUndoableEdit {
  385. protected RemoveUndo(int offset, String string) {
  386. super();
  387. this.offset = offset;
  388. this.string = string;
  389. this.length = string.length();
  390. if(marks != null)
  391. posRefs = getPositionsInRange(null, offset, length);
  392. }
  393. public void undo() throws CannotUndoException {
  394. super.undo();
  395. try {
  396. synchronized(StringContent.this) {
  397. insertString(offset, string);
  398. // Update the Positions that were in the range removed.
  399. if(posRefs != null) {
  400. updateUndoPositions(posRefs);
  401. posRefs = null;
  402. }
  403. string = null;
  404. }
  405. } catch (BadLocationException bl) {
  406. throw new CannotUndoException();
  407. }
  408. }
  409. public void redo() throws CannotRedoException {
  410. super.redo();
  411. try {
  412. synchronized(StringContent.this) {
  413. string = getString(offset, length);
  414. // Get the Positions in the range being removed.
  415. if(marks != null)
  416. posRefs = getPositionsInRange(null, offset, length);
  417. remove(offset, length);
  418. }
  419. } catch (BadLocationException bl) {
  420. throw new CannotRedoException();
  421. }
  422. }
  423. // Where the string goes.
  424. protected int offset;
  425. // Length of the string.
  426. protected int length;
  427. // The string that was inserted. This will be null after an undo.
  428. protected String string;
  429. // An array of instances of UndoPosRef for the Positions in the
  430. // range that was removed, valid before undo.
  431. protected Vector posRefs;
  432. }
  433. }