Package javax.swing.text

Source Code of javax.swing.text.DefaultStyledDocument$ChangeDesc

/*
*  Licensed to the Apache Software Foundation (ASF) under one or more
*  contributor license agreements.  See the NOTICE file distributed with
*  this work for additional information regarding copyright ownership.
*  The ASF licenses this file to You under the Apache License, Version 2.0
*  (the "License"); you may not use this file except in compliance with
*  the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*/
/**
* @author Alexey A. Ivanov
*/
package javax.swing.text;

import java.awt.Color;
import java.awt.Font;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.DocumentEvent.EventType;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.UndoableEdit;

import org.apache.harmony.x.swing.internal.nls.Messages;

public class DefaultStyledDocument extends AbstractDocument
    implements StyledDocument {

    public static class AttributeUndoableEdit extends AbstractUndoableEdit {
        protected AttributeSet copy;
        protected Element element;
        protected boolean isReplacing;
        protected AttributeSet newAttributes;

        public AttributeUndoableEdit(final Element element,
                                     final AttributeSet newAttributes,
                                     final boolean isReplacing) {
            this.element = element;
            this.newAttributes = newAttributes;
            this.isReplacing = isReplacing;
            this.copy = element.getAttributes().copyAttributes();
        }

        public void redo() {
            final AbstractElement elem = (AbstractElement)element;
            if (isReplacing) {
                elem.removeAttributes(elem);
            }
            elem.addAttributes(newAttributes);
        }

        public void undo() {
            final AbstractElement elem = (AbstractElement)element;
            elem.removeAttributes(newAttributes);
            elem.addAttributes(copy);
        }
    }

    public class ElementBuffer implements Serializable {
        private final Element root;
        private transient DefaultDocumentEvent event;
        private transient int offset;
        private transient int length;
        private transient ChangeDesc current;

        private transient Stack changeStack;
        private transient List changes;
        private transient boolean create;
        private transient Element tail;

        public ElementBuffer(final Element root) {
            this.root = root;
            initChangeLists();
        }

        public Element getRootElement() {
            return root;
        }

        public void insert(final int offset, final int length,
                           final ElementSpec[] spec,
                           final DefaultDocumentEvent event) {
            prepare(offset, length, event);

            insertUpdate(spec);

            collectEdits();
        }

        public void remove(final int offset, final int length,
                           final DefaultDocumentEvent event) {
            prepare(offset, length, event);

            removeUpdate();

            applyEdits();
            collectEdits();
        }

        public void change(final int offset, final int length,
                           final DefaultDocumentEvent event) {
            prepare(offset, length, event);

            changeUpdate();

            applyEdits();
            collectEdits();
        }

        public Element clone(final Element parent, final Element clonee) {
            if (clonee.isLeaf()) {
                return createLeafElement(parent,
                                         clonee.getAttributes(),
                                         clonee.getStartOffset(),
                                         clonee.getEndOffset());
            }
            BranchElement result =
                (BranchElement)createBranchElement(parent,
                                                   clonee.getAttributes());
            final int count = clonee.getElementCount();
            if (count > 0) {
                Element[] children = new Element[count];
                for (int i = 0; i < count; i++) {
                    children[i] = clone(result, clonee.getElement(i));
                }
                result.replace(0, 0, children);
            }
            return result;
        }

        protected void insertUpdate(final ElementSpec[] spec) {
            // Find the deepest branch
            Element branch = root;
            do {
                changeStack.push(new ChangeDesc(branch));
                branch = branch.getElement(branch.getElementIndex(offset));
            } while (!branch.isLeaf());

            current = (ChangeDesc)changeStack.peek();

            performSpecs(spec);
            leaveParagraph();
        }

        protected void removeUpdate() {
            final int endOffset = offset + length;
            final Element startLeaf = getCharacterElement(offset);
            final Element startBranch = startLeaf.getParentElement();

            final Element endLeaf = endOffset == startLeaf.getEndOffset()
                                    && endOffset < startBranch.getEndOffset()
                                    ? startLeaf
                                    : getCharacterElement(endOffset);
            final Element endBranch = endLeaf.getParentElement();

            if (startLeaf == endLeaf) {
                if (startLeaf.getStartOffset() == offset
                    && endOffset == startLeaf.getEndOffset()) {

                    current = new ChangeDesc(startBranch, offset);
                    current.removed.add(startLeaf);
                    changes.add(current);
                }
            } else if (startBranch == endBranch) {
                final int index = startBranch.getElementIndex(offset);
                current = new ChangeDesc(startBranch);
                for (int i = index; i < startBranch.getElementCount(); i++) {
                    final Element child = startBranch.getElement(i);
                    if (offset <= child.getStartOffset()
                        && child.getEndOffset() <= endOffset) {

                        current.setChildIndex(i);
                        current.removed.add(child);
                    }
                    if (endOffset < child.getEndOffset()) {
                        break;
                    }
                }

                changes.add(current);
            } else {
                final BranchElement parent =
                    (BranchElement)startBranch.getParentElement();
                if (parent != null) {
                    current = new ChangeDesc(parent, offset);
                   
                    BranchElement branch = (BranchElement)createBranchElement(parent,
                                                                startBranch.getAttributes());
                    List children = new LinkedList();
                   
                    // Copy elements from startBranch
                    int index = startBranch.getElementIndex(offset);
                    if (startBranch.getElement(index).getStartOffset() < offset) {
                        ++index;
                    }
                    for (int i = 0; i < index; i++) {
                        children.add(clone(branch, startBranch.getElement(i)));
                    }
                   
                    // Copy elements from endBranch
                    index = endBranch.getElementIndex(endOffset);
                    for (int i = index; i < endBranch.getElementCount(); i++) {
                        children.add(clone(branch, endBranch.getElement(i)));
                    }
                   
                    index = parent.getElementIndex(endOffset);
                    for (int i = current.getChildIndex(); i <= index; i++) {
                        current.removeChildElement(i);
                    }
                    current.added.add(branch);
                   
                    branch.replace(0, 0, listToElementArray(children));
                } else {
                    current = new ChangeDesc(startBranch, offset);
                   
                    // Copy elements from endBranch
                    int index = endBranch.getElementIndex(endOffset);
                    for (int i = index; i < endBranch.getElementCount(); i++) {
                        current.added.add(clone(startBranch, endBranch.getElement(i)));
                    }
                   
                    // Copy elements from startBranch
                    int startIndex = startBranch.getElementIndex(offset);
                    int endIndex = startBranch.getElementIndex(endOffset);
                    for (int i = startIndex; i <= endIndex; i++) {
                        current.removeChildElement(i);
                    }
                    current.setChildIndex(startIndex);
                   
                    current.apply();
                }

                changes.add(current);
            }
        }

        protected void changeUpdate() {
            final int endOffset = offset + length;
            final Element startLeaf = getCharacterElement(offset);
            final Element endLeaf = getCharacterElement(endOffset);

            if (startLeaf.getStartOffset() == offset
                && endOffset == startLeaf.getEndOffset()) {
                return;
            }

            if (startLeaf == endLeaf) {
                current = new ChangeDesc(startLeaf.getParentElement(), offset);
                current.splitLeafElement(startLeaf, offset, endOffset, true, startLeaf.getAttributes());

                changes.add(current);
            } else {
                // Break the startLeaf
                int start = startLeaf.getStartOffset();
                int end = startLeaf.getEndOffset();

                if (start < offset) {
                    current = new ChangeDesc(startLeaf.getParentElement(), offset);
                    current.splitLeafElement(startLeaf, offset);
                    changes.add(current);
                }

                // Break the endLeaf
                start = endLeaf.getStartOffset();
                end = endLeaf.getEndOffset();

                if (start < endOffset && endOffset < end) {
                    final boolean sameParents = current != null
                        && current.element == endLeaf.getParentElement();
                    if (!sameParents) {
                        current = new ChangeDesc(endLeaf.getParentElement(), endOffset);
                    } else {
                        final int endIndex = current.getChildIndexAtOffset(endOffset);
                        for (int i = current.getChildIndex() + 1;
                             i < endIndex; i++) {

                            final Element child = current.getChildElement(i);
                            current.removed.add(child);
                            current.added.add(child);
                        }
                    }

                    current.splitLeafElement(endLeaf, endOffset);

                    if (!sameParents) {
                        changes.add(current);
                    }
                }
            }
        }

        final void create(final ElementSpec[] specs,
                          final DefaultDocumentEvent event) {
            prepare(event.getOffset(), event.getLength(), event);
            create = true;

            // Remove all elements from the only paragraph
            current = new ChangeDesc(getParagraphElement(0));
            current.setChildIndex(0);
            current.createLeafElement(current.getChildElement(0).getAttributes(),
                                      length, length + 1);
            for (int i = 0; i < current.element.getElementCount(); i++) {
                current.removeChildElement(i);
            }
            current.apply();
            changes.add(current);
            current = null;

            performSpecs(specs);
            leaveParagraph();

            collectEdits();
        }

        private void performSpecs(final ElementSpec[] spec) throws Error {
            for (int i = 0; i < spec.length; i++) {
                switch (spec[i].getType()) {
                case ElementSpec.ContentType:
                    insertContent(spec[i]);
                    break;

                case ElementSpec.EndTagType:
                    insertEndTag();
                    break;

                case ElementSpec.StartTagType:
                    insertStartTag(spec[i]);
                    break;

                default:
                    throw new Error(Messages.getString("swing.err.12")); //$NON-NLS-1$
                }
            }
        }

        private void applyEdits() {
            for (int i = 0; i < changes.size(); i++) {
                final ChangeDesc desc = (ChangeDesc)changes.get(i);
                desc.apply();
            }
        }

        private void collectEdits() {
            while (!changeStack.empty()) {
                ChangeDesc desc = (ChangeDesc)changeStack.pop();
                if (!desc.isEmpty()) {
                    changes.add(desc);
                }
            }

            for (int i = 0; i < changes.size(); i++) {
                final ChangeDesc desc = (ChangeDesc)changes.get(i);
                if (!desc.isEmpty()) {
                    event.addEdit(desc.toElementEdit());
                }
            }
            changes.clear();

            clear();
        }

        private void clear() {
            event = null;
            current = null;
        }

        private void insertContent(final ElementSpec spec) {
            switch (spec.getDirection()) {
            case ElementSpec.OriginateDirection:
                insertContentOriginate(spec);
                break;

            case ElementSpec.JoinNextDirection:
                insertContentJoinNext(spec);
                break;

            case ElementSpec.JoinPreviousDirection:
                break;

            case ElementSpec.JoinFractureDirection:
                insertContentOriginate(spec);
                break;
            }
            offset += spec.getLength();
            length -= spec.getLength();
        }

        private void insertContentOriginate(final ElementSpec spec) {
            final AttributeSet specAttr = spec.getAttributes();
            if (current.element.getElementCount() == 0) {
                current.setChildIndex(0);
                current.createLeafElement(specAttr,
                                          offset, offset + spec.length);
            } else {
                current.setChildIndexByOffset(offset);
                final Element leafToRemove = current.getCurrentChild();
                if (offset == 0 && leafToRemove.isLeaf()) {
                    current.removed.add(leafToRemove);
                    current.createLeafElement(specAttr,
                                              offset, offset + spec.length);
                    current.createLeafElement(leafToRemove.getAttributes(),
                                              offset + length, leafToRemove.getEndOffset());
                    tail = current.getLastAddedElement();
                    current.added.remove(tail);
                } else if (offset == event.getOffset()
                        && leafToRemove.getStartOffset() < offset
                        && offset < leafToRemove.getEndOffset()) {
                    if (leafToRemove.isLeaf()) {
                        current.splitLeafElement(leafToRemove, offset,
                                                 offset + spec.length,
                                                 offset + length,
                                                 true, specAttr);
                        tail = current.getLastAddedElement();
                        current.added.remove(tail);
                    } else {
                        tail = splitBranch(leafToRemove);
                        current.createLeafElement(specAttr,
                                                  offset, offset + spec.length);
                        current.childIndex = current.getChildIndex() + 1;
                    }
                } else {
                    current.createLeafElement(specAttr,
                                              offset, offset + spec.length);
                    if (offset >= current.element.getEndOffset()
                        && current.getChildIndex() < current.element
                                .getElementCount()) {
                        current.childIndex = current.getChildIndex() + 1;
                    }
                }
            }
        }

        private void insertContentJoinNext(final ElementSpec spec) {
            current.setChildIndexByOffset(offset);
            final Element leaf = current.getCurrentChild();
            if (leaf.getStartOffset() >= offset) {
                current.removed.add(leaf);
                current.createLeafElement(leaf.getAttributes(),
                                          offset, leaf.getEndOffset());
            } else {
                final Element next =
                    current.getChildElement(current.getChildIndex() + 1);
                current.removed.add(leaf);
                current.removed.add(next);
                current.createLeafElement(leaf.getAttributes(),
                                          leaf.getStartOffset(), offset);
                current.createLeafElement(next.getAttributes(),
                                          offset, next.getEndOffset());
            }
        }

        private void insertStartTag(final ElementSpec spec) {
            switch (spec.getDirection()) {
            case ElementSpec.OriginateDirection:
                insertStartOriginate(spec);
                break;

            case ElementSpec.JoinNextDirection:
                insertStartJoinNext(spec);
                break;

            case ElementSpec.JoinPreviousDirection:
                insertStartJoinPrevious(spec);
                break;

            case ElementSpec.JoinFractureDirection:
                insertStartFracture(spec);
                break;

            default:
                throw new Error(Messages.getString("swing.err.13","ElementSpec")); //$NON-NLS-1$ //$NON-NLS-2$
            }
        }

        private void insertStartFracture(final ElementSpec spec) {
            final AttributeSet attrs =
                spec.getDirection() == ElementSpec.OriginateDirection
                ? spec.getAttributes()
                : findLastExistedBranch().getAttributes();
            final BranchElement newBranch =
                (BranchElement)createBranchElement(current.element,
                                                   attrs);

            final ChangeDesc lastChange = (ChangeDesc)changes.get(changes.size() - 1);
            int startIndex = lastChange.getChildIndexAtOffset(offset);
            if (lastChange.getChildElement(startIndex).getEndOffset() <= offset) {
                ++startIndex;
            }
            moveChildren(newBranch, lastChange, startIndex);

            current.added.add(newBranch);
            if (current.getChildIndex() == -1) {
                int newIndex = current.getChildIndexAtOffset(offset);
                if (newBranch.getElementCount() > 0
                    && newBranch.getEndOffset()
                       > current.getChildElement(newIndex).getStartOffset()) {
                    ++newIndex;
                }
                current.setChildIndex(newIndex);
            }
            if (current.isApplied()) {
                int replaceIndex = current.getChildIndexAtOffset(offset);
                if (newBranch.getElementCount() > 0
                    && newBranch.getEndOffset()
                       > current.getChildElement(replaceIndex).getStartOffset()) {
                    ++replaceIndex;
                }
                current.element.replace(replaceIndex, 0,
                                        new Element[] {newBranch});
            } else {
                current.apply();
            }

            current = new ChangeDesc(newBranch, true);
            changeStack.push(current);
        }

        private void moveChildren(final BranchElement newParent,
                                  final ChangeDesc sourceDesc,
                                  final int startIndex) {
            // copy all elements from lastBranch to the new one
            final int count = sourceDesc.element.getElementCount();
            final Element[] children = new Element[count - startIndex];
            for (int i = startIndex; i < count; i++) {
                children[i - startIndex] = clone(newParent, sourceDesc.getChildElement(i));
            }
            // Now we need to remove all previously added elements which were
            // copied from added list
            final int i = startIndex - sourceDesc.getChildIndex();
            for (int j = startIndex; j < count; j++) {
                final Object addedElement = sourceDesc.getAddedElement(i);
                final Object existingElement = sourceDesc.getChildElement(j);
                if (addedElement == existingElement) {
                    sourceDesc.added.remove(addedElement);
                } else if (!sourceDesc.justCreated) {
                    sourceDesc.removed.add(existingElement);
                }
            }
            // Complete the removal of elements from source
            if (count - startIndex > 0) {
                sourceDesc.element.replace(startIndex, count - startIndex, new Element[0]);
            }

            // Place copied children into the new parent
            newParent.replace(0, 0, children);
        }

        private void insertStartOriginate(final ElementSpec spec) {
            if (current == null) {
                insertStartJoinPrevious(spec);
            } else if (!create && !changes.isEmpty()) {
                insertStartFracture(spec);
            } else {
                Element branch = createBranchElement(current.element,
                                                     spec.getAttributes());
                current.setChildIndexByOffset(offset);
                current.added.add(branch);
                current = new ChangeDesc(branch, true);
                changeStack.push(current);
            }
        }

        private void insertStartJoinNext(final ElementSpec spec) {
            current = new ChangeDesc(current.getChildAtOffset(offset));
            changeStack.push(current);
        }

        private void insertStartJoinPrevious(final ElementSpec spec) {
            if (current == null) {
                current = new ChangeDesc(getRootElement());
                // TODO are old attributes to be removed?
                final AttributeSet specAttr = spec.getAttributes();
                if (specAttr != null) {
                    ((AbstractElement)getRootElement()).addAttributes(specAttr);
                }
                changeStack.push(current);
            } else {
                current = new ChangeDesc(current.getChildAtOffset(offset));
                changeStack.push(current);
            }
        }

        private void insertEndTag() {
            if (current.isEmpty()) {
                current.setChildIndexByOffset(offset);
                Element leaf = current.getCurrentChild();
                final int start = leaf.getStartOffset();
                final int end = leaf.getEndOffset();
                if (start < offset && offset < end
                    || start < offset + length && offset + length < end) {

                    if (leaf.isLeaf()) {
                        current.splitLeafElement(leaf, offset, offset + length, false, null);
                    } else if (length != 0) {
                        BranchElement rightBranch = splitBranch(leaf);
                        current.added.add(rightBranch);
                        int newIndex = current.getChildIndexAtOffset(offset + length);
                        if (rightBranch.getElementCount() > 0
                                && rightBranch.getEndOffset()
                                > current.getChildElement(newIndex).getStartOffset()) {
                            ++newIndex;
                        }
                        current.childIndex = newIndex;
                    }
                }
            }
            leaveParagraph();
            changes.add(current);
            changeStack.pop();
            current = changeStack.empty() ? null : (ChangeDesc)changeStack.peek();
        }

        private BranchElement splitBranch(final Element branch) {
            BranchElement result = current.createBranchElement(branch.getAttributes());
            final ChangeDesc lastChange = (ChangeDesc)changes.get(changes.size() - 1);
            int startIndex = lastChange.getChildIndexAtOffset(offset + length);
            moveChildren(result, lastChange, startIndex);
            return result;
        }

        private BranchElement findLastExistedBranch() {
            int i = changes.size() - 1;
            ChangeDesc desc = null;
            while (i >= 0 && (desc = (ChangeDesc)changes.get(i)).justCreated) {
                i--;
            }
            return i >= 0 ? desc.element : null;
        }

        private void leaveParagraph() {
            if (current == null || current.isEmpty()) {
                return;
            }

            if (tail != null) {
                current.added.add(tail);
            }
            tail = null;
            current.apply();
        }

        private Element[] listToElementArray(final List list) {
            return (Element[])list.toArray(new Element[list.size()]);
        }

        private void initChangeLists() {
            changeStack = new Stack();
            changes = new ArrayList();
        }

        private void prepare(final int offset, final int length,
                          final DefaultDocumentEvent event) {
            this.offset = offset;
            this.length = length;
            this.event  = event;

            this.changes.clear();
            this.changeStack.clear();
            this.current = null;

            this.create = false;
            this.tail = null;
        }

        private void readObject(final ObjectInputStream ois)
            throws IOException, ClassNotFoundException {

            ois.defaultReadObject();
            initChangeLists();
        }

        private void writeObject(final ObjectOutputStream oos)
            throws IOException {

            oos.defaultWriteObject();
        }
    }

    public static class ElementSpec {
        public static final short ContentType = 3;
        public static final short EndTagType = 2;
        public static final short StartTagType = 1;

        public static final short JoinFractureDirection = 7;
        public static final short JoinNextDirection = 5;
        public static final short JoinPreviousDirection = 4;
        public static final short OriginateDirection = 6;

        private AttributeSet attrs;
        private short type;
        private char[] text;
        private int offset;
        private int length;
        private short direction;

        public ElementSpec(final AttributeSet attrs, final short type) {
            this(attrs, type, null, 0, 0);
        }

        public ElementSpec(final AttributeSet attrs,
                           final short type,
                           final char[] text,
                           final int offset,
                           final int length) {
            this.attrs  = attrs;
            this.type   = type;
            this.text   = text;
            this.offset = offset;
            this.length = length;

            this.direction = OriginateDirection;
        }

        public ElementSpec(final AttributeSet attrs,
                           final short type,
                           final int length) {
            this(attrs, type, null, 0, length);
        }


        public char[] getArray() {
            return text;
        }

        public AttributeSet getAttributes() {
            return attrs;
        }

        public short getDirection() {
            return direction;
        }

        public int getLength() {
            return length;
        }

        public int getOffset() {
            return offset;
        }

        public short getType() {
            return type;
        }

        public void setDirection(final short direction) {
            this.direction = direction;
        }

        public void setType(final short type) {
            this.type = type;
        }

        /*
         * The format of the string is based on 1.5 release behavior
         * which can be revealed using the following code:
         *
         *     Object obj = new DefaultStyledDocument.ElementSpec(null,
         *         DefaultStyledDocument.ElementSpec.ContentType);
         *     System.out.println(obj.toString());
         */
        public String toString() {
            String result;
            switch (type) {
            case StartTagType:
                result = "StartTag:";
                break;
            case ContentType:
                result = "Content:";
                break;
            case EndTagType:
                result = "EndTag:";
                break;
            default:
                result = "??:";
            }

            switch (direction) {
            case OriginateDirection:
                result += "Originate:";
                break;
            case JoinFractureDirection:
                result += "Fracture:";
                break;
            case JoinNextDirection:
                result += "JoinNext:";
                break;
            case JoinPreviousDirection:
                result += "JoinPrevious:";
                break;
            default:
                result += "??:";
            }

            result += length;

            return result;
        }

    }

    protected class SectionElement extends BranchElement {
        public SectionElement() {
            super(null, null);
        }

        public String getName() {
            return AbstractDocument.SectionElementName;
        }
    }

    private final class ChangeDesc {
        public final BranchElement element;
        private int childIndex = -1;
        public final List added = new ArrayList();
        public final List removed = new ArrayList();
        public final boolean justCreated;
        private boolean applied;

        public ChangeDesc(final Element element) {
            this(element, false);
        }

        public ChangeDesc(final Element element,
                          final boolean justCreated) {
            this.element = (BranchElement)element;
            this.justCreated = justCreated;
        }

        public ChangeDesc(final Element element,
                          final int offset) {
            this(element, false);
            setChildIndexByOffset(offset);
        }

        public void setChildIndex(final int index) {
            if (this.childIndex == -1) {
                this.childIndex = index;
            }
        }

        public int getChildIndex() {
            return childIndex;
        }

        public Element[] getChildrenAdded() {
            return (Element[])added.toArray(new Element[added.size()]);
        }

        public Element[] getChildrenRemoved() {
            return (Element[])removed.toArray(new Element[removed.size()]);
        }

        public ElementEdit toElementEdit() {
            return new ElementEdit(element, childIndex,
                                   getChildrenRemoved(),
                                   getChildrenAdded());
        }

        public void apply() {
            if (applied || isEmpty()) {
                return;
            }
            if (childIndex == -1) {
                childIndex = 0;
            }

            applied = true;
            element.replace(childIndex, removed.size(), getChildrenAdded());
        }

        public boolean isEmpty() {
            return removed.size() == 0 && added.size() == 0;
        }

        public boolean isApplied() {
            return applied;
        }

        public void createLeafElement(final AttributeSet attr, final int start,
                                      final int end) {
            added.add(DefaultStyledDocument.this.createLeafElement(element, attr, start, end));
        }

        public BranchElement createBranchElement(final AttributeSet attr) {
            return (BranchElement)DefaultStyledDocument.this.createBranchElement(element, attr);
        }

        public void splitLeafElement(final Element leaf, final int splitOffset) {
            final AttributeSet attrs = leaf.getAttributes();
            createLeafElement(attrs , leaf.getStartOffset(), splitOffset);
            createLeafElement(attrs, splitOffset, leaf.getEndOffset());
            removed.add(leaf);
        }

        public void splitLeafElement(final Element child,
                                     final int splitOffset1,
                                     final int splitOffset2,
                                     final boolean createMiddle,
                                     final AttributeSet middleAttr) {
            splitLeafElement(child, splitOffset1, splitOffset2, splitOffset2, createMiddle, middleAttr);
        }

        public void splitLeafElement(final Element child,
                                     final int splitOffset1,
                                     final int splitOffset2,
                                     final int splitOffset3,
                                     final boolean createMiddle,
                                     final AttributeSet middleAttr) {
            final AttributeSet attrs = child.getAttributes();
            if (child.getStartOffset() < splitOffset1) {
                createLeafElement(attrs, child.getStartOffset(), splitOffset1);
            }
            if (createMiddle) {
                createLeafElement(middleAttr, splitOffset1, splitOffset2);
            }
            if (splitOffset3 < child.getEndOffset()) {
                createLeafElement(attrs, splitOffset3, child.getEndOffset());
            }
            removed.add(child);
        }

        public void setChildIndexByOffset(final int offset) {
            setChildIndex(element.getElementIndex(offset));
        }

        public Element getChildAtOffset(final int offset) {
            return element.getElement(element.getElementIndex(offset));
        }

        public int getChildIndexAtOffset(final int offset) {
            return element.getElementIndex(offset);
        }

        public Element getCurrentChild() {
            return element.getElement(childIndex);
        }

        public Element getChildElement(final int index) {
            return element.getElement(index);
        }

        public void removeChildElement(final int index) {
            removed.add(element.getElement(index));
        }

        public Element getAddedElement(final int i) {
            return (i > 0 && i < added.size()) ? (Element)added.get(i) : null;
        }

        public Element getLastAddedElement() {
            return (Element)added.get(added.size() - 1);
        }
        public Element getLastRemovedElement() {
            return (Element)removed.get(removed.size() - 1);
        }
    }

    public static final int BUFFER_SIZE_DEFAULT = 4096;
    private transient AttributeSet defaultLogicalStyle;

    protected ElementBuffer buffer;

    private ChangeListener styleContextChangeListener;
    private ChangeListener styleChangeListener;

    public DefaultStyledDocument() {
        this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext());
    }

    public DefaultStyledDocument(final Content content,
                                 final StyleContext styles) {
        super(content, styles);
        createDefaultLogicalStyle();
        buffer = new ElementBuffer(createDefaultRoot());
    }

    public DefaultStyledDocument(final StyleContext styles) {
        this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
    }

    public Style addStyle(final String name,
                          final Style parent) {
        return getStyleContext().addStyle(name, parent);
    }

    public void removeStyle(final String name) {
        getStyleContext().removeStyle(name);
    }

    public Style getStyle(final String name) {
        return getStyleContext().getStyle(name);
    }

    public Enumeration<?> getStyleNames() {
        return getStyleContext().getStyleNames();
    }

    public Color getForeground(final AttributeSet attrs) {
        return getStyleContext().getForeground(attrs);
    }

    public Color getBackground(final AttributeSet attrs) {
        return getStyleContext().getBackground(attrs);
    }

    public Font getFont(final AttributeSet attrs) {
        return getStyleContext().getFont(attrs);
    }

    public Element getDefaultRootElement() {
        return buffer.getRootElement();
    }

    public Element getCharacterElement(final int offset) {
        final Element paragraph = getParagraphElement(offset);
        return paragraph.getElement(paragraph.getElementIndex(offset));
    }

    public Element getParagraphElement(final int offset) {
        Element branch;
        Element child = getDefaultRootElement();
        do {
            branch = child;
            child = branch.getElement(branch.getElementIndex(offset));
        } while (!child.isLeaf());
        return branch;
    }

    public void setCharacterAttributes(final int offset,
                                       final int length,
                                       final AttributeSet attrs,
                                       final boolean replace) {
        if (checkInvalid(offset, length)) {
            return;
        }

        writeLock();
        try {
            final DefaultDocumentEvent event =
                new DefaultDocumentEvent(offset, length, EventType.CHANGE);

            buffer.change(offset, length, event);

            AbstractElement element;
            int currentOffset = offset;
            final int limit = offset + length;
            while (currentOffset < limit) {
                element = (AbstractElement)getCharacterElement(currentOffset);
                event.addEdit(new AttributeUndoableEdit(element,
                                                        attrs, replace));
                if (replace) {
                    element.removeAttributes(element.getAttributeNames());
                }
                element.addAttributes(attrs);
                currentOffset = element.getEndOffset();
            }

            event.end();
            fireChangedUpdate(event);
            fireUndoableEditUpdate(new UndoableEditEvent(this, event));
        } finally {
            writeUnlock();
        }
    }

    public void setParagraphAttributes(final int offset,
                                       final int length,
                                       final AttributeSet attrs,
                                       final boolean replace) {
        if (checkInvalid(offset, length)) {
            return;
        }

        writeLock();
        try {
            final DefaultDocumentEvent event =
                new DefaultDocumentEvent(offset, length, EventType.CHANGE);

            AbstractElement element;
            int currentOffset = offset;
            final int limit = offset + length;
            while (currentOffset < limit) {
                element = (AbstractElement)getParagraphElement(currentOffset);
                event.addEdit(new AttributeUndoableEdit(element,
                                                        attrs, replace));
                if (replace) {
                    element.removeAttributes(element.getAttributeNames());
                }
                element.addAttributes(attrs);
                currentOffset = element.getEndOffset();
            }

            event.end();
            fireChangedUpdate(event);
            fireUndoableEditUpdate(new UndoableEditEvent(this, event));
        } finally {
            writeUnlock();
        }
    }

    public void setLogicalStyle(final int offset,
                                final Style style) {
        final AbstractElement branch =
            (AbstractElement)getParagraphElement(offset);
        writeLock();
        try {
            branch.setResolveParent(style);
        } finally {
            writeUnlock();
        }
    }

    public Style getLogicalStyle(final int offset) {
        final Element element = getParagraphElement(offset);
        Object resolver = element.getAttributes().getResolveParent();
        return resolver instanceof Style ? (Style)resolver : null;
    }

    public void addDocumentListener(final DocumentListener listener) {
        super.addDocumentListener(listener);
        getStyleContext().addChangeListener(getStyleContextListener());
        addListenerToStyles();
    }

    public void removeDocumentListener(final DocumentListener listener) {
        super.removeDocumentListener(listener);
        if (getDocumentListeners().length == 0) {
            getStyleContext().removeChangeListener(getStyleContextListener());
            removeListenerFromStyles();
        }
    }

    protected AbstractElement createDefaultRoot() {
        final BranchElement result = new SectionElement();
        writeLock();
        try {
            final BranchElement paragraph =
                (BranchElement)createBranchElement(result, null);
            paragraph.setResolveParent(getStyle(StyleContext.DEFAULT_STYLE));
            final Element content =
                createLeafElement(paragraph, null,
                                  getStartPosition().getOffset(),
                                  getEndPosition().getOffset());
            paragraph.replace(0, 0, new Element[] {content});
            result.replace(0, 0, new Element[] {paragraph});
        } finally {
            writeUnlock();
        }
        return result;
    }

    protected void create(final ElementSpec[] specs) {
        final StringBuffer text = appendSpecsText(specs);

        writeLock();
        try {
            if (getLength() > 0) {
                try {
                    remove(0, getLength());
                } catch (BadLocationException e) {
                    e.printStackTrace();
                }
            }

            final int offset = 0;
            UndoableEdit contentInsert = null;
                try {
                    contentInsert =
                        getContent().insertString(offset, text.toString());
                } catch (BadLocationException e) {
                    e.printStackTrace();
                }

            DefaultDocumentEvent event =
                new DefaultDocumentEvent(offset, text.length(),
                                         EventType.INSERT);
            if (contentInsert != null) {
                event.addEdit(contentInsert);
            }
            event.addEdit(
                new AttributeUndoableEdit(buffer.getRootElement(),
                                          getStyleContext().getEmptySet(),
                                          true));
            ((AbstractElement)buffer.getRootElement())
                              .removeAttributes(buffer.getRootElement()
                                                .getAttributes());

            buffer.create(specs, event);

            event.end();
            fireInsertUpdate(event);
            if (contentInsert != null) {
                fireUndoableEditUpdate(new UndoableEditEvent(this, event));
            }
        } finally {
            writeUnlock();
        }
    }

    protected void insert(final int offset, final ElementSpec[] specs)
        throws BadLocationException {

        final StringBuffer text = appendSpecsText(specs);
        writeLock();
        try {
            UndoableEdit contentInsert =
                getContent().insertString(offset, text.toString());

            DefaultDocumentEvent event =
                new DefaultDocumentEvent(offset, text.length(),
                                         EventType.INSERT);
            if (contentInsert != null) {
                event.addEdit(contentInsert);
            }

            buffer.insert(offset, text.length(), specs, event);

            event.end();
            fireInsertUpdate(event);
            if (contentInsert != null) {
                fireUndoableEditUpdate(new UndoableEditEvent(this, event));
            }
        } finally {
            writeUnlock();
        }
    }

    protected void insertUpdate(final DefaultDocumentEvent event,
                                final AttributeSet attrs) {
        final AttributeSet attributes = attrs == null
                                        ? getStyleContext().getEmptySet()
                                        : attrs;

        final List specs = new LinkedList();

        String text = null;
        final int offset = event.getOffset();
        final int length = event.getLength();

        try {
            text = getText(offset, length);
        } catch (final BadLocationException e) { }

        boolean splitPrevParagraph = false;
        try {
            splitPrevParagraph = offset > 0
                                 && getText(offset - 1, 1).charAt(0) == '\n';
        } catch (final BadLocationException e) { }

        final int firstBreak = text.indexOf('\n');
        final int lastBreak = text.lastIndexOf('\n');
        final boolean hasLineBreak = firstBreak != -1;

        Element charElem = getCharacterElement(offset);
        ElementSpec spec = null;
        if (!hasLineBreak) {
            if (splitPrevParagraph) {
                splitBranch(specs, offset, length, charElem,
                            ElementSpec.JoinNextDirection);
                // The direction of the next Content element must be chosen
                // based on attributes of the first Content element
                // in the next paragraph
                charElem = getCharacterElement(offset + length);
            }
            spec = new ElementSpec(attributes, ElementSpec.ContentType, length);
            if (charElem.getAttributes().isEqual(attributes)) {
                spec.setDirection(splitPrevParagraph
                                  ? ElementSpec.JoinNextDirection
                                  : ElementSpec.JoinPreviousDirection);
            }
            specs.add(spec);
        } else {
            int currentOffset = offset;
            int currentIndex = firstBreak;
            int processedLength = 0;

            if (splitPrevParagraph) {
                splitBranch(specs, offset, length, charElem,
                            ElementSpec.OriginateDirection);
            }

            while (currentOffset < offset + length) {
                if (!(currentIndex < 0)) {
                    spec = new ElementSpec(attributes, ElementSpec.ContentType,
                                           currentIndex + 1 - processedLength);
                    currentOffset += spec.getLength();
                    processedLength += spec.getLength();
                    if (specs.size() == 0
                        && charElem.getAttributes().isEqual(attributes)) {

                        spec.setDirection(ElementSpec.JoinPreviousDirection);
                    }
                    specs.add(spec);

                    specs.add(new ElementSpec(null, ElementSpec.EndTagType));

                    spec = new ElementSpec(defaultLogicalStyle,
                                           ElementSpec.StartTagType);
                    if (currentIndex == lastBreak) {
                        spec.setDirection(splitPrevParagraph
                                          ? ElementSpec.JoinNextDirection
                                          : ElementSpec.JoinFractureDirection);
                    }
                    specs.add(spec);

                    currentIndex = text.indexOf('\n', currentIndex + 1);
                } else {
                    spec = new ElementSpec(attributes, ElementSpec.ContentType,
                                           length - processedLength);
                    currentOffset += spec.getLength();
                    processedLength += spec.getLength();
                    if (getCharacterElement(currentOffset)
                        .getAttributes().isEqual(attributes)) {

                        spec.setDirection(ElementSpec.JoinNextDirection);
                    }
                    specs.add(spec);
                }
            }

        }

        final Object[] specArray = specs.toArray(new ElementSpec[specs.size()]);
        buffer.insert(offset, length, (ElementSpec[])specArray, event);

        super.insertUpdate(event, attrs);
    }

    private void splitBranch(final List specs,
                             final int offset, final int length,
                             final Element leaf,
                             final short lastSpecDirection) {
        ElementSpec spec = null;
        Element branch = leaf.getParentElement();
        final int endOffset = offset + length;
        while (branch != null && branch.getEndOffset() == endOffset) {
            specs.add(new ElementSpec(null, ElementSpec.EndTagType));
            branch = branch.getParentElement();
        }

        branch = branch.getElement(branch.getElementIndex(offset) + 1);
        while (branch != null
               && !branch.isLeaf()
               && branch.getStartOffset() == endOffset) {
            spec = new ElementSpec(branch.getAttributes(),
                                   ElementSpec.StartTagType);
            spec.setDirection(ElementSpec.JoinNextDirection);
            specs.add(spec);
            branch = branch.getElement(0);
        }
        spec.setDirection(lastSpecDirection);
    }

    protected void removeUpdate(final DefaultDocumentEvent event) {
        buffer.remove(event.getOffset(), event.getLength(), event);
    }

    protected void styleChanged(final Style style) {
    }

    private StringBuffer appendSpecsText(final ElementSpec[] specs) {
        final StringBuffer result = new StringBuffer();
        for (int i = 0; i < specs.length; i++) {
            if (specs[i].getLength() > 0) {
                result.append(specs[i].getArray(), specs[i].getOffset(),
                            specs[i].getLength());
            }
        }
        return result;
    }

    private void addListenerToStyles() {
        final Enumeration names = getStyleNames();
        while (names.hasMoreElements()) {
            String name = (String)names.nextElement();
            getStyle(name).addChangeListener(getStyleChangeListener());
        }
    }

    private void removeListenerFromStyles() {
        final Enumeration names = getStyleNames();
        while (names.hasMoreElements()) {
            String name = (String)names.nextElement();
            getStyle(name).removeChangeListener(getStyleChangeListener());
        }
    }

    private boolean checkInvalid(final int offset, final int length) {
        return offset < 0 || length <= 0 || offset + length > getLength() + 1;
    }

    private void createDefaultLogicalStyle() {
        final StyleContext styles = getStyleContext();
        defaultLogicalStyle =
            styles.addAttribute(styles.getEmptySet(),
                                AttributeSet.ResolveAttribute,
                                styles.getStyle(StyleContext.DEFAULT_STYLE));
    }

    private ChangeListener getStyleChangeListener() {
        if (styleChangeListener == null) {
            styleChangeListener = new ChangeListener() {
                public void stateChanged(final ChangeEvent e) {
                    styleChanged((Style)e.getSource());
                }
            };
        }
        return styleChangeListener;
    }

    private ChangeListener getStyleContextListener() {
        if (styleContextChangeListener == null) {
            styleContextChangeListener = new ChangeListener() {
                public void stateChanged(final ChangeEvent e) {
                    removeListenerFromStyles();
                    addListenerToStyles();
                }
            };
        }
        return styleContextChangeListener;
    }

    private StyleContext getStyleContext() {
        return (StyleContext)getAttributeContext();
    }

    private void readObject(final ObjectInputStream ois)
        throws IOException, ClassNotFoundException {

        ois.defaultReadObject();
        createDefaultLogicalStyle();
    }

    private void writeObject(final ObjectOutputStream oos)
        throws IOException {

        oos.defaultWriteObject();
    }
}

TOP

Related Classes of javax.swing.text.DefaultStyledDocument$ChangeDesc

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.