Index: javax/swing/text/AbstractDocument.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/text/AbstractDocument.java,v retrieving revision 1.39 diff -u -r1.39 AbstractDocument.java --- javax/swing/text/AbstractDocument.java 17 Nov 2005 20:31:51 -0000 1.39 +++ javax/swing/text/AbstractDocument.java 23 Nov 2005 14:11:37 -0000 @@ -544,7 +544,8 @@ insertUpdate(event, attributes); writeUnlock(); - fireInsertUpdate(event); + if (event.modified) + fireInsertUpdate(event); if (undo != null) fireUndoableEditUpdate(new UndoableEditEvent(this, undo)); } @@ -1809,6 +1810,12 @@ Hashtable changes; /** + * Indicates if this event has been modified or not. This is used to + * determine if this event is thrown. + */ + boolean modified; + + /** * Creates a new DefaultDocumentEvent. * * @param offset the starting offset of the change @@ -1822,6 +1829,7 @@ this.length = length; this.type = type; changes = new Hashtable(); + modified = false; } /** @@ -1836,6 +1844,7 @@ // XXX - Fully qualify ElementChange to work around gcj bug #2499. if (edit instanceof DocumentEvent.ElementChange) { + modified = true; DocumentEvent.ElementChange elEdit = (DocumentEvent.ElementChange) edit; changes.put(elEdit.getElement(), elEdit); Index: javax/swing/text/DefaultStyledDocument.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/text/DefaultStyledDocument.java,v retrieving revision 1.17 diff -u -r1.17 DefaultStyledDocument.java --- javax/swing/text/DefaultStyledDocument.java 8 Nov 2005 16:43:29 -0000 1.17 +++ javax/swing/text/DefaultStyledDocument.java 23 Nov 2005 14:11:38 -0000 @@ -42,6 +42,7 @@ import java.awt.Font; import java.io.Serializable; import java.util.Enumeration; +import java.util.Stack; import java.util.Vector; import javax.swing.event.ChangeEvent; @@ -429,6 +430,26 @@ private int length; /** + * The number of inserted end tags. This is a counter which always gets + * incremented when an end tag is inserted. This is evaluated before + * content insertion to go up the element stack. + */ + private int numEndTags; + + /** + * The number of inserted start tags. This is a counter which always gets + * incremented when an end tag is inserted. This is evaluated before + * content insertion to go up the element stack. + */ + private int numStartTags; + + /** + * The current position in the element tree. This is used for bulk inserts + * using ElementSpecs. + */ + private Stack elementStack; + + /** * Holds fractured elements during insertion of end and start tags. * Inserting an end tag may lead to fracturing of the current paragraph * element. The elements that have been cut off may be added to the @@ -450,6 +471,7 @@ public ElementBuffer(Element root) { this.root = root; + elementStack = new Stack(); } /** @@ -493,11 +515,50 @@ { // Split up the element at the start offset if necessary. Element el = getCharacterElement(offset); - split(el, offset); + Element[] res = split(el, offset, 0); + BranchElement par = (BranchElement) el.getParentElement(); + if (res[1] != null) + { + int index = par.getElementIndex(offset); + Element[] removed; + Element[] added; + if (res[0] == null) + { + removed = new Element[0]; + added = new Element[]{ res[1] }; + index++; + } + else + { + removed = new Element[]{ el }; + added = new Element[]{ res[0], res[1] }; + } + par.replace(index, removed.length, added); + addEdit(par, index, removed, added); + } int endOffset = offset + length; el = getCharacterElement(endOffset); - split(el, endOffset); + res = split(el, endOffset, 0); + par = (BranchElement) el.getParentElement(); + if (res[1] != null) + { + int index = par.getElementIndex(offset); + Element[] removed; + Element[] added; + if (res[1] == null) + { + removed = new Element[0]; + added = new Element[]{ res[1] }; + } + else + { + removed = new Element[]{ el }; + added = new Element[]{ res[0], res[1] }; + } + par.replace(index, removed.length, added); + addEdit(par, index, removed, added); + } } /** @@ -505,42 +566,96 @@ * * @param el the Element to possibly split * @param offset the offset at which to possibly split - */ - void split(Element el, int offset) - { - if (el instanceof AbstractElement) + * @param space the amount of space to create between the splitted parts + * + * @return An array of elements which represent the split result. This + * array has two elements, the two parts of the split. The first + * element might be null, which means that the element which should + * be splitted can remain in place. The second element might also + * be null, which means that the offset is already at an element + * boundary and the element doesn't need to be splitted. + * + */ + private Element[] split(Element el, int offset, int space) + { + // If we are at an element boundary, then return an empty array. + if ((offset == el.getStartOffset() || offset == el.getEndOffset()) + && space == 0 && el.isLeaf()) + return new Element[2]; + + // If the element is an instance of BranchElement, then we recursivly + // call this method to perform the split. + Element[] res = new Element[2]; + if (el instanceof BranchElement) { - AbstractElement ael = (AbstractElement) el; - int startOffset = ael.getStartOffset(); - int endOffset = ael.getEndOffset(); - int len = endOffset - startOffset; - if (startOffset != offset && endOffset != offset) + int index = el.getElementIndex(offset); + Element child = el.getElement(index); + Element[] result = split(child, offset, space); + Element[] removed; + Element[] added; + Element[] newAdded; + + int count = el.getElementCount(); + if (!(result[1] == null)) { - Element paragraph = ael.getParentElement(); - if (paragraph instanceof BranchElement) + // This is the case when we can keep the first element. + if (result[0] == null) { - BranchElement par = (BranchElement) paragraph; - Element child1 = createLeafElement(par, ael, startOffset, - offset); - Element child2 = createLeafElement(par, ael, offset, - endOffset); - int index = par.getElementIndex(startOffset); - Element[] add = new Element[]{ child1, child2 }; - par.replace(index, 1, add); - documentEvent.addEdit(new ElementEdit(par, index, - new Element[]{ el }, - add)); + removed = new Element[count - index - 1]; + newAdded = new Element[count - index - 1]; + added = new Element[]{}; } + // This is the case when we may not keep the first element. else - throw new AssertionError("paragraph elements are expected to " - + "be instances of " - + "javax.swing.text.AbstractDocument.BranchElement"); + { + removed = new Element[count - index]; + newAdded = new Element[count - index]; + added = new Element[]{result[0]}; + } + newAdded[0] = result[1]; + for (int i = index; i < count; i++) + { + Element el2 = el.getElement(i); + int ind = i - count + removed.length; + removed[ind] = el2; + if (ind != 0) + newAdded[ind] = el2; + } + + ((BranchElement) el).replace(index, removed.length, added); + addEdit(el, index, removed, added); + BranchElement newPar = + (BranchElement) createBranchElement(el.getParentElement(), + el.getAttributes()); + newPar.replace(0, 0, newAdded); + res = new Element[]{ null, newPar }; + } + else + { + removed = new Element[count - index]; + for (int i = index; i < count; ++i) + removed[i - index] = el.getElement(i); + added = new Element[0]; + ((BranchElement) el).replace(index, removed.length, + added); + addEdit(el, index, removed, added); + BranchElement newPar = + (BranchElement) createBranchElement(el.getParentElement(), + el.getAttributes()); + newPar.replace(0, 0, removed); + res = new Element[]{ null, newPar }; } } - else - throw new AssertionError("content elements are expected to be " - + "instances of " - + "javax.swing.text.AbstractDocument.AbstractElement"); + else if (el instanceof LeafElement) + { + BranchElement par = (BranchElement) el.getParentElement(); + Element el1 = createLeafElement(par, el.getAttributes(), + el.getStartOffset(), offset); + Element el2 = createLeafElement(par, el.getAttributes(), + offset + space, el.getEndOffset()); + res = new Element[]{ el1, el2 }; + } + return res; } /** @@ -563,6 +678,12 @@ this.offset = offset; this.length = length; documentEvent = ev; + // Push the root and the paragraph at offset onto the element stack. + elementStack.clear(); + elementStack.push(root); + elementStack.push(root.getElement(root.getElementIndex(offset))); + numEndTags = 0; + numStartTags = 0; insertUpdate(data); } @@ -581,93 +702,102 @@ switch (data[i].getType()) { case ElementSpec.StartTagType: - insertStartTag(data[i]); + numStartTags++; break; case ElementSpec.EndTagType: - insertEndTag(data[i]); + numEndTags++; break; default: insertContentTag(data[i]); break; } } + endEdit(); } /** - * Insert a new paragraph after the paragraph at the current position. - * - * @param tag the element spec that describes the element to be inserted + * Finishes an insertion by possibly evaluating the outstanding start and + * end tags. However, this is only performed if the event has received any + * modifications. */ - void insertStartTag(ElementSpec tag) + private void endEdit() { - BranchElement root = (BranchElement) getDefaultRootElement(); - int index = root.getElementIndex(offset); - if (index == -1) - index = 0; - - BranchElement newParagraph = - (BranchElement) createBranchElement(root, tag.getAttributes()); - newParagraph.setResolveParent(getStyle(StyleContext.DEFAULT_STYLE)); - - // Add new paragraph into document structure. - Element[] added = new Element[]{newParagraph}; - root.replace(index + 1, 0, added); - ElementEdit edit = new ElementEdit(root, index + 1, new Element[0], - added); - documentEvent.addEdit(edit); + if (documentEvent.modified) + prepareContentInsertion(); + } - // Maybe add fractured elements. - if (tag.getDirection() == ElementSpec.JoinFractureDirection) + /** + * Evaluates the number of inserted end tags and performs the corresponding + * structural changes. + */ + private void prepareContentInsertion() + { + while (numEndTags > 0) { - Element[] newFracture = new Element[fracture.length]; - for (int i = 0; i < fracture.length; i++) - { - Element oldLeaf = fracture[i]; - Element newLeaf = createLeafElement(newParagraph, - oldLeaf.getAttributes(), - oldLeaf.getStartOffset(), - oldLeaf.getEndOffset()); - newFracture[i] = newLeaf; - } - newParagraph.replace(0, 0, newFracture); - edit = new ElementEdit(newParagraph, 0, new Element[0], - fracture); - documentEvent.addEdit(edit); - fracture = new Element[0]; + elementStack.pop(); + numEndTags--; + } + + while (numStartTags > 0) + { + Element current = (Element) elementStack.peek(); + Element newParagraph = + insertParagraph((BranchElement) current, offset); + elementStack.push(newParagraph); + numStartTags--; } } - /** - * Inserts an end tag into the document structure. This cuts of the - * current paragraph element, possibly fracturing it's child elements. - * The fractured elements are saved so that they can be joined later - * with a new paragraph element. - */ - void insertEndTag(ElementSpec tag) - { - BranchElement root = (BranchElement) getDefaultRootElement(); - int parIndex = root.getElementIndex(offset); - BranchElement paragraph = (BranchElement) root.getElement(parIndex); - - int index = paragraph.getElementIndex(offset); - LeafElement content = (LeafElement) paragraph.getElement(index); - // We might have to split the element at offset. - split(content, offset); - index = paragraph.getElementIndex(offset); - - int count = paragraph.getElementCount(); - // Store fractured elements. - fracture = new Element[count - index]; - for (int i = index; i < count; ++i) - fracture[i - index] = paragraph.getElement(i); - - // Delete fractured elements. - paragraph.replace(index, count - index, new Element[0]); - - // Add this action to the document event. - ElementEdit edit = new ElementEdit(paragraph, index, fracture, - new Element[0]); - documentEvent.addEdit(edit); + private Element insertParagraph(BranchElement par, int offset) + { + Element current = par.getElement(par.getElementIndex(offset)); + Element[] res = split(current, offset, 0); + int index = par.getElementIndex(offset); + Element ret; + if (res[1] != null) + { + Element[] removed; + Element[] added; + if (res[0] == null) + { + removed = new Element[0]; + if (res[1] instanceof BranchElement) + { + added = new Element[]{ res[1] }; + ret = res[1]; + } + else + { + ret = createBranchElement(par, null); + added = new Element[]{ ret, res[1] }; + } + index++; + } + else + { + removed = new Element[]{ current }; + if (res[1] instanceof BranchElement) + { + ret = res[1]; + added = new Element[]{ res[0], res[1] }; + } + else + { + ret = createBranchElement(par, null); + added = new Element[]{ res[0], ret, res[1] }; + } + } + par.replace(index, removed.length, added); + addEdit(par, index, removed, added); + } + else + { + ret = createBranchElement(par, null); + Element[] added = new Element[]{ ret }; + par.replace(index, 0, added); + addEdit(par, index, new Element[0], added); + } + return ret; } /** @@ -675,100 +805,87 @@ * * @param tag the element spec */ - void insertContentTag(ElementSpec tag) + private void insertContentTag(ElementSpec tag) { + prepareContentInsertion(); int len = tag.getLength(); int dir = tag.getDirection(); if (dir == ElementSpec.JoinPreviousDirection) { - Element prev = getCharacterElement(offset); - BranchElement prevParent = (BranchElement) prev.getParentElement(); - Element join = createLeafElement(prevParent, tag.getAttributes(), - prev.getStartOffset(), - Math.max(prev.getEndOffset(), - offset + len)); - int ind = prevParent.getElementIndex(offset); - if (ind == -1) - ind = 0; - Element[] add = new Element[]{join}; - prevParent.replace(ind, 1, add); - - // Add this action to the document event. - ElementEdit edit = new ElementEdit(prevParent, ind, - new Element[]{prev}, add); - documentEvent.addEdit(edit); + // The mauve tests to this class show that a JoinPrevious insertion + // does not add any edits to the document event. To me this means + // that nothing is done here. The previous element naturally should + // expand so that it covers the new characters. } else if (dir == ElementSpec.JoinNextDirection) { - Element next = getCharacterElement(offset + len); - BranchElement nextParent = (BranchElement) next.getParentElement(); - Element join = createLeafElement(nextParent, tag.getAttributes(), - offset, - next.getEndOffset()); - int ind = nextParent.getElementIndex(offset + len); - if (ind == -1) - ind = 0; - Element[] add = new Element[]{join}; - nextParent.replace(ind, 1, add); + BranchElement paragraph = (BranchElement) elementStack.peek(); + int currentIndex = paragraph.getElementIndex(offset); + Element current = paragraph.getElement(currentIndex); + Element next = paragraph.getElement(currentIndex + 1); + + Element newEl1 = createLeafElement(paragraph, + current.getAttributes(), + current.getStartOffset(), + offset); + Element newEl2 = createLeafElement(paragraph, + current.getAttributes(), + offset, + next.getEndOffset()); + + Element[] add = new Element[] {newEl1, newEl2}; + Element[] remove = new Element[] {current, next}; + paragraph.replace(currentIndex, 2, add); // Add this action to the document event. - ElementEdit edit = new ElementEdit(nextParent, ind, - new Element[]{next}, add); - documentEvent.addEdit(edit); + addEdit(paragraph, currentIndex, remove, add); } else { - BranchElement par = (BranchElement) getParagraphElement(offset); - - int ind = par.getElementIndex(offset); - - // Make room for the element. - // Cut previous element. - Element prev = par.getElement(ind); - if (prev != null && prev.getStartOffset() < offset) + BranchElement paragraph = (BranchElement) elementStack.peek(); + int index = paragraph.getElementIndex(offset); + Element current = paragraph.getElement(index); + + Element[] added; + Element[] removed; + Element[] splitRes = split(current, offset, length); + // Special case for when offset == startOffset or offset == endOffset. + if (splitRes[0] == null) { - Element cutPrev = createLeafElement(par, prev.getAttributes(), - prev.getStartOffset(), - offset); - Element[] remove = new Element[]{prev}; - Element[] add = new Element[]{cutPrev}; - if (prev.getEndOffset() > offset + len) - { - Element rem = createLeafElement(par, prev.getAttributes(), - offset + len, - prev.getEndOffset()); - add = new Element[]{cutPrev, rem}; - } - - par.replace(ind, 1, add); - documentEvent.addEdit(new ElementEdit(par, ind, remove, add)); - ind++; + added = new Element[2]; + added[0] = createLeafElement(paragraph, tag.getAttributes(), + offset, offset + length); + added[1] = splitRes[1]; + removed = new Element[0]; + index++; } - // ind now points to the next element. - - // Cut next element if necessary. - Element next = par.getElement(ind); - if (next != null && next.getStartOffset() < offset + len) + else if (current.getStartOffset() == offset) { - Element cutNext = createLeafElement(par, next.getAttributes(), - offset + len, - next.getEndOffset()); - Element[] remove = new Element[]{next}; - Element[] add = new Element[]{cutNext}; - par.replace(ind, 1, add); - documentEvent.addEdit(new ElementEdit(par, ind, remove, - add)); + added = new Element[2]; + added[0] = createLeafElement(paragraph, tag.getAttributes(), + offset, offset + length); + added[1] = splitRes[1]; + removed = new Element[] { current }; } - - // Insert new element. - Element newEl = createLeafElement(par, tag.getAttributes(), - offset, offset + len); - Element[] added = new Element[]{newEl}; - par.replace(ind, 0, added); - // Add this action to the document event. - ElementEdit edit = new ElementEdit(par, ind, new Element[0], - added); - documentEvent.addEdit(edit); + else if (current.getEndOffset() - length == offset) + { + added = new Element[2]; + added[0] = splitRes[0]; + added[1] = createLeafElement(paragraph, tag.getAttributes(), + offset, offset + length); + removed = new Element[] { current }; + } + else + { + added = new Element[3]; + added[0] = splitRes[0]; + added[1] = createLeafElement(paragraph, tag.getAttributes(), + offset, offset + length); + added[2] = splitRes[1]; + removed = new Element[] { current }; + } + paragraph.replace(index, removed.length, added); + addEdit(paragraph, index, removed, added); } offset += len; } @@ -800,6 +917,79 @@ result.replace(0, 0, children); return result; } + + /** + * Adds an ElementChange for a given element modification to the document + * event. If there already is an ElementChange registered for this element, + * this method tries to merge the ElementChanges together. However, this + * is only possible if the indices of the new and old ElementChange are + * equal. + * + * @param e the element + * @param i the index of the change + * @param removed the removed elements, or null + * @param added the added elements, or null + */ + private void addEdit(Element e, int i, Element[] removed, Element[] added) + { + // Perform sanity check first. + DocumentEvent.ElementChange ec = documentEvent.getChange(e); + + // Merge the existing stuff with the new stuff. + Element[] oldAdded = ec == null ? null: ec.getChildrenAdded(); + Element[] newAdded; + if (oldAdded != null && added != null) + { + if (ec.getIndex() <= i) + { + int index = i - ec.getIndex(); + // Merge adds together. + newAdded = new Element[oldAdded.length + added.length]; + System.arraycopy(oldAdded, 0, newAdded, 0, index); + System.arraycopy(added, 0, newAdded, index, added.length); + System.arraycopy(oldAdded, index, newAdded, index + added.length, + oldAdded.length - index); + i = ec.getIndex(); + } + else + throw new AssertionError("Not yet implemented case."); + } + else if (added != null) + newAdded = added; + else if (oldAdded != null) + newAdded = oldAdded; + else + newAdded = new Element[0]; + + Element[] oldRemoved = ec == null ? null: ec.getChildrenRemoved(); + Element[] newRemoved; + if (oldRemoved != null && removed != null) + { + if (ec.getIndex() <= i) + { + int index = i - ec.getIndex(); + // Merge removes together. + newRemoved = new Element[oldRemoved.length + removed.length]; + System.arraycopy(oldAdded, 0, newRemoved, 0, index); + System.arraycopy(removed, 0, newRemoved, index, removed.length); + System.arraycopy(oldRemoved, index, newRemoved, + index + removed.length, + oldRemoved.length - index); + i = ec.getIndex(); + } + else + throw new AssertionError("Not yet implemented case."); + } + else if (removed != null) + newRemoved = removed; + else if (oldRemoved != null) + newRemoved = oldRemoved; + else + newRemoved = new Element[0]; + + // Replace the existing edit for the element with the merged. + documentEvent.addEdit(new ElementEdit(e, i, newRemoved, newAdded)); + } } /** @@ -1353,7 +1543,8 @@ // Finally we must update the document structure and fire the insert update // event. buffer.insert(offset, index - offset, data, ev); - fireInsertUpdate(ev); + if (ev.modified) + fireInsertUpdate(ev); writeUnlock(); }