alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.editor;

import javax.swing.text.AbstractDocument;
import javax.swing.text.Element;
import javax.swing.text.Document;
import javax.swing.text.AttributeSet;
import javax.swing.text.Position;
import javax.swing.undo.UndoableEdit;
import java.util.ArrayList;
import javax.swing.text.BadLocationException;
import javax.swing.text.Segment;
import javax.swing.text.StyleContext;
import org.netbeans.modules.editor.util.element.GapBranchElement;
import org.openide.ErrorManager;

/**
 * Line root element implementation.
 *
 * @author Miloslav Metelka
 * @version 1.00
 */

final class LineRootElement extends GapBranchElement {
    
    private static final LineElement[] EMPTY_LINE_ELEMENT_ARRAY = new LineElement[0];
 
    private static final String NAME
        = AbstractDocument.SectionElementName;
    
    private BaseDocument doc;
    
    private LineElement[] addedLines = EMPTY_LINE_ELEMENT_ARRAY;
    
    LineRootElement(BaseDocument doc) {
        this.doc = doc;

        assert (doc.getLength() == 0) : "Cannot start with non-empty document"; // NOI18N
        
        Position startPos = doc.getStartPosition();
        assert (startPos.getOffset() == 0) : "Document.getStartPosition() != 0"; // NOI18N

        Position endPos = doc.getEndPosition();
        assert (endPos.getOffset() == 1) : "Document.getEndPosition() != 1"; // NOI18N

        Element line = new LineElement(this, startPos, endPos);
        replace(0, 0, new Element[]{ line });

        assert (getElement(0) != null);
    }
    
    /**
     * Double size of the addedLines array and return
     * the index value that corresponds to the original zero index.
     */
    private int doubleAddedLinesCapacity() {
        int addedLinesLength = addedLines.length;
        int newCapacity = Math.max(4, addedLinesLength * 2);
        LineElement[] newAddedLines = new LineElement[newCapacity];
        // Copy current contents to end of array
        System.arraycopy(addedLines, 0, newAddedLines,
            newCapacity - addedLinesLength, addedLinesLength);
        addedLines = newAddedLines;
        return (newCapacity - addedLinesLength); // value for original index zero
    }
    
    public Element getElement(int index) {
        if (index < 0) {
            throw new IndexOutOfBoundsException("Invalid line index=" + index + " < 0"); // NOI18N
        }
        int elementCount = getElementCount();
        if (index >= elementCount) {
            throw new IndexOutOfBoundsException("Invalid line index=" + index // NOI18N
                + " >= lineCount=" + elementCount); // NOI18N
        }
        
        LineElement elem = (LineElement)super.getElement(index);
        assert (elem != null);
        return elem;
    }

    UndoableEdit insertUpdate(int insertOffset, int insertLength) {
        int lastInsertedCharOffset = insertOffset + insertLength - 1;
        CharSeq text = doc.getText();
        Edit edit = null;
        
        int index = -1; // Index of the elements modification
        Element[] removeElements = null; // Removed line elements
        // Index in the addedLines array - adding from last to first
        int firstAddedLineIndex = addedLines.length; // nothing added yet
        
        int offset = lastInsertedCharOffset;
        // insertAtPrevLineEndOffset - whether the insert was done at the end (after '\n')
        // of a previous line i.e. in fact at a begining of the next line.
        boolean insertAtPrevLineEndOffset;
        int beforeInsertOffset; // in fact Math.max(insertOffset - 1, 0)
        if (insertOffset == 0) { // [swing] marks (and elements) at offset zero do not move up
            beforeInsertOffset = 0;
            insertAtPrevLineEndOffset = false;
        } else { // inserting inside doc
            beforeInsertOffset = insertOffset - 1; // check char before offset for '\n'
            insertAtPrevLineEndOffset = (text.charAt(beforeInsertOffset) == '\n');
        }

        try {
            // Go through all the inserted lines plus the char at beforeInsertOffset (if exists)
            // and create new line elements at every occurrence of '\n'
            Position futureAddedLineEndPos = null;
            while (offset >= beforeInsertOffset) {
                if (text.charAt(offset) == '\n') { // line break at offset
                    boolean addLine = true; // whether line element should be added
                    if (futureAddedLineEndPos == null) {
                        // Find the first line element that will be removed
                        index = getElementIndex(insertOffset);
                        LineElement removeLine = (LineElement)getElement(index);
                        // If inserting at begining of line (insertAtPrevLineEndOffset == true)
                        // and the inserted chars do not end with '\n'
                        // then not only current line must be removed
                        // but the next one as well.
                        if (insertAtPrevLineEndOffset) { // '\n' at (insertOffset - 1)
                            if (offset == lastInsertedCharOffset) { // inserted 'xxx\n' 
                                removeElements = new Element[] { removeLine };
                                futureAddedLineEndPos = removeLine.getEndPosition();
                                addLine = false; // do not add new line in this case
                            } else {
                                LineElement nextRemoveLine = (LineElement)getElement(index + 1);
                                removeElements = new Element[] {
                                    removeLine,
                                    nextRemoveLine
                                };
                                futureAddedLineEndPos = nextRemoveLine.getEndPosition();
                            }
                                
                        } else { // otherwise use the next element as the next for added
                            removeElements = new Element[] { removeLine };
                            futureAddedLineEndPos = removeLine.getEndPosition();
                        }
                    }

                    if (addLine) {
                        if (firstAddedLineIndex == 0) { // no more space to add
                            firstAddedLineIndex = doubleAddedLinesCapacity();
                        }
                        firstAddedLineIndex--; // will fill in added line element soon
                        Position lineStartPos = doc.createPosition(offset + 1);
                        addedLines[firstAddedLineIndex] = new LineElement(
                            this, lineStartPos, futureAddedLineEndPos);
                        futureAddedLineEndPos = lineStartPos;
                    }
                }
                offset--;
            }

            if (futureAddedLineEndPos != null) { // will add (and remove) lines
                // Create array of added lines and add extra one at begining
                int addedLineCount = addedLines.length - firstAddedLineIndex;
                Element[] addElements = new Element[addedLineCount + 1];
                System.arraycopy(addedLines, firstAddedLineIndex, addElements, 1, addedLineCount);
                addElements[0] = new LineElement(
                    this,
                    ((LineElement)removeElements[0]).getStartPosition(),
                    futureAddedLineEndPos
                );

                replace(index, removeElements.length, addElements);
                edit = new Edit(index, removeElements, addElements);
            }

        } catch (BadLocationException e) {
            // Should never happen but in case it happens
            // retain the current consistent state (no replace is done)
            // and report this as serious error
            ErrorManager.getDefault().notify(ErrorManager.ERROR, e);
        }
        checkConsistency();
        return edit;
    }

    UndoableEdit removeUpdate(int removeOffset, int removeLength) {
        // The algorithm here is similar to the one in PlainDocument.removeUpdate().
        // Unfortunately in case exactly a line element (or multiple line elements)
        // the algorithm removes extra line that follows the end of removed area.
        // That could be improved but compatibility with PlainDocument would be lost.
        Edit edit = null;
        int removeEndOffset = removeOffset + removeLength;
        int line0 = getElementIndex(removeOffset);
        int line1 = getElementIndex(removeEndOffset);
        if (line0 != line1) {
            // at least one line was removed
            line1++; // will remove the line where remove ends as well
            Element[] removeElements = new Element[line1 - line0];
            copyElements(line0, line1, removeElements, 0);
            Element[] addElements = new Element[] {
                new LineElement(this,
                    ((LineElement)removeElements[0]).getStartPosition(),
                    ((LineElement)removeElements[removeElements.length - 1]).getEndPosition()
                )
            };
            
            replace(line0, removeElements.length, addElements);
            edit = new Edit(line0, removeElements, addElements);
        }
        checkConsistency();
        return edit;
    }

/*    protected void compact() {
        super.compact();
        addedLines = EMPTY_LINE_ELEMENT_ARRAY;
    }
 */

    public Document getDocument() {
        return doc;
    }
    
    public Element getParentElement() {
        return null;
    }

    public String getName() {
        return NAME;
    }

    public AttributeSet getAttributes() {
        return StyleContext.getDefaultStyleContext().getEmptySet();
    }

    public int getStartOffset() {
        return 0;
    }

    public int getEndOffset() {
        return doc.getLength() + 1;
    }

    public int getElementIndex(int offset) {
        if (offset == 0) { // NB uses this frequently to just get the parent
            return 0;
        }

        return super.getElementIndex(offset);
    }

    private void checkConsistency() {
        int lineCount = getElementCount();
        assert (lineCount > 0); // Should be 1 or greater
        int prevLineEndOffset = 0;
        for (int i = 0; i < lineCount; i++) {
            LineElement elem = (LineElement)getElement(i);
            assert (prevLineEndOffset == elem.getStartOffset());
            assert (prevLineEndOffset < elem.getEndOffset())
                : "Line " + i + " of " + lineCount + ": " + lineToString(elem); // NOI18N
            prevLineEndOffset = elem.getEndOffset();
        }
        assert (prevLineEndOffset == (doc.getLength() + 1));
    }
    
    private String lineToString(Element line) {
        return "<" + line.getStartOffset() + ", " // NOI18N
            + line.getEndOffset() + ">"; // NOI18N
    }

}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.