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