/*
 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package javax.swing.text;

import java.util.Vector;

A plain document that maintains no character attributes. The default element structure for this document is a map of the lines in the text. The Element returned by getDefaultRootElement is a map of the lines, and each child element represents a line. This model does not maintain any character level attributes, but each line can be tagged with an arbitrary set of attributes. Line to offset, and offset to line translations can be quickly performed using the default root element. The structure information of the DocumentEvent's fired by edits will indicate the line structure changes.

The default content storage management is performed by a gapped buffer implementation (GapContent). It supports editing reasonably large documents with good efficiency when the edits are contiguous or clustered, as is typical.

Warning: Serialized objects of this class will not be compatible with future Swing releases. The current serialization support is appropriate for short term storage or RMI between applications running the same version of Swing. As of 1.4, support for long term storage of all JavaBeans™ has been added to the java.beans package. Please see XMLEncoder.

Author: Timothy Prinzing
See Also:
/** * A plain document that maintains no character attributes. The * default element structure for this document is a map of the lines in * the text. The Element returned by getDefaultRootElement is * a map of the lines, and each child element represents a line. * This model does not maintain any character level attributes, * but each line can be tagged with an arbitrary set of attributes. * Line to offset, and offset to line translations can be quickly * performed using the default root element. The structure information * of the DocumentEvent's fired by edits will indicate the line * structure changes. * <p> * The default content storage management is performed by a * gapped buffer implementation (GapContent). It supports * editing reasonably large documents with good efficiency when * the edits are contiguous or clustered, as is typical. * <p> * <strong>Warning:</strong> * Serialized objects of this class will not be compatible with * future Swing releases. The current serialization support is * appropriate for short term storage or RMI between applications running * the same version of Swing. As of 1.4, support for long term storage * of all JavaBeans&trade; * has been added to the <code>java.beans</code> package. * Please see {@link java.beans.XMLEncoder}. * * @author Timothy Prinzing * @see Document * @see AbstractDocument */
@SuppressWarnings("serial") // Same-version serialization only public class PlainDocument extends AbstractDocument {
Name of the attribute that specifies the tab size for tabs contained in the content. The type for the value is Integer.
/** * Name of the attribute that specifies the tab * size for tabs contained in the content. The * type for the value is Integer. */
public static final String tabSizeAttribute = "tabSize";
Name of the attribute that specifies the maximum length of a line, if there is a maximum length. The type for the value is Integer.
/** * Name of the attribute that specifies the maximum * length of a line, if there is a maximum length. * The type for the value is Integer. */
public static final String lineLimitAttribute = "lineLimit";
Constructs a plain text document. A default model using GapContent is constructed and set.
/** * Constructs a plain text document. A default model using * <code>GapContent</code> is constructed and set. */
public PlainDocument() { this(new GapContent()); }
Constructs a plain text document. A default root element is created, and the tab size set to 8.
Params:
  • c – the container for the content
/** * Constructs a plain text document. A default root element is created, * and the tab size set to 8. * * @param c the container for the content */
public PlainDocument(Content c) { super(c); putProperty(tabSizeAttribute, Integer.valueOf(8)); defaultRoot = createDefaultRoot(); }
Inserts some content into the document. Inserting content causes a write lock to be held while the actual changes are taking place, followed by notification to the observers on the thread that grabbed the write lock.

This method is thread safe, although most Swing methods are not. Please see Concurrency in Swing for more information.

Params:
  • offs – the starting offset >= 0
  • str – the string to insert; does nothing with null/empty strings
  • a – the attributes for the inserted content
