FileDocCategorySizeDatePackage
DefaultStyledDocument.javaAPI DocJava SE 5 API77365Fri Aug 26 14:58:14 BST 2005javax.swing.text

DefaultStyledDocument

public class DefaultStyledDocument extends AbstractDocument implements StyledDocument
A document that can be marked up with character and paragraph styles in a manner similar to the Rich Text Format. The element structure for this document represents style crossings for style runs. These style runs are mapped into a paragraph element structure (which may reside in some other structure). The style runs break at paragraph boundaries since logical styles are assigned to paragraph boundaries.

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 JavaBeansTM has been added to the java.beans package. Please see {@link java.beans.XMLEncoder}.

author
Timothy Prinzing
version
1.124 05/05/04
see
Document
see
AbstractDocument

Fields Summary
public static final int
BUFFER_SIZE_DEFAULT
The default size of the initial content buffer.
protected ElementBuffer
buffer
private transient Vector
listeningStyles
Styles listening to.
private transient ChangeListener
styleChangeListener
Listens to Styles.
private transient ChangeListener
styleContextChangeListener
Listens to Styles.
private transient ChangeUpdateRunnable
updateRunnable
Run to create a change event for the document
Constructors Summary
public DefaultStyledDocument(Content c, StyleContext styles)
Constructs a styled document.

param
c the container for the content
param
styles resources and style definitions which may be shared across documents

	super(c, styles);
	listeningStyles = new Vector();
	buffer = new ElementBuffer(createDefaultRoot());
	Style defaultStyle = styles.getStyle(StyleContext.DEFAULT_STYLE);
	setLogicalStyle(0, defaultStyle);
    
public DefaultStyledDocument(StyleContext styles)
Constructs a styled document with the default content storage implementation and a shared set of styles.

param
styles the styles

	this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
    
public DefaultStyledDocument()
Constructs a default styled document. This buffers input content by a size of BUFFER_SIZE_DEFAULT and has a style context that is scoped by the lifetime of the document and is not shared with other documents.

	this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext());
    
Methods Summary
public voidaddDocumentListener(javax.swing.event.DocumentListener listener)
Adds a document listener for notification of any changes.

param
listener the listener
see
Document#addDocumentListener

	synchronized(listeningStyles) {
	    int oldDLCount = listenerList.getListenerCount
		                          (DocumentListener.class);
	    super.addDocumentListener(listener);
	    if (oldDLCount == 0) {
		if (styleContextChangeListener == null) {
		    styleContextChangeListener =
			              createStyleContextChangeListener();
		}
		if (styleContextChangeListener != null) {
		    StyleContext styles = (StyleContext)getAttributeContext();
		    styles.addChangeListener(styleContextChangeListener);
		}
		updateStylesListeningTo();
	    }
	}
    
public javax.swing.text.StyleaddStyle(java.lang.String nm, javax.swing.text.Style parent)
Adds a new style into the logical style hierarchy. Style attributes resolve from bottom up so an attribute specified in a child will override an attribute specified in the parent.

param
nm the name of the style (must be unique within the collection of named styles). The name may be null if the style is unnamed, but the caller is responsible for managing the reference returned as an unnamed style can't be fetched by name. An unnamed style may be useful for things like character attribute overrides such as found in a style run.
param
parent the parent style. This may be null if unspecified attributes need not be resolved in some other style.
return
the style

	StyleContext styles = (StyleContext) getAttributeContext();
	return styles.addStyle(nm, parent);
    
