22.3 The Document Model


The Document is the M part of the MVC (Model-View-Controller) architecture for all of Swing's text components. It is responsible for the text content of the component as well as relevant style information for text components that support styled text. The Document model must be simple enough to be used by JTextField, but powerful and flexible enough to be used by JTextPane and JEditorPane. Swing accomplishes this by providing the classes and interfaces shown in Figure 22-7.

Figure 22-7. High-level Document class diagram
figs/swng2.2207.gif

Basically, a Document partitions its content into small pieces called Elements. Each Element is small enough that its style information can be represented by a single AttributeSet. The Elements are organized into a tree structure[22] with a single root.

[22] The term Element applies to the interior nodes of the tree as well as to the leaf nodes. So it is more accurate to say that the Document partitions its content into small pieces called LeafElements. The interior nodes are called BranchElements, and each may have an arbitrary number of child Elements.

Swing provides the Document interface, which doesn't support styled text, and the StyledDocument interface, which does. But note that there is no StyledElement interface. Swing provides a single Element interface that does support style. The simpler document types (such as PlainDocument, which JTextField and JTextArea use by default) use Elements but don't assign any style information to them.

22.3.1 The Element Interface

The Element interface is used to describe a portion of a document. But note that an Element does not actually contain a portion of the document; it just defines a way of structuring a portion. It maintains a start offset and end offset into the actual text content, which is stored elsewhere by the Document.

Each Element may have style information, stored in an AttributeSet, that applies to the entire Element. Figure 22-8 gives an example of how a Document that supports styled text must change a single Element representing a phrase in italics into a subtree of Elements when a word in the middle is changed from italic to bold.

Figure 22-8. Sample Element structure
figs/swng2.2208.gif

The branch Element may have an AttributeSet of its own, though how this is interpreted is up to the implementing class. The point is probably moot because the classes in Swing that implement Element also implement MutableAttributeSet, with the resolveParent (unless explicitly set otherwise) defaulting to the parent Element. Even if the resolveParent is explicitly set, the Element's getAttribute( ) method is overridden to consult its parent in the Element tree if it doesn't find a value through its resolveParent.

22.3.1.1 Properties

The Element interface defines the properties shown in Table 22-19. The attributes property is the AttributeSet containing the style information that applies to this element. The document property provides access to the Document this Element describes.

Table 22-19. Element properties

Property

Data type

get

is

set

Default value

attributes

AttributeSet

·

     

document

Document

·

     

elementi

Element

·

     

elementCount

int

·

     

endOffset

int

·

     

leaf

boolean

 

·

   

name

String

·

     

parentElement

Element

·

     

startOffset

int

·

     

iindexed

The elementCount property specifies the number of children (possibly zero) the Element has, and element is an indexed property for accessing those children. The children are always kept in order, so you are assured that getElement(0) affects offsets in the document that appear before those affected by getElement(1). If this Element has no children, the leaf property is true. The parentElement property is the Element that contains this Element, or null if this Element is a root.

The name property is a brief name for the Element and is often one of the values listed in Table 22-23.

The startOffset and endOffset properties are offsets into the document text that specify the portion of the document the Element covers. Expect these values to change as text is inserted in or deleted from the document. These offsets are relative to the beginning of the document, not relative to the parentElement's startOffset. (Note that endOffset actually points to the position after the last character of the Element, as depicted in Figure 22-8.)

22.3.1.2 Element lookup method
public int getElementIndex(int offset)

Return the index of the child element closest to the given offset (relative to the beginning of the Document). The return value can be used as the index for the element property to retrieve the child Element itself. If the element that you invoke this method on is a leaf element (and therefore has no children), it returns -1.

22.3.2 The Document Interface

The Document interface is the foundation of the document model used by all Swing text components. It defines methods for manipulating the text content, methods for registering event listeners, and methods for accessing the Element tree.

Actually, Document supports the existence of multiple Element trees, though typically there is only one. The idea is to support multiple ways of structuring the same document content. For example, a play could have one Element tree organized by act/scene/line and another organized by page/paragraph/sentence. Both trees cover the entire text of the play, just in different ways. (It would be a mistake to have one tree cover Act I and another tree cover Act II. If that's what you want, use two separate Documents.)

22.3.2.1 Properties

Table 22-20 shows the properties defined by the Document interface. They are fairly straightforward. The defaultRootElement property is the root of this Document's Element tree, or if this Document has more than one tree, the root of the "default" tree. The rootElements property is an array of all the tree roots, including defaultRootElement.

Table 22-20. Document properties

Property

Data Type

get

is

set

Default value

defaultRootElement

Element

·

     

endPosition

Position

·

     

length

int

·

     

rootElements

Element[]

·

     

startPosition

Position

·

     

The length property is the total number of "characters" in the Document. This contains all the actual characters in the document, including tabs and newlines[23] embedded Components and Icons (which count as one "character" each).

[23] Recall that newlines are always stored in memory as \n, regardless of how the current platform writes them in a file, so they always count as one character.

The startPosition and endPosition properties are offsets to the beginning and end (actually, one position past the last character) of the current document text. The type of these properties is Position, which keeps track of a particular location in a Document as text is inserted or deleted over time. We cover Position later in this chapter.

22.3.2.2 Events

Implementations of the Document interface fire DocumentEvents to indicate changes to the Document's contents. Document types that support undo also fire UndoableEditEvents. UndoableEditEvent and UndoableEditListener are covered in Chapter 18. DocumentEvent and DocumentListener are discussed later in this chapter.

Document defines the following standard methods for managing event listeners:

public void addDocumentListener(DocumentListener listener)
public void removeDocumentListener(DocumentListener listener)
public void addUndoableEditListener(UndoableEditListener listener)
public void removeUndoableEditListener(UndoableEditListener listener)
22.3.2.3 Constants

Document defines the two constants shown in Table 22-21, which are intended to be used as keys when calling the getProperty( ) and putProperty( ) methods.

Table 22-21. Document constants

Constant

Type

Description

StreamDescriptionProperty

String

The key used to store a description of the stream the Document was created from, if it was initialized from a stream and anything is known about the stream

TitleProperty

String

The key used to store the Document's name, if it has one

22.3.2.4 Text manipulation methods

These methods manipulate the contents of the Document. They all throw a BadLocationException if you attempt to reference a document offset that does not exist (for example, if you try to insert text at offset 200 of a 100-character Document). BadLocationException (defined later in this chapter) is used throughout the text package to signal this condition.

public String getText(int offset, int length) throws BadLocationException

Retrieve length characters of text starting at the specified offset into the Document.

public void getText(int offset, int length, Segment txt) throws BadLocationException

Equivalent to getText(offset, length), except for efficiency, the text is not returned as a String. Instead, the requested text is placed into the supplied Segment object. (We discuss the Segment class later in this chapter.)

public void insertString(int offset, String str, AttributeSet a) throws BadLocationException

Insert the specified string (with the specified style attributes) at the specified offset into the Document and update its Element tree(s) to reflect the change. The AttributeSet may be null.

public void remove(int offset, int len) throws BadLocationException

Delete length characters of text starting at the specified offset into the Document and update its Element tree(s) to reflect the change. Some versions of Swing spuriously call this method with a length of zero, so implementations should be prepared to handle this case.

22.3.2.5 Other methods
public Position createPosition(int offset) throws BadLocationException

Return a Position object used to track the content of the Document at the specified offset as text is inserted or deleted over time.

public Object getProperty(Object key)
public void putProperty(Object key, Object value)

Retrieve and set (respectively) arbitrary properties associated with the document. These properties can be used to store such things as the document's title, author, etc.

public void render(Runnable r)

Execute the given Runnable, guaranteeing that the content of the model is not changed while the Runnable is running. The Runnable itself must not alter the model. This method allows the Document to be painted without concerns about its contents changing during the painting process. It is called by the TextUI's paint( ) method.

22.3.3 The AbstractDocument Class

Much of the implementation of the Document interface is provided by the AbstractDocument class. One significant feature provided by this default implementation is a basic locking mechanism. Unlike most methods in Swing, certain methods in the classes that make up the document model are thread-safe. AbstractDocument is specifically designed so that it may be used by multiple threads. It also defines several important inner classes/interfaces:

Content

An inner interface that defines the API for storing and accessing the text content of the Document. Implementations of this interface may or may not support undoable edits.

AttributeContext

An inner interface that defines an API for AttributeSet caching. See Section 22.2.6.6 earlier in this chapter.

LeafElement

An inner class that provides a concrete implementation of the Element interface. This class is tailored for Elements that directly represent content and have no children.

BranchElement

An inner class that provides a concrete implementation of the Element interface. This class is tailored for interior Elements with one or more child Elements.

AbstractElement

An abstract inner class that is the superclass of both LeafElement and BranchElement. It implements the MutableAttributeSet interface in addition to Element.[24] The resolveParent of the AttributeSet defaults to the parent Element in the Element tree unless explicitly set otherwise. Even if set otherwise, the AttributeSet getValue( ) method consults the parent Element in the Element tree if it doesn't find a value through the resolveParent.

[24] AbstractDocument.AbstractElement also implements the TreeNode interface. See Chapter 17.

This means that the attributes of the Element apply to its children in the Element tree, but attributes of the child Elements override parent values.

DefaultDocumentEvent and ElementEdit

These inner classes descend from AbstractUndoableEdit and are used for event notification.

22.3.3.1 Properties

AbstractDocument defines the properties shown in Table 22-22. The important ones are defined by the Document interface. The documentProperties property (a java.util.Dictionary) allows direct access to the storage mechanism used in support of the getProperty( ) and putProperty( ) methods defined by Document. documentListeners and undoableEditListeners provide access to any registered listeners on the Document (as does the getListeners( ) method).

Table 22-22. AbstractDocument properties

Property

Data type

get

is

set

Default value

asynchronousLoadPriority

int

·

 

·

-1

bidiRootElement

Element

·

     

defaultRootElemento

Element

·

   

Abstract

documentFilter1.4

DocumentFilter

·

 

·

null

documentListeners1.4

DocumentListener[]

·

   

null

documentProperties

Dictionary

·

 

·

null

endPositiono

Position

·

     

lengtho

int

·

   

0

rootElementso

Element[]

·

   

{ defaultRootElement, bidiRootElement }

startPositiono

Position

·

     

undoableEditListeners1.4

UndoableEditListener[]

·

   

null

1.4since 1.4, ooverridden

The documentFilter property (if not null) is an object that oversees and can influence any insertions, replacements, or deletions within the documentFilter's text content. We cover the DocumentFilter class later in this chapter.

In addition to the expected defaultRootElement, AbstractDocument defines a bidiRootElement property, which is useful only if you're interested in the bidirectional level (as defined by the Unicode bidi algorithm) in mixed left-to-right and right-to-left text.

The asynchronousLoadPriority property is not used outside of HTMLEditorKit. A value less than zero indicates that the Document should not be loaded asynchronously.

22.3.3.2 Events

AbstractDocument fires DocumentEvents and UndoableEditEvents when changes are made to the document. It implements the following standard methods for managing event listeners:

public void addDocumentListener(DocumentListener listener)
public void removeDocumentListener(DocumentListener listener)
public DocumentListener[] getDocumentListeners( ) (since 1.4)
public void addUndoableEditListener(UndoableEditListener listener)
public void removeUndoableEditListener(UndoableEditListener listener)
public UndoableEditListener[] getUndoableEditListeners( ) (since 1.4)
public EventListener[] getListeners(Class listenerType) (since 1.3)
protected void fireChangedUpdate(DocumentEvent e)
protected void fireInsertUpdate(DocumentEvent e)
protected void fireRemoveUpdate(DocumentEvent e)
protected void fireUndoableEditUpdate(UndoableEditEvent e)
22.3.3.3 Constants

AbstractDocument defines an attribute key constant and four attribute value constants for that key. These public constants are shown in Table 22-23.

Table 22-23. AbstractDocument constants

Constant

Data type

Description

BidiElementName

String

name value typically used by bidi Elements

ContentElementName

String

name value typically used by LeafElements

ElementNameAttribute

String

Attribute key used to store element names

ParagraphElementName

String

name value often used by BranchElements

SectionElementName

String

Possible name value for "higher" BranchElements

Also see Table 22-11 for two additional values for the ElementNameAttribute key.

22.3.3.4 Constructors

Since AbstractDocument is an abstract class, its constructors are called only by subclasses. The constructors require arguments that are implementations of inner interfaces defined by AbstractDocument. Fortunately, Swing provides implementations for us.

protected AbstractDocument(AbstractDocument.Content data, AbstractDocument.AttributeContext context)

The data argument is responsible for actually holding the text content of the Document and should be an object of type GapContent.[25] Because the caret is allowed to be placed immediately after the last character of the Document, AbstractDocument expects a single newline character (which it calls the "implied break") to be already present in this object when it is passed to its constructor. (The GapContent and StringContent insert this character automatically in their constructors.) The context argument caches AttributeSets (for efficiency) and should be an object of type StyleContext.

[25] Swing provides another implementation of the AbstractDocument.Content interface called StringContent, but it is inefficient compared to GapContent. The GapContent and StringContent classes are covered later in this chapter.

protected AbstractDocument(AbstractDocument.Content data)

Equivalent to calling the two-argument version of the constructor, passing StyleContext.getDefaultStyleContext( ) as the second argument.

22.3.3.5 Locking methods

AbstractDocument implements a basic locking mechanism that ensures that, at any given time, there is either a single writer of the document or zero or more readers. That is, if no one is writing to the document, anyone is allowed to read it or write to it. Once someone begins writing, no one is able to read until the writer has finished.

Certain methods that technically "read" the document (such as getText( )) do not actually obtain a read lock to do so. The only method that obtains a read lock is the render( ) method, meaning that you are not guaranteed document stability when other access methods are implemented.

This locking scheme is supported by the following methods. If you decide to use the existing document types, you don't have to understand all the details; the locks are exploited automatically. But if you decide to implement your own document type, it is important to understand how this works. Any code that modifies the text content, the Element tree, or the Document properties should be framed like this:

try {   writeLock( );   // Code that messes with the Document goes here. } finally { writeUnlock( ); }
protected final void writeLock( )

Block until able to obtain the write lock. If the write lock is held by another thread, or if there are any readers, this method waits until it is notified that the state of the locks has changed before making an attempt to obtain the lock. Once the lock has been obtained, this method returns, and no other read or write locks can be obtained until the lock is released.

protected final void writeUnlock( )

Release the write lock, allowing waiting readers or writers to obtain locks.

protected final Thread getCurrentWriter( )

Return the thread currently holding the write lock or null if there is none.

public final void readLock( )

Block until able to obtain a read lock. If another thread holds the write lock, this method waits until it is notified that the lock has been released before trying to obtain the lock again. Multiple threads may hold read locks simultaneously.

public final void readUnlock( )

Called to indicate that the current thread is no longer reading the document. If this was the only reader, writing may begin (threads waiting for the lock are notified).

public void render(Runnable r)

Called to render the Document visually. It obtains a read lock, ensuring that no changes are made to the Document during the rendering process. It then calls the input Runnable's run( )[26] method. This method must not attempt to modify the Document since deadlock occurs if it tries to obtain a write lock. When the run( ) method completes (either naturally or by throwing an exception), the read lock is released. Note that there is nothing in this method directly related to rendering the Document. It could technically be used to execute any arbitrary code while holding a read lock.

[26] It calls this method directly from the current Thread. No new Thread is created.

22.3.3.6 Text manipulation methods

These methods read and write the underlying Document content. The methods that modify the content must obtain a write lock before proceeding.

public String getText(int offset, int length) throws BadLocationException

Retrieve length characters of text starting at the specified offset into the Document. (This method does not obtain a read lock while accessing the Document's content.)

public void getText(int offset, int length, Segment txt) throws BadLocationException

Equivalent to getText(offset, length), except for efficiency, the text is not returned as a String. Instead, the requested text is placed into the supplied Segment object. (Segment is discussed in depth later in this chapter.)

public void insertString(int offset, String str, AttributeSet attrs) throws BadLocationException

Insert the specified string (with the specified style attributes) at the specified offset into the Document. This method blocks until it can obtain the Document's write lock. After performing the insertion, it fires a DocumentEvent and (if this is an undoable edit) an UndoableEditEvent( ).

public void remove(int offset, int len) throws BadLocationException

Delete length characters of text starting at the specified offset into the Document. Like insertString( ), this method blocks until it can obtain a write lock. After the removal, it fires a DocumentEvent and (if this is an undoable edit) an UndoableEditEvent( ).

public void replace(int offset, int length, int offset, String text, AttributeSet attrs) throws BadLocationException

Equivalent to calling remove(offset, length) followed by insertString(offset, text, attrs). This is for the benefit of classes (particularly DocumentFilter and JFormattedTextField) that prefer to see a replacement as a single edit instead of as a separate deletion and insertion. Subclasses may or may not actually implement this as a single atomic edit. (This method was introduced in SDK 1.4.)

22.3.3.7 Other public methods
public Position createPosition(int offs) throws BadLocationException

Return a Position object used to track the content of the Document at the specified offset even as text is inserted or deleted over time.

public abstract Element getParagraphElement(int pos)

Return the Element representing the paragraph that contains the specified Document offset. It is up to the subclass to decide exactly what "paragraph" means.

public Object getProperty(Object key)
public void putProperty(Object key, Object value)

Retrieve and set (respectively) properties associated with the document. The properties are stored in the documentProperties Dictionary.

22.3.4 The PlainDocument Class

PlainDocument is a concrete subclass of AbstractDocument used for simple documents that do not need to manage complex formatting styles. The JTextField and JTextArea classes use PlainDocument as their default model. It's worth noting that PlainDocument provides more power than these components typically need. As a subclass of AbstractDocument, it supports AttributeSets, allowing the document to contain different fonts, colors, font styles, etc. These attributes are ignored when rendering the simple text components that use this document type.

The Elements that make up a PlainDocument correspond to distinct lines of text that end with a newline (\n). Each line of text maps to a single LeafElement. All of these LeafElements are contained by a single BranchElement (the Document's root Element). PlainDocument always keeps its Element tree structured as two levels like this, but subclasses are free to implement other schemes.

22.3.4.1 Properties

PlainDocument does not define any properties beyond those of AbstractDocument (see Table 22-22), though of course it provides implementations for abstract methods, such as getDefaultRootElement( ). PlainDocument does support setting its tab stop size, but clients must do this using putProperty(TabSizeAttribute, new Integer(size)) rather than with a set method. The default tab stop size is 8.

22.3.4.2 Constants

PlainDocument defines the constants shown in Table 22-24.

Table 22-24. PlainDocument constants

Constant

Data type

Description

lineLimitAttribute

String

Attribute key used to store the maximum length of a line, if any

tabSizeAttribute

String

Attribute key used to store the size of tab stops

The attribute values paired with these keys should be of type Integer. Swing never uses lineLimitAttribute, but PlainView does respect tabSizeAttribute when it draws text with tabs.

22.3.4.3 Constructors
public PlainDocument( )

Call the constructor below, passing in a new instance of GapContent.

public PlainDocument(AbstractDocument.Content content)

Create a new document using the specified Content. It adds a document property reflecting a default tab size of 8 and creates a default root Element. This constructor was protected prior to SDK 1.4.

22.3.4.4 Public methods

The only new methods defined in this class are the following:

public Element getParagraphElement(int pos)

Because PlainDocument has an Element for each line of text, this method returns the Element for the line containing the specified offset into the Document.

public void insertString(int offset, String str, AttributeSet a) throws BadLocationException

Because JTextField and its subclasses have only a single line of text, PlainDocument overrides this method to change newlines into spaces when it determines that it is appropriate to do so. Otherwise, the behavior is the same as it is in the superclass.

22.3.4.5 Restriction example

Figure 19-1 shows a JFormattedTextField that restricts the length of its content. It is possible to achieve the same effect by using a custom Document model. Before the advent of JFormattedTextField and DocumentFilter in SDK 1.4, this was the only good way to restrict a field's content. In any case, it demonstrates how easy it can be to subclass PlainDocument.

Here's the code for our custom Document model that restricts the length of its content. One complication is that the replace( ) method was added to AbstractDocument in SDK 1.4. We need to override replace( ), or else the user could exceed our length limit by selecting all and pasting in a bunch of text. But since our implementation calls super.replace( ), it won't even compile on versions prior to 1.4. There are ways around this (e.g., reflection), but it's not worth it for this simple example. With Version 1.4 and later, it's easier to use a JFormattedTextField anyway.

// MaxLengthDocument.java // import javax.swing.*; import javax.swing.text.*; // An extension of PlainDocument that restricts the length of its content public class MaxLengthDocument extends PlainDocument {   private int max;   // Create a Document with a specified max length.   public MaxLengthDocument(int maxLength) {     max = maxLength;   }   // Don't allow an insertion to exceed the max length.   public void insertString(int offset, String str, AttributeSet a)               throws BadLocationException {     if (getLength( ) + str.length( ) > max)          java.awt.Toolkit.getDefaultToolkit( ).beep( );     else super.insertString(offset, str, a);   }   // We'd need to override replace( ) as well if running under SDK 1.4.   // A sample main( ) method that demonstrates using MaxLengthDocument with a   // JTextField. Note that new JFormattedTextField(new MaskFormatter("*****")) would   // be easier.   public static void main(String[] args) {     Document doc = new MaxLengthDocument(5); // Set maximum length to 5.     JTextField field = new JTextField(doc, "", 8);     JPanel flowPanel = new JPanel( );     flowPanel.add(field);     JFrame frame = new JFrame("MaxLengthDocument demo");     frame.setContentPane(flowPanel);     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);     frame.setSize(160, 80);     frame.setVisible(true);   } } 

22.3.5 The StyledDocument Interface

StyledDocument is an extension of the Document interface that can associate different styles with different parts of its text content. StyledDocument has methods that can assign new style attributes to blocks of text. It also supports named Styles.

This interface defines the notions of "character attributes," "paragraph attributes," and "logical styles." This isn't so much a distinction of the attribute itself as it is a description of where it applies. For example, a green foreground color could be set as a character attribute, a paragraph attribute, or an attribute of a logical style. (Some attributes, such as indentation, affect only the paragraph level.) Character attributes apply to LeafElements; paragraph attributes apply to BranchElements. Logical styles also apply to BranchElements but can be overridden by local attributes. These local overrides can be either paragraph or character attributes.

The implementing class decides exactly how it structures its Element tree, and therefore what "paragraph" means.

22.3.5.1 Properties

StyledDocument does not define any properties beyond those it inherits from Document (see Table 22-20). Note that although logicalStyle, characterElement, and paragraphElement appear (by mechanical interpretation of the JavaBeans specification) to be indexed properties of StyledDocument, the "index" associated with them is a character offset into the document, not a simple array index. We omit these "properties" here and discuss the methods and those related to them in the descriptions that follow.

22.3.5.2 Style application methods

The superinterface's insertString( ) method takes an AttributeSet, but if you want to change the style attributes of text already in the Document, you need to use the methods defined here:

public void setCharacterAttributes(int offset, int length, AttributeSet s, boolean replace)

This method applies the given attributes to the specified portion of the Document. If you want the new set of attributes to completely replace the existing set (instead of augmenting it), use true for the replace argument.

public void setParagraphAttributes(int offset, int length, AttributeSet s, boolean replace)

This method applies the given attributes to the paragraphs contained (or partially contained) by the specified range. If you want the new set of attributes to completely replace the existing set (instead of augmenting it), use true for the replace argument.

public void setLogicalStyle(int offset, Style s)

This method applies the supplied Style to the paragraph that contains the specified document offset. The new Style replaces any previous logical Style.

22.3.5.3 Query methods

These methods are used to retrieve style information at a particular document offset:

public Element getCharacterElement(int offset)

Return the LeafElement that contains the specified document offset. If you're interested only in the character attributes, call getAttributes( ) on the element that is returned.

public Element getParagraphElement(int offset)

Return the BranchElement representing the paragraph that contains the specified document offset. If you're interested only in the paragraph attributes, call getAttributes( ) on the element that is returned.

public Style getLogicalStyle(int offset)

Return the logical Style in effect for the paragraph that contains the given document offset, if there is one.

22.3.5.4 StyleContext delegation methods

The StyledDocument interface defines several methods that exactly match methods provided by the StyleContext class. Though implementing classes could theoretically do something different, in practice they just delegate to an instance of StyleContext. (See Section 22.2.6 earlier in this chapter for a description of these methods.)

public Style getStyle(String nm)
public Style addStyle(String nm, Style parent)
public void removeStyle(String nm)
public Color getForeground(AttributeSet attr)
public Color getBackground(AttributeSet attr)
public Font getFont(AttributeSet attr)

22.3.6 The DefaultStyledDocument Class

DefaultStyledDocument is the implementation of StyledDocument that JTextPane uses by default. It inherits much of its functionality from AbstractDocument but adds methods for style handling. DefaultStyledDocument structures its Element tree as shown in Figure 22-9. This, together with Figure 22-8, gives a good idea of how DefaultStyledDocument works.

Figure 22-9. DefaultStyledDocument element tree structure
figs/swng2.2209.gif

This default root has a child for each paragraph (delimited by the newline character, \n) in the Document. Each of these is a BranchElement with one or more LeafElement children. The number of LeafElements depends on the character attributes of the paragraph. Within a paragraph, any block of text with differing character attributes requires its own LeafElement. If the entire paragraph has the same set of character attributes, the corresponding BranchElement has only a single child.

Character attributes are stored in the AttributeSets attached to the LeafElements. Paragraph attributes are stored in the AttributeSets attached to the BranchElements (excluding the root). Logical Styles are stored as the resolving parents of the AttributeSets that hold the paragraph attributes (recall that the Style interface extends AttributeSet). The AttributeSets that hold the character attributes and the logical Style attributes may have resolving parents, but the AttributeSets that hold the paragraph attributes may not (unless there is no logical Style assigned to that paragraph).

Figure 22-9 doesn't show a resolveParent link between the AttributeSet for character attributes and the AttributeSet for paragraph attributes, and in practice there doesn't need to be one. All the Element objects that DefaultStyledDocument creates are subclasses of AbstractDocument.AbstractElement which knows how to climb the Element tree when looking for attribute values. Keep this in mind if you plan to subclass DefaultStyledDocument to create your own Document types.

DefaultStyledDocument sometimes refers to the root as the "section" Element, the middle tier as the "paragraph" Elements, and the leaf tier as the "content" Elements. DefaultStyledDocument always keeps its Element tree structured in three levels like this, but subclasses are free to implement other schemes.

22.3.6.1 Properties

DefaultStyledDocument defines the properties shown in Table 22-25. The default-RootElement is the same as the one described earlier. The styleNames property is an Enumeration with the names of all the named Styles available to this Document.

Table 22-25. DefaultStyledDocument properties

Property

Data type

get

is

set

Default value

defaultRootElemento

Element

·

     

styleNames

Enumeration (the type of each element is String)

·

   

From the StyleContext set in the constructor

ooverridden

See also properties from the AbstractDocument class (Table 22-22).

22.3.6.2 Events

DefaultStyledDocument fires DocumentEvents and UndoableEditEvents when changes are made to the document, just as its superclass does.

22.3.6.3 Constant

DefaultStyledDocument defines the constant shown in Table 22-26.

Table 22-26. DefaultStyledDocument constant

Name

Data type

Description

BUFFER_SIZE_DEFAULT

int

Default size for the GapContent object created by two of DefaultStyledDocument's constructors (value is 4096)

22.3.6.4 Constructors
public DefaultStyledDocument( )

Instantiate a DefaultStyledDocument with a new GapContent and a new (unshared) StyleContext object. If you want to use the default StyleContext to share styles, call a version that lets you pass StyleContext.getDefaultStyleContext( ) instead.

public DefaultStyledDocument(StyleContext context)

Instantiate a DefaultStyledDocument with a new GapContent and the supplied StyleContext.

public DefaultStyledDocument(AbstractDocument.Content content, StyleContext context)

Instantiate a DefaultStyledDocument with the supplied Content model and StyleContext. The context argument is responsible for actually holding the text content of the Document and should be an object of type GapContent.[27] Because the caret is allowed to be placed immediately after the last character of the Document, DefaultStyledDocument expects a newline character (which it calls the "implied break") to be already present in this object when it is passed to its constructor. (The GapContent and StringContent insert this character automatically in their constructors.)

[27] Swing provides another implementation of the AbstractDocument.Content interface called StringContent, but it is inefficient compared to GapContent. The GapContent and StringContent classes are covered later in this chapter.

22.3.6.5 Content style methods

The insertString( ), replace( ), remove( ), and getText( ) methods inherited from AbstractDocument query and alter the document's text content. The following methods query and alter the style attributes assigned to the text content:

public Element getCharacterElement(int offset)

Return the LeafElement that contains the specified document offset. If you're interested only in the character attributes, call getAttributes( ) on the result.

public void setCharacterAttributes(int offset, int length, AttributeSet s, boolean replace)

This method obtains a write lock, applies the supplied attributes to the specified portion of the Document, and fires events to notify listeners of the change. If you want the new set of attributes to completely replace the existing set (instead of augmenting it), use true for the replace argument. Setting character attributes often causes new Elements to be added to the Element tree. (See Figure 22-8 for an illustration of this.)

public Element getParagraphElement(int offset)

Return the BranchElement representing the paragraph that contains the specified document offset (the one on the middle level of the Element tree, not the root). Call getAttributes( ) on the result if you're interested only in the paragraph attributes.

public void setParagraphAttributes(int offset, int length, AttributeSet s, boolean replace)

This method obtains a write lock, applies the given attributes to the paragraphs contained (or partially contained) by the specified range, and fires events to notify listeners of the change. If you want the new set of attributes to completely replace the existing set (instead of augmenting it), use true for the replace argument. Unlike setCharacterAttributes( ), this method does not change the structure of the Element tree.

public Style getLogicalStyle(int offset)

Return the logical Style assigned to the paragraph that contains the specified document offset. This is done by finding the AttributeSet for this offset's paragraph attributes and returning its resolving parent. (If casting the resolveParent to Style fails, it returns null.)

public void setLogicalStyle(int offset, Style s)

This method obtains a write lock, sets the style for the paragraph containing the specified offset, and fires events to notify listeners of the change. This is done by finding the AttributeSet for this offset's paragraph attributes and setting its resolving parent to the given Style. The new Style replaces any previous logical Style (or any previous resolveParent of the paragraph attributes). Note that attributes of the logical Style may be overridden by existing paragraph attributes, so they may have no effect.

22.3.6.6 StyleContext delegation methods

The DefaultStyledDocument has several methods that delegate to the StyleContext established by its constructors. (See Section 22.2.6 for a full description of these methods.)

public Style getStyle(String nm)
public Style addStyle(String nm, Style parent)
public void removeStyle(String nm)

These methods retrieve, create, and delete named Styles available to the Document.

public Color getForeground(AttributeSet attr)
public Color getBackground(AttributeSet attr)
public Font getFont(AttributeSet attr)

These methods are discussed in Section 22.2.6.

22.3.6.7 StyledDocument example

Here's a quick example that manipulates a pane's StyledDocument to draw parentheses, brackets, and braces in different colors. It sets each half of a matching pair to the same color and sets mismatches to red. The interesting work happens in the run( ) method. To keep things simple, it doesn't try to ignore quoted parentheses or those in comments.

// ParenMatcher.java // import javax.swing.*; import javax.swing.text.*; import java.awt.event.*; import java.awt.Color; import java.awt.BorderLayout; // A simple paren matcher public class ParenMatcher extends JTextPane implements Runnable {   public static Color[] matchColor = { Color.blue, Color.magenta, Color.green };   public static Color badColor = Color.red;   private AttributeSet[] matchAttrSet;   private AttributeSet badAttrSet;   public ParenMatcher( ) {     // Create an array of AttributeSets from the array of Colors.     StyleContext sc = StyleContext.getDefaultStyleContext( );     badAttrSet = sc.addAttribute(SimpleAttributeSet.EMPTY,                           StyleConstants.Foreground, badColor);     matchAttrSet = new AttributeSet[matchColor.length];     for (int j=0; j < matchColor.length; j+=1)       matchAttrSet[j] = sc.addAttribute(SimpleAttributeSet.EMPTY,                                  StyleConstants.Foreground, matchColor[j]);   }      // Match and color the parens/brackets/braces.   public void run( ) {     StyledDocument doc = getStyledDocument( );     String text = "";     int len = doc.getLength( );     try {       text = doc.getText(0, len);     } catch (BadLocationException ble) { }     java.util.Stack stack = new java.util.Stack( );     for (int j=0; j < text.length( ); j+=1) {       char ch = text.charAt(j);       if (ch == '(' || ch == '[' || ch == '{') {         int depth = stack.size( );         stack.push(""+ch+j); // Push a String containing the char and the offset.         AttributeSet aset = matchAttrSet[depth % matchAttrSet.length];         doc.setCharacterAttributes(j, 1, aset, false);       }       if (ch == ')' || ch == ']' || ch == '}') {         String peek = stack.empty( ) ? "." : (String)stack.peek( );         if (matches(peek.charAt(0), ch)) { // Does it match?           stack.pop( );           int depth = stack.size( );           AttributeSet aset = matchAttrSet[depth % matchAttrSet.length];           doc.setCharacterAttributes(j, 1, aset, false);         }         else { // Mismatch           doc.setCharacterAttributes(j, 1, badAttrSet, false);         }       }      }            while (! stack.empty( )) { // Anything left in the stack is a mismatch.        String pop = (String)stack.pop( );        int offset = Integer.parseInt(pop.substring(1));        doc.setCharacterAttributes(offset, 1, badAttrSet, false);      }   }   // Unset the foreground color (if any) whenever the user enters text   // (if not for this, text entered after a paren would catch the paren's color).   public void replaceSelection(String content) {     getInputAttributes( ).removeAttribute(StyleConstants.Foreground);     super.replaceSelection(content);   }   // Return true if 'left' and 'right' are matching parens/brackets/braces.   public static boolean matches(char left, char right) {     if (left == '(') return (right == ')');     if (left == '[') return (right == ']');     if (left == '{') return (right == '}');     return false;   }   public static void main(String[] args) {     JFrame frame = new JFrame("ParenMatcher");     final ParenMatcher matcher = new ParenMatcher( );     matcher.setText("int fact(int n) {\n"                    +"  if (n <= 1) return 1;\n"                    +"  return(n * fact(n-1));\n"                    +"}\n");     frame.getContentPane( ).add(new JScrollPane(matcher), BorderLayout.CENTER);     JButton matchButton = new JButton("match parens");     matchButton.addActionListener(new ActionListener( ) {       public void actionPerformed(ActionEvent ae) { matcher.run( ); }     });     frame.getContentPane( ).add(matchButton, BorderLayout.SOUTH);     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);     frame.setSize(200, 150);     frame.setVisible(true);   } } 


Java Swing
Graphic Java 2: Mastering the Jfc, By Geary, 3Rd Edition, Volume 2: Swing
ISBN: 0130796670
EAN: 2147483647
Year: 2001
Pages: 289
Authors: David Geary

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net