All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.editor.DocumentContent Maven / Gradle / Ivy

/*
 *                 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 java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.text.Segment;
import javax.swing.text.BadLocationException;
import javax.swing.text.Position;
import javax.swing.text.AbstractDocument;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.UndoableEdit;

/**
 * Content of the document.
 *
 * @author Miloslav Metelka
 * @version 1.00
 */

final class DocumentContent implements AbstractDocument.Content, CharSeq, GapStart {
    
    private static final char[] EMPTY_CHAR_ARRAY = new char[0];

    /**
     * Invalid undoable edit being used to mark that the line undo was already
     * processed. It must never be undone/redone as it's used in a flyweight
     * way but the undomanager's operation changes states of undoable edits
     * being undone/redone.
     */
    private static final UndoableEdit INVALID_EDIT = new AbstractUndoableEdit();
        
    /** Vector holding the marks for the document */
    private final MarkVector markVector;
    
    /** Array with gap holding the text of the document */
    private char[] charArray;

    /** Start index of the gap */
    private int gapStart;

    /** Length of the gap */
    private int gapLength;
    
    DocumentContent() {
        charArray = EMPTY_CHAR_ARRAY;
        markVector = new MarkVector();
        
        // Insert implied '\n'
        insertText(0, "\n"); // NOI18N
    }
    
    public final int getGapStart() { // to implement GapStart
        return gapStart;
    }

    public UndoableEdit insertString(int offset, String text)
    throws BadLocationException {

        checkBounds(offset, 0, length() - 1);
        return new Edit(offset, text);
    }
    
    public UndoableEdit remove(int offset, int length)
    throws BadLocationException {

        checkBounds(offset, length, length() - 1);
        return new Edit(offset, length);
    }

    public Position createPosition(int offset) throws BadLocationException {
        return new BasePosition(createMark(offset));
    }

    public Position createBiasPosition(int offset, Position.Bias bias)
    throws BadLocationException {
        return new BasePosition(createBiasMark(offset, bias));
    }

    MultiMark createBiasMark(int offset, Position.Bias bias) throws BadLocationException {
        checkOffset(offset);
        return markVector.insert(markVector.createBiasMark(offset, bias));
    }

    MultiMark createMark(int offset) throws BadLocationException {
        checkOffset(offset);
        return markVector.insert(markVector.createMark(offset));
    }

    public int length() {
        return charArray.length - gapLength;
    }
    
    public void getChars(int offset, int length, Segment chars)
    throws BadLocationException {

        checkBounds(offset, length, length());
        
        if ((offset + length) <= gapStart) { // completely below gap
            chars.array = charArray;
            chars.offset = offset;
            
        } else if (offset >= gapStart) { // completely above gap
            chars.array = charArray;
            chars.offset = offset + gapLength;
            
        } else { // spans the gap, must copy
            chars.array = copySpanChars(offset, length);
            chars.offset = 0;
        }
        
        chars.count = length;
    }

    public String getString(int offset, int length)
    throws BadLocationException {

        checkBounds(offset, length, length());
        return getText(offset, length);
    }
    
    String getText(int offset, int length) {
        if (offset < 0 || length < 0) {
            throw new IllegalStateException("offset=" + offset + ", length=" + length); // NOI18N
        }

        String ret;
        if ((offset + length) <= gapStart) { // completely below gap
            ret = new String(charArray, offset, length);
            
        } else if (offset >= gapStart) { // completely above gap
            ret = new String(charArray, offset + gapLength, length);
            
        } else { // spans the gap, must copy
            ret = new String(copySpanChars(offset, length));
        }
        
        return ret;
    }

    public char charAt(int index) {
        return charArray[getRawIndex(index)];
    }
    
    void compact() {
        if (gapLength > 0) {
            int newLength = charArray.length - gapLength;
            char[] newCharArray = new char[newLength];
            int gapEnd = gapStart + gapLength;
            System.arraycopy(charArray, 0, newCharArray, 0, gapStart);
            System.arraycopy(charArray, gapEnd, newCharArray, gapStart, 
                charArray.length - gapEnd);
            charArray = newCharArray;
            gapStart = charArray.length;
            gapLength = 0;
        }
        
        markVector.compact();
    }
    
    private int getRawIndex(int index) {
        return (index < gapStart) ? index : (index + gapLength);
    }
    
    private void moveGap(int index) {
        if (index <= gapStart) { // move gap down
            int moveSize = gapStart - index;
            System.arraycopy(charArray, index, charArray,
                gapStart + gapLength - moveSize, moveSize);
            gapStart = index;

        } else { // above gap
            int gapEnd = gapStart + gapLength;
            int moveSize = index - gapStart;
            System.arraycopy(charArray, gapEnd, charArray, gapStart, moveSize);
            gapStart += moveSize;
        }
    }
    