protected voidcreate(javax.swing.text.DefaultStyledDocument$ElementSpec[] data)
Initialize the document to reflect the given element structure (i.e. the structure reported by the getDefaultRootElement method. If the document contained any data it will first be removed.

	try {
	    if (getLength() != 0) {
		remove(0, getLength());
	    }
	    writeLock();

	    // install the content
	    Content c = getContent();
	    int n = data.length;
	    StringBuffer sb = new StringBuffer();
	    for (int i = 0; i < n; i++) {
		ElementSpec es = data[i];
		if (es.getLength() > 0) {
		    sb.append(es.getArray(), es.getOffset(),  es.getLength());
		}
	    }
	    UndoableEdit cEdit = c.insertString(0, sb.toString());

	    // build the event and element structure
	    int length = sb.length();
	    DefaultDocumentEvent evnt = 
		new DefaultDocumentEvent(0, length, DocumentEvent.EventType.INSERT);
	    evnt.addEdit(cEdit);
	    buffer.create(length, data, evnt);

	    // update bidi (possibly)
	    super.insertUpdate(evnt, null);

	    // notify the listeners
	    evnt.end();
	    fireInsertUpdate(evnt);
	    fireUndoableEditUpdate(new UndoableEditEvent(this, evnt));
	} catch (BadLocationException ble) {
	    throw new StateInvariantError("problem initializing");
	} finally {
	    writeUnlock();
	}
	
    
protected AbstractElementcreateDefaultRoot()
Creates the root element to be used to represent the default document structure.

return
the element base

	// grabs a write-lock for this initialization and
	// abandon it during initialization so in normal
	// operation we can detect an illegitimate attempt
	// to mutate attributes.
	writeLock();
	BranchElement section = new SectionElement();
	BranchElement paragraph = new BranchElement(section, null);

	LeafElement brk = new LeafElement(paragraph, null, 0, 1);
	Element[] buff = new Element[1];
	buff[0] = brk;
	paragraph.replace(0, 0, buff);

	buff[0] = paragraph;
	section.replace(0, 0, buff);
	writeUnlock();
	return section;
    
shortcreateSpecsForInsertAfterNewline(javax.swing.text.Element paragraph, javax.swing.text.Element pParagraph, javax.swing.text.AttributeSet pattr, java.util.Vector parseBuffer, int offset, int endOffset)
This is called by insertUpdate when inserting after a new line. It generates, in parseBuffer, ElementSpecs that will position the stack in paragraph.

It returns the direction the last StartSpec should have (this don't necessarily create the last start spec).

	// Need to find the common parent of pParagraph and paragraph.
	if(paragraph.getParentElement() == pParagraph.getParentElement()) {
	    // The simple (and common) case that pParagraph and
	    // paragraph have the same parent.
	    ElementSpec spec = new ElementSpec(pattr, ElementSpec.EndTagType);
	    parseBuffer.addElement(spec);
	    spec = new ElementSpec(pattr, ElementSpec.StartTagType);
	    parseBuffer.addElement(spec);
	    if(pParagraph.getEndOffset() != endOffset)
		return ElementSpec.JoinFractureDirection;

	    Element parent = pParagraph.getParentElement();
	    if((parent.getElementIndex(offset) + 1) < parent.getElementCount())
		return ElementSpec.JoinNextDirection;
	}
	else {
	    // Will only happen for text with more than 2 levels.
	    // Find the common parent of a paragraph and pParagraph
	    Vector leftParents = new Vector();
	    Vector rightParents = new Vector();
	    Element e = pParagraph;
	    while(e != null) {
		leftParents.addElement(e);
		e = e.getParentElement();
	    }
	    e = paragraph;
	    int leftIndex = -1;
	    while(e != null && (leftIndex = leftParents.indexOf(e)) == -1) {
		rightParents.addElement(e);
		e = e.getParentElement();
	    }
	    if(e != null) {
		// e identifies the common parent.
		// Build the ends.
		for(int counter = 0; counter < leftIndex;
		    counter++) {
		    parseBuffer.addElement(new ElementSpec
					      (null, ElementSpec.EndTagType));
		}
		// And the starts.
		ElementSpec spec = null;
		for(int counter = rightParents.size() - 1;
		    counter >= 0; counter--) {
		    spec = new ElementSpec(((Element)rightParents.
				   elementAt(counter)).getAttributes(),
				   ElementSpec.StartTagType);
		    if(counter > 0)
			spec.setDirection(ElementSpec.JoinNextDirection);
		    parseBuffer.addElement(spec);
		}
		// If there are right parents, then we generated starts
		// down the right subtree and there will be an element to
		// join to.
		if(rightParents.size() > 0)
		    return ElementSpec.JoinNextDirection;
		// No right subtree, e.getElement(endOffset) is a
		// leaf. There will be a facture.
		return ElementSpec.JoinFractureDirection;
	    }
	    // else: Could throw an exception here, but should never get here!
	}
	return ElementSpec.OriginateDirection;
    
javax.swing.event.ChangeListenercreateStyleChangeListener()
Returns a new instance of StyleChangeHandler.

	return new StyleChangeHandler();
    
javax.swing.event.ChangeListenercreateStyleContextChangeListener()
Returns a new instance of StyleContextChangeHandler.

	return new StyleContextChangeHandler();
    
public java.awt.ColorgetBackground(javax.swing.text.AttributeSet attr)
Gets the background color from an attribute set.

param
attr the attribute set
return
the color

	StyleContext styles = (StyleContext) getAttributeContext();
	return styles.getBackground(attr);
    
public javax.swing.text.ElementgetCharacterElement(int pos)
Gets a character element based on a position.

param
pos the position in the document >= 0
return
the element

	Element e = null;
	for (e = getDefaultRootElement(); ! e.isLeaf(); ) {
	    int index = e.getElementIndex(pos);
	    e = e.getElement(index);
	}
	return e;
    
public javax.swing.text.ElementgetDefaultRootElement()
Gets the default root element.

return
the root
see
Document#getDefaultRootElement

	return buffer.getRootElement();
    
public java.awt.FontgetFont(javax.swing.text.AttributeSet attr)
Gets the font from an attribute set.

param
attr the attribute set
return
the font

	StyleContext styles = (StyleContext) getAttributeContext();
	return styles.getFont(attr);
    
public java.awt.ColorgetForeground(javax.swing.text.AttributeSet attr)
Gets the foreground color from an attribute set.

param
attr the attribute set
return
the color

	StyleContext styles = (StyleContext) getAttributeContext();
	return styles.getForeground(attr);
    
public javax.swing.text.StylegetLogicalStyle(int p)
Fetches the logical style assigned to the paragraph represented by the given position.

param
p the location to translate to a paragraph and determine the logical style assigned >= 0. This is an offset from the start of the document.
return
the style, null if none

	Style s = null;
	Element paragraph = getParagraphElement(p);
	if (paragraph != null) {
	    AttributeSet a = paragraph.getAttributes();
	    AttributeSet parent = a.getResolveParent();
	    if (parent instanceof Style) {
		s = (Style) parent;
	    }
	}
	return s;
    
public javax.swing.text.ElementgetParagraphElement(int pos)
Gets the paragraph element at the offset pos. A paragraph consists of at least one child Element, which is usually a leaf.

param
pos the starting offset >= 0
return
the element

	Element e = null;
	for (e = getDefaultRootElement(); ! e.isLeaf(); ) {
	    int index = e.getElementIndex(pos);
	    e = e.getElement(index);
	}
	if(e != null)
	    return e.getParentElement();
	return e;
    
public javax.swing.text.StylegetStyle(java.lang.String nm)
Fetches a named style previously added.

param
nm the name of the style
return
the style

	StyleContext styles = (StyleContext) getAttributeContext();
	return styles.getStyle(nm);
    
public java.util.EnumerationgetStyleNames()
Fetches the list of of style names.

return
all the style names

	return ((StyleContext) getAttributeContext()).getStyleNames();
    
protected voidinsert(int offset, javax.swing.text.DefaultStyledDocument$ElementSpec[] data)
Inserts new elements in bulk. This is useful to allow parsing with the document in an unlocked state and prepare an element structure modification. This method takes an array of tokens that describe how to update an element structure so the time within a write lock can be greatly reduced in an asynchronous update situation.

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

param
offset the starting offset >= 0
param
data the element data
exception
BadLocationException for an invalid starting offset

	if (data == null || data.length == 0) {
	    return;
	}

	try {
	    writeLock();

	    // install the content
	    Content c = getContent();
	    int n = data.length;
	    StringBuffer sb = new StringBuffer();
	    for (int i = 0; i < n; i++) {
		ElementSpec es = data[i];
		if (es.getLength() > 0) {
		    sb.append(es.getArray(), es.getOffset(),  es.getLength());
		}
	    }
	    if (sb.length() == 0) {
		// Nothing to insert, bail.
		return;
	    }
	    UndoableEdit cEdit = c.insertString(offset, sb.toString());

	    // create event and build the element structure
	    int length = sb.length();
	    DefaultDocumentEvent evnt = 
		new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.INSERT);
	    evnt.addEdit(cEdit);
	    buffer.insert(offset, length, data, evnt);
	    
	    // update bidi (possibly)
	    super.insertUpdate(evnt, null);

	    // notify the listeners
	    evnt.end();
	    fireInsertUpdate(evnt);
	    fireUndoableEditUpdate(new UndoableEditEvent(this, evnt));
	} finally {
	    writeUnlock();
	}
    
protected voidinsertUpdate(DefaultDocumentEvent chng, javax.swing.text.AttributeSet attr)
Updates document structure as a result of text insertion. This will happen within a write lock. This implementation simply parses the inserted content for line breaks and builds up a set of instructions for the element buffer.

param
chng a description of the document change
param
attr the attributes

	int offset = chng.getOffset();
	int length = chng.getLength();
	if (attr == null) {
	    attr = SimpleAttributeSet.EMPTY;
	}

	// Paragraph attributes should come from point after insertion.
	// You really only notice this when inserting at a paragraph
	// boundary.
	Element paragraph = getParagraphElement(offset + length);
	AttributeSet pattr = paragraph.getAttributes();
	// Character attributes should come from actual insertion point.
	Element pParagraph = getParagraphElement(offset);
	Element run = pParagraph.getElement(pParagraph.getElementIndex
					    (offset));
	int endOffset = offset + length;
	boolean insertingAtBoundry = (run.getEndOffset() == endOffset);
	AttributeSet cattr = run.getAttributes();

	try {
	    Segment s = new Segment();
	    Vector parseBuffer = new Vector();
	    ElementSpec lastStartSpec = null;
	    boolean insertingAfterNewline = false;
	    short lastStartDirection = ElementSpec.OriginateDirection;
	    // Check if the previous character was a newline.
	    if (offset > 0) {
		getText(offset - 1, 1, s);
		if (s.array[s.offset] == '\n") {
		    // Inserting after a newline.
		    insertingAfterNewline = true;
		    lastStartDirection = createSpecsForInsertAfterNewline
			          (paragraph, pParagraph, pattr, parseBuffer,
				   offset, endOffset);
		    for(int counter = parseBuffer.size() - 1; counter >= 0;
			counter--) {
			ElementSpec spec = (ElementSpec)parseBuffer.
			                    elementAt(counter);
			if(spec.getType() == ElementSpec.StartTagType) {
			    lastStartSpec = spec;
			    break;
			}
		    }
		}
	    }
	    // If not inserting after a new line, pull the attributes for
	    // new paragraphs from the paragraph under the insertion point.
	    if(!insertingAfterNewline)
		pattr = pParagraph.getAttributes();

	    getText(offset, length, s);
	    char[] txt = s.array;
	    int n = s.offset + s.count;
	    int lastOffset = s.offset;

	    for (int i = s.offset; i < n; i++) {
		if (txt[i] == '\n") {
		    int breakOffset = i + 1;
		    parseBuffer.addElement(
                        new ElementSpec(attr, ElementSpec.ContentType,
					       breakOffset - lastOffset));
		    parseBuffer.addElement(
                        new ElementSpec(null, ElementSpec.EndTagType));
		    lastStartSpec = new ElementSpec(pattr, ElementSpec.
						   StartTagType);
		    parseBuffer.addElement(lastStartSpec);
		    lastOffset = breakOffset;
		}
	    }
	    if (lastOffset < n) {
		parseBuffer.addElement(
                    new ElementSpec(attr, ElementSpec.ContentType,
					   n - lastOffset));
	    }

	    ElementSpec first = (ElementSpec) parseBuffer.firstElement();

	    int docLength = getLength();

	    // Check for join previous of first content.
	    if(first.getType() == ElementSpec.ContentType &&
	       cattr.isEqual(attr)) {
		first.setDirection(ElementSpec.JoinPreviousDirection);
	    }

	    // Do a join fracture/next for last start spec if necessary.
	    if(lastStartSpec != null) {
		if(insertingAfterNewline) {
		    lastStartSpec.setDirection(lastStartDirection);
		}
		// Join to the fracture if NOT inserting at the end
		// (fracture only happens when not inserting at end of
		// paragraph).
		else if(pParagraph.getEndOffset() != endOffset) {
		    lastStartSpec.setDirection(ElementSpec.
					       JoinFractureDirection);
		}
		// Join to next if parent of pParagraph has another
		// element after pParagraph, and it isn't a leaf.
		else {
		    Element parent = pParagraph.getParentElement();
		    int pParagraphIndex = parent.getElementIndex(offset);
		    if((pParagraphIndex + 1) < parent.getElementCount() &&
		       !parent.getElement(pParagraphIndex + 1).isLeaf()) {
			lastStartSpec.setDirection(ElementSpec.
						   JoinNextDirection);
		    }
		}
	    }

	    // Do a JoinNext for last spec if it is content, it doesn't
	    // already have a direction set, no new paragraphs have been
	    // inserted or a new paragraph has been inserted and its join
	    // direction isn't originate, and the element at endOffset 
	    // is a leaf.
	    if(insertingAtBoundry && endOffset < docLength) {
		ElementSpec last = (ElementSpec) parseBuffer.lastElement();
		if(last.getType() == ElementSpec.ContentType &&
		   last.getDirection() != ElementSpec.JoinPreviousDirection &&
		   ((lastStartSpec == null && (paragraph == pParagraph ||
					       insertingAfterNewline)) ||
		    (lastStartSpec != null && lastStartSpec.getDirection() !=
		     ElementSpec.OriginateDirection))) {
		    Element nextRun = paragraph.getElement(paragraph.
					   getElementIndex(endOffset));
		    // Don't try joining to a branch!
		    if(nextRun.isLeaf() &&
		       attr.isEqual(nextRun.getAttributes())) {
			last.setDirection(ElementSpec.JoinNextDirection);
		    }
		}
	    }
	    // If not inserting at boundary and there is going to be a
	    // fracture, then can join next on last content if cattr
	    // matches the new attributes.
	    else if(!insertingAtBoundry && lastStartSpec != null &&
		    lastStartSpec.getDirection() ==
		    ElementSpec.JoinFractureDirection) {
		ElementSpec last = (ElementSpec) parseBuffer.lastElement();
		if(last.getType() == ElementSpec.ContentType &&
		   last.getDirection() != ElementSpec.JoinPreviousDirection &&
		   attr.isEqual(cattr)) {
		    last.setDirection(ElementSpec.JoinNextDirection);
		}
	    }

	    // Check for the composed text element. If it is, merge the character attributes
	    // into this element as well.
	    if (Utilities.isComposedTextAttributeDefined(attr)) {
	        ((MutableAttributeSet)attr).addAttributes(cattr);
	        ((MutableAttributeSet)attr).addAttribute(AbstractDocument.ElementNameAttribute, 
		                                         AbstractDocument.ContentElementName);
	    }

	    ElementSpec[] spec = new ElementSpec[parseBuffer.size()];
	    parseBuffer.copyInto(spec);
	    buffer.insert(offset, length, spec, chng);
	} catch (BadLocationException bl) {
	}

        super.insertUpdate( chng, attr );
    
private voidreadObject(java.io.ObjectInputStream s)

	listeningStyles = new Vector();
	s.defaultReadObject();
	// Reinstall style listeners.
	if (styleContextChangeListener == null &&
	    listenerList.getListenerCount(DocumentListener.class) > 0) {
	    styleContextChangeListener = createStyleContextChangeListener();
	    if (styleContextChangeListener != null) {
		StyleContext styles = (StyleContext)getAttributeContext();
		styles.addChangeListener(styleContextChangeListener);
	    }
	    updateStylesListeningTo();
	}
    
public voidremoveDocumentListener(javax.swing.event.DocumentListener listener)
Removes a document listener.

param
listener the listener
see
Document#removeDocumentListener

	synchronized(listeningStyles) {
	    super.removeDocumentListener(listener);
	    if (listenerList.getListenerCount(DocumentListener.class) == 0) {
		for (int counter = listeningStyles.size() - 1; counter >= 0;
		     counter--) {
		    ((Style)listeningStyles.elementAt(counter)).
			            removeChangeListener(styleChangeListener);
		}
		listeningStyles.removeAllElements();
		if (styleContextChangeListener != null) {
		    StyleContext styles = (StyleContext)getAttributeContext();
		    styles.removeChangeListener(styleContextChangeListener);
		}
	    }
	}
    
public voidremoveStyle(java.lang.String nm)
Removes a named style previously added to the document.

param
nm the name of the style to remove

	StyleContext styles = (StyleContext) getAttributeContext();
	styles.removeStyle(nm);
    
protected voidremoveUpdate(DefaultDocumentEvent chng)
Updates document structure as a result of text removal.

param
chng a description of the document change

        super.removeUpdate(chng);
	buffer.remove(chng.getOffset(), chng.getLength(), chng);
    
public voidsetCharacterAttributes(int offset, int length, javax.swing.text.AttributeSet s, boolean replace)
Sets attributes for some part of the document. A write lock is held by this operation while changes are being made, and a DocumentEvent is sent to the listeners after the change has been successfully completed.

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

param
offset the offset in the document >= 0
param
length the length >= 0
param
s the attributes
param
replace true if the previous attributes should be replaced before setting the new attributes

        if (length == 0) {
            return;
        }
	try {
	    writeLock();
	    DefaultDocumentEvent changes = 
		new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);

	    // split elements that need it
	    buffer.change(offset, length, changes);

	    AttributeSet sCopy = s.copyAttributes();

	    // PENDING(prinz) - this isn't a very efficient way to iterate
	    int lastEnd = Integer.MAX_VALUE;
	    for (int pos = offset; pos < (offset + length); pos = lastEnd) {
		Element run = getCharacterElement(pos);
		lastEnd = run.getEndOffset();
                if (pos == lastEnd) {
                    // offset + length beyond length of document, bail.
                    break;
                }
		MutableAttributeSet attr = (MutableAttributeSet) run.getAttributes();
		changes.addEdit(new AttributeUndoableEdit(run, sCopy, replace));
		if (replace) {
		    attr.removeAttributes(attr);
		}
		attr.addAttributes(s);
	    }
	    changes.end();
	    fireChangedUpdate(changes);
	    fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
	} finally {
	    writeUnlock();
	}

    