Throws:
See Also:
/** * Inserts some content into the document. * Inserting content causes a write lock to be held while the * actual changes are taking place, followed by notification * to the observers on the thread that grabbed the write lock. * <p> * This method is thread safe, although most Swing methods * are not. Please see * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency * in Swing</A> for more information. * * @param offs the starting offset &gt;= 0 * @param str the string to insert; does nothing with null/empty strings * @param a the attributes for the inserted content * @exception BadLocationException the given insert position is not a valid * position within the document * @see Document#insertString */
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { // fields don't want to have multiple lines. We may provide a field-specific // model in the future in which case the filtering logic here will no longer // be needed. Object filterNewlines = getProperty("filterNewlines"); if ((filterNewlines instanceof Boolean) && filterNewlines.equals(Boolean.TRUE)) { if ((str != null) && (str.indexOf('\n') >= 0)) { StringBuilder filtered = new StringBuilder(str); int n = filtered.length(); for (int i = 0; i < n; i++) { if (filtered.charAt(i) == '\n') { filtered.setCharAt(i, ' '); } } str = filtered.toString(); } } super.insertString(offs, str, a); }
Gets the default root element for the document model.
See Also:
Returns:the root
/** * Gets the default root element for the document model. * * @return the root * @see Document#getDefaultRootElement */
public Element getDefaultRootElement() { return defaultRoot; }
Creates the root element to be used to represent the default document structure.
Returns:the element base
/** * Creates the root element to be used to represent the * default document structure. * * @return the element base */
protected AbstractElement createDefaultRoot() { BranchElement map = (BranchElement) createBranchElement(null, null); Element line = createLeafElement(map, null, 0, 1); Element[] lines = new Element[1]; lines[0] = line; map.replace(0, 0, lines); return map; }
Get the paragraph element containing the given position. Since this document only models lines, it returns the line instead.
/** * Get the paragraph element containing the given position. Since this * document only models lines, it returns the line instead. */
public Element getParagraphElement(int pos){ Element lineMap = getDefaultRootElement(); return lineMap.getElement( lineMap.getElementIndex( pos ) ); }
Updates document structure as a result of text insertion. This will happen within a write lock. Since this document simply maps out lines, we refresh the line map.
Params:
  • chng – the change event describing the dit
  • attr – the set of attributes for the inserted text
/** * Updates document structure as a result of text insertion. This * will happen within a write lock. Since this document simply * maps out lines, we refresh the line map. * * @param chng the change event describing the dit * @param attr the set of attributes for the inserted text */
protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) { removed.removeAllElements(); added.removeAllElements(); BranchElement lineMap = (BranchElement) getDefaultRootElement(); int offset = chng.getOffset(); int length = chng.getLength(); if (offset > 0) { offset -= 1; length += 1; } int index = lineMap.getElementIndex(offset); Element rmCandidate = lineMap.getElement(index); int rmOffs0 = rmCandidate.getStartOffset(); int rmOffs1 = rmCandidate.getEndOffset(); int lastOffset = rmOffs0; try { if (s == null) { s = new Segment(); } getContent().getChars(offset, length, s); boolean hasBreaks = false; for (int i = 0; i < length; i++) { char c = s.array[s.offset + i]; if (c == '\n') { int breakOffset = offset + i + 1; added.addElement(createLeafElement(lineMap, null, lastOffset, breakOffset)); lastOffset = breakOffset; hasBreaks = true; } } if (hasBreaks) { removed.addElement(rmCandidate); if ((offset + length == rmOffs1) && (lastOffset != rmOffs1) && ((index+1) < lineMap.getElementCount())) { Element e = lineMap.getElement(index+1); removed.addElement(e); rmOffs1 = e.getEndOffset(); } if (lastOffset < rmOffs1) { added.addElement(createLeafElement(lineMap, null, lastOffset, rmOffs1)); } Element[] aelems = new Element[added.size()]; added.copyInto(aelems); Element[] relems = new Element[removed.size()]; removed.copyInto(relems); ElementEdit ee = new ElementEdit(lineMap, index, relems, aelems); chng.addEdit(ee); lineMap.replace(index, relems.length, aelems); } if (Utilities.isComposedTextAttributeDefined(attr)) { insertComposedTextUpdate(chng, attr); } } catch (BadLocationException e) { throw new Error("Internal error: " + e.toString()); } super.insertUpdate(chng, attr); }
Updates any document structure as a result of text removal. This will happen within a write lock. Since the structure represents a line map, this just checks to see if the removal spans lines. If it does, the two lines outside of the removal area are joined together.
Params:
  • chng – the change event describing the edit
/** * Updates any document structure as a result of text removal. * This will happen within a write lock. Since the structure * represents a line map, this just checks to see if the * removal spans lines. If it does, the two lines outside * of the removal area are joined together. * * @param chng the change event describing the edit */
protected void removeUpdate(DefaultDocumentEvent chng) { removed.removeAllElements(); BranchElement map = (BranchElement) getDefaultRootElement(); int offset = chng.getOffset(); int length = chng.getLength(); int line0 = map.getElementIndex(offset); int line1 = map.getElementIndex(offset + length); if (line0 != line1) { // a line was removed for (int i = line0; i <= line1; i++) { removed.addElement(map.getElement(i)); } int p0 = map.getElement(line0).getStartOffset(); int p1 = map.getElement(line1).getEndOffset(); Element[] aelems = new Element[1]; aelems[0] = createLeafElement(map, null, p0, p1); Element[] relems = new Element[removed.size()]; removed.copyInto(relems); ElementEdit ee = new ElementEdit(map, line0, relems, aelems); chng.addEdit(ee); map.replace(line0, relems.length, aelems); } else { //Check for the composed text element Element line = map.getElement(line0); if (!line.isLeaf()) { Element leaf = line.getElement(line.getElementIndex(offset)); if (Utilities.isComposedTextElement(leaf)) { Element[] aelem = new Element[1]; aelem[0] = createLeafElement(map, null, line.getStartOffset(), line.getEndOffset()); Element[] relem = new Element[1]; relem[0] = line; ElementEdit ee = new ElementEdit(map, line0, relem, aelem); chng.addEdit(ee); map.replace(line0, 1, aelem); } } } super.removeUpdate(chng); } // // Inserts the composed text of an input method. The line element // where the composed text is inserted into becomes an branch element // which contains leaf elements of the composed text and the text // backing store. // private void insertComposedTextUpdate(DefaultDocumentEvent chng, AttributeSet attr) { added.removeAllElements(); BranchElement lineMap = (BranchElement) getDefaultRootElement(); int offset = chng.getOffset(); int length = chng.getLength(); int index = lineMap.getElementIndex(offset); Element elem = lineMap.getElement(index); int elemStart = elem.getStartOffset(); int elemEnd = elem.getEndOffset(); BranchElement[] abelem = new BranchElement[1]; abelem[0] = (BranchElement) createBranchElement(lineMap, null); Element[] relem = new Element[1]; relem[0] = elem; if (elemStart != offset) added.addElement(createLeafElement(abelem[0], null, elemStart, offset)); added.addElement(createLeafElement(abelem[0], attr, offset, offset+length)); if (elemEnd != offset+length) added.addElement(createLeafElement(abelem[0], null, offset+length, elemEnd)); Element[] alelem = new Element[added.size()]; added.copyInto(alelem); ElementEdit ee = new ElementEdit(lineMap, index, relem, abelem); chng.addEdit(ee); abelem[0].replace(0, 0, alelem); lineMap.replace(index, 1, abelem); } private AbstractElement defaultRoot; private Vector<Element> added = new Vector<Element>(); private Vector<Element> removed = new Vector<Element>(); private transient Segment s; }