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