public voidsetLogicalStyle(int pos, javax.swing.text.Style s)
Sets the logical style to use for the paragraph at the given position. If attributes aren't explicitly set for character and paragraph attributes they will resolve through the logical style assigned to the paragraph, which in turn may resolve through some hierarchy completely independent of the element hierarchy in the document.

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

param
pos the offset from the start of the document >= 0
param
s the logical style to assign to the paragraph, null if none

	Element paragraph = getParagraphElement(pos);
	if ((paragraph != null) && (paragraph instanceof AbstractElement)) {
	    try {
		writeLock();
		StyleChangeUndoableEdit edit = new StyleChangeUndoableEdit((AbstractElement)paragraph, s);
		((AbstractElement)paragraph).setResolveParent(s);
		int p0 = paragraph.getStartOffset();
		int p1 = paragraph.getEndOffset();
		DefaultDocumentEvent e = 
		  new DefaultDocumentEvent(p0, p1 - p0, DocumentEvent.EventType.CHANGE);
		e.addEdit(edit);
		e.end();
		fireChangedUpdate(e);
		fireUndoableEditUpdate(new UndoableEditEvent(this, e));
	    } finally {
		writeUnlock();
	    }
	}
    
public voidsetParagraphAttributes(int offset, int length, javax.swing.text.AttributeSet s, boolean replace)
Sets attributes for a paragraph.

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

