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