    private void enlargeGap(int extraLength) {
        int newLength = Math.max(10, charArray.length * 3 / 2 + extraLength);
        int gapEnd = gapStart + gapLength;
        int afterGapLength = (charArray.length - gapEnd);
        int newGapEnd = newLength - afterGapLength;
        char[] newCharArray = new char[newLength];
        System.arraycopy(charArray, 0, newCharArray, 0, gapStart);
        System.arraycopy(charArray, gapEnd, newCharArray, newGapEnd, afterGapLength);
        charArray = newCharArray;
        gapLength = newGapEnd - gapStart;
    }

    private char[] copyChars(int offset, int length) {
        char[] ret;
        if ((offset + length) <= gapStart) { // completely below gap
            ret = new char[length];
            System.arraycopy(charArray, offset, ret, 0, length);
            
        } else if (offset >= gapStart) { // completely above gap
            ret = new char[length];
            System.arraycopy(charArray, offset + gapLength, ret, 0, length);
            
        } else { // spans the gap, must copy
            ret = copySpanChars(offset, length);
        }
        
        return ret;
    }

    private char[] copySpanChars(int offset, int length) {
        char[] ret = new char[length];
        int belowGap = gapStart - offset;
        System.arraycopy(charArray, offset, ret, 0, belowGap);
        System.arraycopy(charArray, gapStart + gapLength,
            ret, belowGap, length - belowGap);
        return ret;
    }

    void insertText(int offset, String text) {
        ///*DEBUG*/System.err.println("DocumentContent.insertText(" + offset + ", \"" + text + "\")");
        int textLength = text.length();
        int extraLength = textLength - gapLength;
        if (extraLength > 0) {
            enlargeGap(extraLength);
        }
        if (offset != gapStart) {
            moveGap(offset);
        }
        text.getChars(0, textLength, charArray, gapStart);
        gapStart += textLength;
        gapLength -= textLength;
    }
    
    void removeText(int offset, int length) {
        ///*DEBUG*/System.err.println("DocumentContent.removeText(" + offset + ", " + length + ")");
        if (offset >= gapStart) { // completely over gap
            if (offset > gapStart) {
                moveGap(offset);
            }

        } else { // completely below gap or spans the gap
            int endOffset = offset + length;
            if (endOffset <= gapStart) {
                if (endOffset < gapStart) {
                    moveGap(endOffset);
                }
                gapStart -= length;
                
            } else { // spans gap
                gapStart = offset;
            }
        }

        gapLength += length;
    }
    
    private void checkOffset(int offset) throws BadLocationException {
        if (offset > length()) { // can be doc.getLength() + 1 i.e. getEndPosition()
            throw new BadLocationException("Invalid offset=" + offset // I18N // NOI18N
                + ", docLength=" + (length() - 1), offset); // I18N // NOI18N
        }
    }

    private void checkBounds(int offset, int length, int limitOffset)
    throws BadLocationException {

	if (offset < 0) {
	    throw new BadLocationException("Invalid offset=" + offset, offset); // NOI18N
	}
        if (length < 0) {
            throw new BadLocationException("Invalid length" + length, length); // NOI18N
        }
	if (offset + length > limitOffset) {
	    throw new BadLocationException(
                "docLength=" + (length() - 1) // NOI18N
                + ":  Invalid offset" // NOI18N
                + ((length != 0) ? "+length" : "") // NOI18N
                + "=" + (offset + length), // NOI18N
                (offset + length)
            );
	}
    }
    
    class Edit extends AbstractUndoableEdit {
        
        /** Constructor used for insert.
         * @param offset offset of insert.
         * @param text inserted text.
         */
        Edit(int offset, String text) {
            this.offset = offset;
            this.length = text.length();
            this.text = text;

            undoOrRedo(length, false); // pretend redo
            
        }
        
        /** Constructor used for remove.
         * @param offset offset of remove.
         * @param length length of the removed text.
         */
        Edit(int offset, int length) {
            this.offset = offset;
            this.length = -length;
            
            // Added to make sure the text is not inited later at unappropriate time
            this.text = getText(offset, length);

            undoOrRedo(-length, false); // pretend redo
            
        }
        
        private int offset;
        
        private int length;
        
        private String text;
        
        private MarkVector.Undo markVectorUndo;
        
        public void undo() throws CannotUndoException {
            super.undo();

            undoOrRedo(-length, true);
        }
        
        public void redo() throws CannotRedoException {
            super.redo();

            undoOrRedo(length, false);
        }
        
        private void undoOrRedo(int len, boolean undo) {
            // Fix text content
            if (len < 0) { // do remove
                removeText(offset, -len);
            } else { // do insert
                insertText(offset, text);
            }
            
            // Update marks
            markVectorUndo = markVector.update(offset, len, markVectorUndo);
        }

        /**
         * @return text of the modification.
         */
        final String getUndoRedoText() {
            return text;
        }
        
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy