Here comes some locking fixes and an optimization for AbstractDocument
that I did some days ago.
2006-08-09 Roman Kennke <[EMAIL PROTECTED]>
* javax/swing/text/AbstractDocument.java
(insertString): Perform modifications inside a write lock.
(insertStringImpl): Don't lock here. This is already done
in insertString().
(replace): Perform modifications inside a write lock.
(AbstractElement.AbstractElement): Call addAttributes() to
add the attributes.
(AbstractElement.getName): Fetch name from the ElementNameAttibute.
(BranchElement.lastIndex): New field. Optimizes getElementIndex().
(BranchElement.BranchElement): Set lastIndex to -1.
(BranchElement.getElementIndex): Implemented more efficient
search.
/Roman
Index: javax/swing/text/AbstractDocument.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/AbstractDocument.java,v
retrieving revision 1.60
diff -u -1 -2 -r1.60 AbstractDocument.java
--- javax/swing/text/AbstractDocument.java 27 Jul 2006 15:39:41 -0000 1.60
+++ javax/swing/text/AbstractDocument.java 9 Aug 2006 15:14:29 -0000
@@ -565,58 +565,59 @@
* @param text the content to be inserted
* @param attributes the text attributes to be assigned to that string
*
* @throws BadLocationException if <code>offset</code> is not a valid
* location in this <code>Document</code>
*/
public void insertString(int offset, String text, AttributeSet attributes)
throws BadLocationException
{
// Bail out if we have a bogus insertion (Behavior observed in RI).
if (text == null || text.length() == 0)
return;
-
- if (documentFilter == null)
- insertStringImpl(offset, text, attributes);
- else
- documentFilter.insertString(getBypass(), offset, text, attributes);
+
+ writeLock();
+ try
+ {
+ if (documentFilter == null)
+ insertStringImpl(offset, text, attributes);
+ else
+ documentFilter.insertString(getBypass(), offset, text, attributes);
+ }
+ finally
+ {
+ writeUnlock();
+ }
}
void insertStringImpl(int offset, String text, AttributeSet attributes)
throws BadLocationException
{
// Just return when no text to insert was given.
if (text == null || text.length() == 0)
return;
DefaultDocumentEvent event =
new DefaultDocumentEvent(offset, text.length(),
DocumentEvent.EventType.INSERT);
- try
- {
- writeLock();
- UndoableEdit undo = content.insertString(offset, text);
- if (undo != null)
- event.addEdit(undo);
-
- insertUpdate(event, attributes);
-
- fireInsertUpdate(event);
- if (undo != null)
- fireUndoableEditUpdate(new UndoableEditEvent(this, undo));
- }
- finally
- {
- writeUnlock();
- }
+ UndoableEdit undo = content.insertString(offset, text);
+ if (undo != null)
+ event.addEdit(undo);
+
+ insertUpdate(event, attributes);
+
+ fireInsertUpdate(event);
+
+ if (undo != null)
+ fireUndoableEditUpdate(new UndoableEditEvent(this, undo));
}
/**
* Called to indicate that text has been inserted into this
* <code>Document</code>. The default implementation does nothing.
* This method is executed within a write lock.
*
* @param chng the <code>DefaultDocumentEvent</code> describing the change
* @param attr the attributes of the changed content
*/
protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr)
{
@@ -805,39 +806,46 @@
* document
*
* @since 1.4
*/
public void replace(int offset, int length, String text,
AttributeSet attributes)
throws BadLocationException
{
// Bail out if we have a bogus replacement (Behavior observed in RI).
if (length == 0
&& (text == null || text.length() == 0))
return;
-
- if (documentFilter == null)
+
+ writeLock();
+ try
{
- // It is important to call the methods which again do the checks
- // of the arguments and the DocumentFilter because subclasses may
- // have overridden these methods and provide crucial behavior
- // which would be skipped if we call the non-checking variants.
- // An example for this is PlainDocument where insertString can
- // provide a filtering of newlines.
- remove(offset, length);
- insertString(offset, text, attributes);
+ if (documentFilter == null)
+ {
+ // It is important to call the methods which again do the checks
+ // of the arguments and the DocumentFilter because subclasses may
+ // have overridden these methods and provide crucial behavior
+ // which would be skipped if we call the non-checking variants.
+ // An example for this is PlainDocument where insertString can
+ // provide a filtering of newlines.
+ remove(offset, length);
+ insertString(offset, text, attributes);
+ }
+ else
+ documentFilter.replace(getBypass(), offset, length, text, attributes);
+ }
+ finally
+ {
+ writeUnlock();
}
- else
- documentFilter.replace(getBypass(), offset, length, text, attributes);
-
}
void replaceImpl(int offset, int length, String text,
AttributeSet attributes)
throws BadLocationException
{
removeImpl(offset, length);
insertStringImpl(offset, text, attributes);
}
/**
* Adds a <code>DocumentListener</code> object to this document.
@@ -1246,25 +1254,25 @@
* specified parent <code>Element</code> and <code>AttributeSet</code>.
*
* @param p the parent of this <code>AbstractElement</code>
* @param s the attributes to be assigned to this
* <code>AbstractElement</code>
*/
public AbstractElement(Element p, AttributeSet s)
{
element_parent = p;
AttributeContext ctx = getAttributeContext();
attributes = ctx.getEmptySet();
if (s != null)
- attributes = ctx.addAttributes(attributes, s);
+ addAttributes(s);
}
/**
* Returns the child nodes of this <code>Element</code> as an
* <code>Enumeration</code> of [EMAIL PROTECTED] TreeNode}s.
*
* @return the child nodes of this <code>Element</code> as an
* <code>Enumeration</code> of [EMAIL PROTECTED] TreeNode}s
*/
public abstract Enumeration children();
/**
@@ -1558,25 +1566,25 @@
*
* @return the requested element
*/
public abstract Element getElement(int index);
/**
* Returns the name of this element.
*
* @return the name of this element
*/
public String getName()
{
- return (String) getAttribute(NameAttribute);
+ return (String) attributes.getAttribute(ElementNameAttribute);
}
/**
* Returns the parent element of this element.
*
* @return the parent element of this element
*/
public Element getParentElement()
{
return element_parent;
}
@@ -1696,36 +1704,42 @@
/**
* The child elements of this BranchElement.
*/
private Element[] children;;
/**
* The number of children in the branch element.
*/
private int numChildren;
/**
+ * The last found index in getElementIndex(). Used for faster searching.
+ */
+ private int lastIndex;
+
+ /**
* Creates a new <code>BranchElement</code> with the specified
* parent and attributes.
*
* @param parent the parent element of this <code>BranchElement</code>
* @param attributes the attributes to set on this
* <code>BranchElement</code>
*/
public BranchElement(Element parent, AttributeSet attributes)
{
super(parent, attributes);
children = new Element[1];
numChildren = 0;
+ lastIndex = -1;
}
/**
* Returns the children of this <code>BranchElement</code>.
*
* @return the children of this <code>BranchElement</code>
*/
public Enumeration children()
{
if (children.length == 0)
return null;
@@ -1776,53 +1790,91 @@
/**
* Returns the index of the child element that spans the specified
* offset in the document model.
*
* @param offset the offset for which the responsible element is searched
*
* @return the index of the child element that spans the specified
* offset in the document model
*/
public int getElementIndex(int offset)
{
- // If offset is less than the start offset of our first child,
- // return 0
- if (offset < getStartOffset())
- return 0;
-
- // XXX: There is surely a better algorithm
- // as beginning from first element each time.
- for (int index = 0; index < numChildren - 1; ++index)
+ // Implemented using an improved linear search.
+ // This makes use of the fact that searches are not random but often
+ // close to the previous search. So we try to start the binary
+ // search at the last found index.
+
+ int i0 = 0; // The lower bounds.
+ int i1 = numChildren - 1; // The upper bounds.
+ int index = -1; // The found index.
+
+ int p0 = getStartOffset();
+ int p1; // Start and end offset local variables.
+
+ if (numChildren == 0)
+ index = 0;
+ else if (offset >= getEndOffset())
+ index = numChildren - 1;
+ else
{
- Element elem = children[index];
+ // Try lastIndex.
+ if (lastIndex >= i0 && lastIndex <= i1)
+ {
+ Element last = getElement(lastIndex);
+ p0 = last.getStartOffset();
+ p1 = last.getEndOffset();
+ if (offset >= p0 && offset < p1)
+ index = lastIndex;
+ else
+ {
+ // Narrow the search bounds using the lastIndex, even
+ // if it hasn't been a hit.
+ if (offset < p0)
+ i1 = lastIndex;
+ else
+ i0 = lastIndex;
+ }
+ }
+ // The actual search.
+ int i = 0;
+ while (i0 <= i1 && index == -1)
+ {
+ i = i0 + (i1 - i0) / 2;
+ Element el = getElement(i);
+ p0 = el.getStartOffset();
+ p1 = el.getEndOffset();
+ if (offset >= p0 && offset < p1)
+ {
+ // Found it!
+ index = i;
+ }
+ else if (offset < p0)
+ i1 = i - 1;
+ else
+ i0 = i + 1;
+ }
- if ((elem.getStartOffset() <= offset)
- && (offset < elem.getEndOffset()))
- return index;
- // If the next element's start offset is greater than offset
- // then we have to return the closest Element, since no Elements
- // will contain the offset
- if (children[index + 1].getStartOffset() > offset)
+ if (index == -1)
{
- if ((offset - elem.getEndOffset()) > (children[index + 1].getStartOffset() - offset))
- return index + 1;
+ // Didn't find it. Return the boundary index.
+ if (offset < p0)
+ index = i;
else
- return index;
+ index = i + 1;
}
- }
- // If offset is greater than the index of the last element, return
- // the index of the last element.
- return getElementCount() - 1;
+ lastIndex = index;
+ }
+ return index;
}
/**
* Returns the offset inside the document model that is after the last
* character of this element.
* This is the end offset of the last child element. If this element
* has no children, this method throws a <code>NullPointerException</code>.
*
* @return the offset inside the document model that is after the last
* character of this element
*
* @throws NullPointerException if this branch element has no children