param
offset the offset into the paragraph >= 0
param
length the number of characters affected >= 0
param
s the attributes
param
replace whether to replace existing attributes, or merge them

	try {
	    writeLock();
	    DefaultDocumentEvent changes = 
		new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);

	    AttributeSet sCopy = s.copyAttributes();

	    // PENDING(prinz) - this assumes a particular element structure
	    Element section = getDefaultRootElement();
	    int index0 = section.getElementIndex(offset);
	    int index1 = section.getElementIndex(offset + ((length > 0) ? length - 1 : 0));
            boolean isI18N = Boolean.TRUE.equals(getProperty(I18NProperty));
            boolean hasRuns = false;
	    for (int i = index0; i <= index1; i++) {
		Element paragraph = section.getElement(i);
		MutableAttributeSet attr = (MutableAttributeSet) paragraph.getAttributes();
		changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
		if (replace) {
		    attr.removeAttributes(attr);
		}
		attr.addAttributes(s);
                if (isI18N && !hasRuns) {
                    hasRuns = (attr.getAttribute(TextAttribute.RUN_DIRECTION) != null);
                }
	    }

            if (hasRuns) {
                updateBidi( changes );
            }

	    changes.end();
	    fireChangedUpdate(changes);
	    fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
	} finally {
	    writeUnlock();
	}
    
