/*
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
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.
* <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™
* has been added to the <code>java.beans</code> package.
* Please see {@link java.beans.XMLEncoder}.
*
* @author Timothy Prinzing
* @see Document
* @see AbstractDocument
*/
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.
*/
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.
*/
public static final
String lineLimitAttribute = "lineLimit";
/**
* 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.
*
* @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.
* <p>
* This method is thread safe, although most Swing methods
* are not. Please see
* <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
* in Swing</A> for more information.
*
* @param offs the starting offset >= 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.
*
* @return the root
* @see Document#getDefaultRootElement
*/
public
Element getDefaultRootElement() {
return
defaultRoot;
}
/**
* 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.
*/
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.
*
* @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.
*
* @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;
}