1. /*
  2. * @(#)PlainDocument.java 1.43 03/12/19
  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 javax.swing.event.*;
  10. /**
  11. * A plain document that maintains no character attributes. The
  12. * default element structure for this document is a map of the lines in
  13. * the text. The Element returned by getDefaultRootElement is
  14. * a map of the lines, and each child element represents a line.
  15. * This model does not maintain any character level attributes,
  16. * but each line can be tagged with an arbitrary set of attributes.
  17. * Line to offset, and offset to line translations can be quickly
  18. * performed using the default root element. The structure information
  19. * of the DocumentEvent's fired by edits will indicate the line
  20. * structure changes.
  21. * <p>
  22. * The default content storage management is performed by a
  23. * gapped buffer implementation (GapContent). It supports
  24. * editing reasonably large documents with good efficiency when
  25. * the edits are contiguous or clustered, as is typical.
  26. * <p>
  27. * <strong>Warning:</strong>
  28. * Serialized objects of this class will not be compatible with
  29. * future Swing releases. The current serialization support is
  30. * appropriate for short term storage or RMI between applications running
  31. * the same version of Swing. As of 1.4, support for long term storage
  32. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  33. * has been added to the <code>java.beans</code> package.
  34. * Please see {@link java.beans.XMLEncoder}.
  35. *
  36. * @author Timothy Prinzing
  37. * @version 1.43 12/19/03
  38. * @see Document
  39. * @see AbstractDocument
  40. */
  41. public class PlainDocument extends AbstractDocument {
  42. /**
  43. * Name of the attribute that specifies the tab
  44. * size for tabs contained in the content. The
  45. * type for the value is Integer.
  46. */
  47. public static final String tabSizeAttribute = "tabSize";
  48. /**
  49. * Name of the attribute that specifies the maximum
  50. * length of a line, if there is a maximum length.
  51. * The type for the value is Integer.
  52. */
  53. public static final String lineLimitAttribute = "lineLimit";
  54. /**
  55. * Constructs a plain text document. A default model using
  56. * <code>GapContent</code> is constructed and set.
  57. */
  58. public PlainDocument() {
  59. this(new GapContent());
  60. }
  61. /**
  62. * Constructs a plain text document. A default root element is created,
  63. * and the tab size set to 8.
  64. *
  65. * @param c the container for the content
  66. */
  67. public PlainDocument(Content c) {
  68. super(c);
  69. putProperty(tabSizeAttribute, new Integer(8));
  70. defaultRoot = createDefaultRoot();
  71. }
  72. /**
  73. * Inserts some content into the document.
  74. * Inserting content causes a write lock to be held while the
  75. * actual changes are taking place, followed by notification
  76. * to the observers on the thread that grabbed the write lock.
  77. * <p>
  78. * This method is thread safe, although most Swing methods
  79. * are not. Please see
  80. * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
  81. * and Swing</A> for more information.
  82. *
  83. * @param offs the starting offset >= 0
  84. * @param str the string to insert; does nothing with null/empty strings
  85. * @param a the attributes for the inserted content
  86. * @exception BadLocationException the given insert position is not a valid
  87. * position within the document
  88. * @see Document#insertString
  89. */
  90. public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
  91. // fields don't want to have multiple lines. We may provide a field-specific
  92. // model in the future in which case the filtering logic here will no longer
  93. // be needed.
  94. Object filterNewlines = getProperty("filterNewlines");
  95. if ((filterNewlines instanceof Boolean) && filterNewlines.equals(Boolean.TRUE)) {
  96. if ((str != null) && (str.indexOf('\n') >= 0)) {
  97. StringBuffer filtered = new StringBuffer(str);
  98. int n = filtered.length();
  99. for (int i = 0; i < n; i++) {
  100. if (filtered.charAt(i) == '\n') {
  101. filtered.setCharAt(i, ' ');
  102. }
  103. }
  104. str = filtered.toString();
  105. }
  106. }
  107. super.insertString(offs, str, a);
  108. }
  109. /**
  110. * Gets the default root element for the document model.
  111. *
  112. * @return the root
  113. * @see Document#getDefaultRootElement
  114. */
  115. public Element getDefaultRootElement() {
  116. return defaultRoot;
  117. }
  118. /**
  119. * Creates the root element to be used to represent the
  120. * default document structure.
  121. *
  122. * @return the element base
  123. */
  124. protected AbstractElement createDefaultRoot() {
  125. BranchElement map = (BranchElement) createBranchElement(null, null);
  126. Element line = createLeafElement(map, null, 0, 1);
  127. Element[] lines = new Element[1];
  128. lines[0] = line;
  129. map.replace(0, 0, lines);
  130. return map;
  131. }
  132. /**
  133. * Get the paragraph element containing the given position. Since this
  134. * document only models lines, it returns the line instead.
  135. */
  136. public Element getParagraphElement(int pos){
  137. Element lineMap = getDefaultRootElement();
  138. return lineMap.getElement( lineMap.getElementIndex( pos ) );
  139. }
  140. /**
  141. * Updates document structure as a result of text insertion. This
  142. * will happen within a write lock. Since this document simply
  143. * maps out lines, we refresh the line map.
  144. *
  145. * @param chng the change event describing the dit
  146. * @param attr the set of attributes for the inserted text
  147. */
  148. protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
  149. removed.removeAllElements();
  150. added.removeAllElements();
  151. BranchElement lineMap = (BranchElement) getDefaultRootElement();
  152. int offset = chng.getOffset();
  153. int length = chng.getLength();
  154. if (offset > 0) {
  155. offset -= 1;
  156. length += 1;
  157. }
  158. int index = lineMap.getElementIndex(offset);
  159. Element rmCandidate = lineMap.getElement(index);
  160. int rmOffs0 = rmCandidate.getStartOffset();
  161. int rmOffs1 = rmCandidate.getEndOffset();
  162. int lastOffset = rmOffs0;
  163. try {
  164. if (s == null) {
  165. s = new Segment();
  166. }
  167. getContent().getChars(offset, length, s);
  168. boolean hasBreaks = false;
  169. for (int i = 0; i < length; i++) {
  170. char c = s.array[s.offset + i];
  171. if (c == '\n') {
  172. int breakOffset = offset + i + 1;
  173. added.addElement(createLeafElement(lineMap, null, lastOffset, breakOffset));
  174. lastOffset = breakOffset;
  175. hasBreaks = true;
  176. }
  177. }
  178. if (hasBreaks) {
  179. int rmCount = 1;
  180. removed.addElement(rmCandidate);
  181. if ((offset + length == rmOffs1) && (lastOffset != rmOffs1) &&
  182. ((index+1) < lineMap.getElementCount())) {
  183. rmCount += 1;
  184. Element e = lineMap.getElement(index+1);
  185. removed.addElement(e);
  186. rmOffs1 = e.getEndOffset();
  187. }
  188. if (lastOffset < rmOffs1) {
  189. added.addElement(createLeafElement(lineMap, null, lastOffset, rmOffs1));
  190. }
  191. Element[] aelems = new Element[added.size()];
  192. added.copyInto(aelems);
  193. Element[] relems = new Element[removed.size()];
  194. removed.copyInto(relems);
  195. ElementEdit ee = new ElementEdit(lineMap, index, relems, aelems);
  196. chng.addEdit(ee);
  197. lineMap.replace(index, relems.length, aelems);
  198. }
  199. if (Utilities.isComposedTextAttributeDefined(attr)) {
  200. insertComposedTextUpdate(chng, attr);
  201. }
  202. } catch (BadLocationException e) {
  203. throw new Error("Internal error: " + e.toString());
  204. }
  205. super.insertUpdate(chng, attr);
  206. }
  207. /**
  208. * Updates any document structure as a result of text removal.
  209. * This will happen within a write lock. Since the structure
  210. * represents a line map, this just checks to see if the
  211. * removal spans lines. If it does, the two lines outside
  212. * of the removal area are joined together.
  213. *
  214. * @param chng the change event describing the edit
  215. */
  216. protected void removeUpdate(DefaultDocumentEvent chng) {
  217. removed.removeAllElements();
  218. BranchElement map = (BranchElement) getDefaultRootElement();
  219. int offset = chng.getOffset();
  220. int length = chng.getLength();
  221. int line0 = map.getElementIndex(offset);
  222. int line1 = map.getElementIndex(offset + length);
  223. if (line0 != line1) {
  224. // a line was removed
  225. for (int i = line0; i <= line1; i++) {
  226. removed.addElement(map.getElement(i));
  227. }
  228. int p0 = map.getElement(line0).getStartOffset();
  229. int p1 = map.getElement(line1).getEndOffset();
  230. Element[] aelems = new Element[1];
  231. aelems[0] = createLeafElement(map, null, p0, p1);
  232. Element[] relems = new Element[removed.size()];
  233. removed.copyInto(relems);
  234. ElementEdit ee = new ElementEdit(map, line0, relems, aelems);
  235. chng.addEdit(ee);
  236. map.replace(line0, relems.length, aelems);
  237. } else {
  238. //Check for the composed text element
  239. Element line = map.getElement(line0);
  240. if (!line.isLeaf()) {
  241. Element leaf = line.getElement(line.getElementIndex(offset));
  242. if (Utilities.isComposedTextElement(leaf)) {
  243. Element[] aelem = new Element[1];
  244. aelem[0] = createLeafElement(map, null,
  245. line.getStartOffset(), line.getEndOffset());
  246. Element[] relem = new Element[1];
  247. relem[0] = line;
  248. ElementEdit ee = new ElementEdit(map, line0, relem, aelem);
  249. chng.addEdit(ee);
  250. map.replace(line0, 1, aelem);
  251. }
  252. }
  253. }
  254. super.removeUpdate(chng);
  255. }
  256. //
  257. // Inserts the composed text of an input method. The line element
  258. // where the composed text is inserted into becomes an branch element
  259. // which contains leaf elements of the composed text and the text
  260. // backing store.
  261. //
  262. private void insertComposedTextUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
  263. added.removeAllElements();
  264. BranchElement lineMap = (BranchElement) getDefaultRootElement();
  265. int offset = chng.getOffset();
  266. int length = chng.getLength();
  267. int index = lineMap.getElementIndex(offset);
  268. Element elem = lineMap.getElement(index);
  269. int elemStart = elem.getStartOffset();
  270. int elemEnd = elem.getEndOffset();
  271. BranchElement[] abelem = new BranchElement[1];
  272. abelem[0] = (BranchElement) createBranchElement(lineMap, null);
  273. Element[] relem = new Element[1];
  274. relem[0] = elem;
  275. if (elemStart != offset)
  276. added.addElement(createLeafElement(abelem[0], null, elemStart, offset));
  277. added.addElement(createLeafElement(abelem[0], attr, offset, offset+length));
  278. if (elemEnd != offset+length)
  279. added.addElement(createLeafElement(abelem[0], null, offset+length, elemEnd));
  280. Element[] alelem = new Element[added.size()];
  281. added.copyInto(alelem);
  282. ElementEdit ee = new ElementEdit(lineMap, index, relem, abelem);
  283. chng.addEdit(ee);
  284. abelem[0].replace(0, 0, alelem);
  285. lineMap.replace(index, 1, abelem);
  286. }
  287. private AbstractElement defaultRoot;
  288. private Vector added = new Vector(); // Vector<Element>
  289. private Vector removed = new Vector(); // Vector<Element>
  290. private transient Segment s;
  291. }