protected voidstyleChanged(javax.swing.text.Style style)
Called when any of this document's styles have changed. Subclasses may wish to be intelligent about what gets damaged.

param
style The Style that has changed.

        // Only propagate change updated if have content
        if (getLength() != 0) {
            // lazily create a ChangeUpdateRunnable
            if (updateRunnable == null) {
                updateRunnable = new ChangeUpdateRunnable();
            }
            
            // We may get a whole batch of these at once, so only
            // queue the runnable if it is not already pending
            synchronized(updateRunnable) {
                if (!updateRunnable.isPending) {
                    SwingUtilities.invokeLater(updateRunnable);
                    updateRunnable.isPending = true;
                }
            }
        }
    
voidupdateStylesListeningTo()
Adds a ChangeListener to new styles, and removes ChangeListener from old styles.

	synchronized(listeningStyles) {
	    StyleContext styles = (StyleContext)getAttributeContext();
	    if (styleChangeListener == null) {
		styleChangeListener = createStyleChangeListener();
	    }
	    if (styleChangeListener != null && styles != null) {
		Enumeration styleNames = styles.getStyleNames();
		Vector v = (Vector)listeningStyles.clone();
		listeningStyles.removeAllElements();
		while (styleNames.hasMoreElements()) {
		    String name = (String)styleNames.nextElement();
		    Style aStyle = styles.getStyle(name);
		    int index = v.indexOf(aStyle);
		    listeningStyles.addElement(aStyle);
		    if (index == -1) {
			aStyle.addChangeListener(styleChangeListener);
		    }
		    else {
			v.removeElementAt(index);
		    }
		}
		for (int counter = v.size() - 1; counter >= 0; counter--) {
		    Style aStyle = (Style)v.elementAt(counter);
		    aStyle.removeChangeListener(styleChangeListener);
		}
		if (listeningStyles.size() == 0) {
		    styleChangeListener = null;
		}
	    }
	}