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 java.util.List;
import javax.swing.text.Position;
import java.util.Iterator;

/**
 * Container for {@link MultiMark} for the document.
 * The structure is array with a gap together with an offset gap
 * for the abstract storage of the document text.
 *
 * @author Miloslav Metelka
 * @version 1.00
 */

final class MarkVector {

    /** Empty array of marks used initially as markArray. */
    private static final MultiMark[] EMPTY = new MultiMark[0];
    
    /** Default size of the offset gap. */
    private static final int INITIAL_OFFSET_GAP_SIZE = (Integer.MAX_VALUE >> 1);
    
    /** Array of the marks with the gap. */
    private MultiMark[] markArray;
    
    /** Starting index of the gap in the markArray. */
    private int gapStart;
    
    /** Length of the gap in the markArray.
     * The length of the gap must always be >0.
     */
    private int gapLength;
    
    /** Starting offset of the offset gap */
    private int offsetGapStart;
    
    /** Length of the offset gap */
    private int offsetGapLength;
    
    /** Number of marks that are still in the markArray but 
     * that are no longer valid.
     */
    private int disposedMarkCount;
    
    MarkVector() {
        markArray = EMPTY;
        offsetGapLength = INITIAL_OFFSET_GAP_SIZE;
    }
    
    /** Create mark with the given bias. The mark is not automatically
     * inserted into the vector.
     * @param offset offset of the mark.
     * @param bias bias of the mark.
     */
    MultiMark createBiasMark(int offset, Position.Bias bias) {
        return new MultiMark(this, offset, bias);
    }
    
    /** Create swing-compatible mark. The mark is not automatically
     * inserted into the vector.
     * @param offset offset of the mark.
     */
    MultiMark createMark(int offset) {
        return new MultiMark(this, offset);
    }
    
    /** @return total count of marks in the vector.
     */
    int getMarkCount() {
        return markArray.length - gapLength;
    }
    
    MultiMark getMark(int index) {
        return markArray[getRawIndex(index)];
    }
    
    int getMarkOffsetInternal(int index) {
        return getOffset(getMark(index).rawOffset);
    }

    /** Insert mark previously created by createBiasMark()
     * or createMark().
     * @param mark mark to insert. The mark must have the valid
     *  offset and flags filled in.
     */
    synchronized MultiMark insert(MultiMark mark) {
        int flags = mark.flags;
        if ((flags & MultiMark.VALID) != 0) { // trying to re-insert valid mark
            throw new IllegalStateException();
        }

        int offset = mark.rawOffset;
        int index = findInsertIndex(offset);
        if (gapLength == 0) {
            enlargeGap(1);
        }
        if (index != gapStart) {
            moveGap(index);
        }

        if (offset > offsetGapStart
            || (offset == offsetGapStart
                && ((flags & MultiMark.BACKWARD_BIAS) == 0))
        ) { // above offset gap
            mark.rawOffset += offsetGapLength;
        }

        markArray[gapStart++] = mark;
        gapLength--;
        mark.flags |= MultiMark.VALID;

        return mark;
    }
    
    /** Insert list of marks at once. When inserting
     * a large number of sorted marks (at least partially) this method offers
     * better performance.
     * @param markList list of MultiMarks to insert.
     */
    synchronized void insertList(List markList) {
        int lastOffset = Integer.MAX_VALUE;
        boolean lastBackwardBias = true;
        int upperOffset = 0;
        boolean upperBackwardBias = false;
        int markCount = getMarkCount();
        int insertMarkCount = markList.size();
        
        if (gapLength < insertMarkCount) {
            enlargeGap(insertMarkCount); // enough space for all the marks
        }
        
        for (int i = 0; i < insertMarkCount; i++ ) {
            MultiMark mark = (MultiMark)markList.get(i);
            int flags = mark.flags;
            if ((flags & MultiMark.VALID) != 0) { // trying to re-insert valid mark
                throw new IllegalStateException();
            }
            boolean backwardBias = ((flags & MultiMark.BACKWARD_BIAS) != 0);

            int offset = mark.rawOffset;
            if ((offset < lastOffset)
                || (offset == lastOffset && backwardBias && !lastBackwardBias)
                || (offset > upperOffset)
                || (offset == upperOffset && !backwardBias && upperBackwardBias)
            ) {
                // Find the index and inspect previous/next marks
                int index = findInsertIndex(offset);
                if (index != gapStart) {
                    moveGap(index);
                }
                
                if (index < markCount) {
                    MultiMark m = markArray[getRawIndex(index)];
                    upperOffset = getOffset(m.rawOffset);
                    upperBackwardBias = ((m.flags & MultiMark.BACKWARD_BIAS) != 0);

                } else { // was last mark
                    upperOffset = Integer.MAX_VALUE;
                    upperBackwardBias = false;
                }
            }
                
            if (offset > offsetGapStart
                || (offset == offsetGapStart
                    && ((flags & MultiMark.BACKWARD_BIAS) == 0))
            ) { // above offset gap
                mark.rawOffset += offsetGapLength;
            }
            
            markArray[gapStart++] = mark;
            gapLength--;
            mark.flags |= MultiMark.VALID;

            lastOffset = offset;
            lastBackwardBias = backwardBias;
            markCount++;
        }
    }
    
    void notifyMarkDisposed() {
        disposedMarkCount++;
        
        if (disposedMarkCount > Math.max(5, getMarkCount() / 10)) {
            removeDisposedMarks();
        }
    }

    void compact() {
        if (gapLength > 0) {
            int newLength = markArray.length - gapLength;
            MultiMark[] newMarkArray = new MultiMark[newLength];
            int gapEnd = gapStart + gapLength;
            System.arraycopy(markArray, 0, newMarkArray, 0, gapStart);
            System.arraycopy(markArray, gapEnd, newMarkArray, gapStart, 
                markArray.length - gapEnd);
            markArray = newMarkArray;
            gapStart = markArray.length;
            gapLength = 0;
        }
    }

    /** Document was modified. This means that the ficitonal
     * offset gap must be updated by the modifiaction. The offset gap can
     * possibly be moved and the marks in the moved area must
     * be updated accordingly.
     * @param offset offset of the modification
     * @param length length of added/removed data. If the length is positive
     *  then the insert occured. If the length is negative then
     *  the removal has occured.
     * @param undo undo information to be processed. It can be null
     *  if there is no undo information for this update operation.
     * @return non-null undo record or null in case an insert was done
     *  or there are no marks to be undone.
     */
    synchronized Undo update(int offset, int length, Undo undo) {
        if (length < 0) { // removal occured - temporarily increase offset var
            offset -= length; // move offset after end of removal (length < 0)
        }

        // First move the gap if necessary
        int offsetGapIndex = findInsertIndex(offset);

        moveOffsetGap(offsetGapIndex, offset);
        offsetGapStart += length;
        offsetGapLength -= length;

        if (length >= 0) { // insert performed
            if (undo != null) { // in undo or redo
                /* It's necessary to restore the offsets
                 * of the marks contained in the linked list of undo items.
                 * The 'logicalNext' will allow to find whether
                 * there were some marks added or not
                 * since the time the items being undone were created.
                 * The 'next' determines the original
                 * order of the marks.
                 * It's possible that some marks were already
                 * disposed so these will be ignored
                 * (they are possibly no longer in the array).
                 */
                
                // Process fwd bias marks first
                UndoItem dirFirstItem = undo.fbItem;
                int fbUndoMarkCount = 0;

                /* Eliminate the items (by clearing the mark in them)
                 * of marks that were removed from the vector.
                 */
                while (dirFirstItem != null) {
                    if ((dirFirstItem.mark.flags & MultiMark.REMOVED) == 0) { // valid
                        fbUndoMarkCount++;
                        UndoItem item = dirFirstItem.logicalNext;
                        // Eliminate marks removed from vector
                        while (item != null) {
                            if ((item.mark.flags & MultiMark.REMOVED) == 0) { // valid
                                fbUndoMarkCount++;
                            } else { // mark removed from vector
                                item.mark = null;
                            }

                            item = item.logicalNext;
                        }
                        break;

                    } else { // mark removed from vector
                        dirFirstItem.mark = null;
                    }

                    dirFirstItem = dirFirstItem.logicalNext;
                }
                
                if (dirFirstItem != null) { // some fwd marks to undo
                    // Find the dirFirstItem mark's index
                    MultiMark firstItemMark = dirFirstItem.mark;
                    int index = offsetGapIndex;
                    
                    while (markArray[getRawIndex(index)] != firstItemMark) {
                        index++;
                    }
                    
                    while (--index >= offsetGapIndex) {
                        markArray[getRawIndex(index + fbUndoMarkCount)]
                             = markArray[getRawIndex(index)];
                    }
                }
                
                dirFirstItem = undo.bbItem;
                int bbUndoMarkCount = 0;
                
                /* Eliminate the items (by clearing the mark in them)
                 * of marks that were removed from the vector.
                 */
                while (dirFirstItem != null) {
                    if ((dirFirstItem.mark.flags & MultiMark.REMOVED) == 0) { // valid
                        bbUndoMarkCount++;
                        UndoItem item = dirFirstItem.logicalNext;
                        // Eliminate marks removed from vector
                        while (item != null) {
                            if ((item.mark.flags & MultiMark.REMOVED) == 0) { // valid
                                bbUndoMarkCount++;
                            } else { // mark removed from vector
                                item.mark = null;
                            }
                            item = item.logicalNext;
                        }
                        break;
                        
                    } else { // mark removed from vector
                        dirFirstItem.mark = null;
                    }
                        
                    dirFirstItem = dirFirstItem.logicalNext;
                }

                if (dirFirstItem != null) { // some marks to undo
                    // Find the dirFirstItem's mark
                    MultiMark firstItemMark = dirFirstItem.mark;
                    int index = offsetGapIndex;
                    
                    while (markArray[getRawIndex(--index)] != firstItemMark) {
                    }
                    index++;
                    
                    while (index < offsetGapIndex) {
                        markArray[getRawIndex(index - bbUndoMarkCount)]
                             = markArray[getRawIndex(index)];
                        index++;
                    }
                }


                UndoItem origItem = undo.firstItem;
                /* Rewrite the current bwd and fwd bias marks
                 * by the original ones in the right order
                 */
                offsetGapIndex -= bbUndoMarkCount;
                while (origItem != null) {
                    MultiMark mark = origItem.mark;
                    if (mark != null) {
                        // Undo the offset
                        mark.rawOffset = origItem.undoOffset;
                        markArray[getRawIndex(offsetGapIndex++)] = mark;
                    }

                    origItem = origItem.next;
                }
                
                /* Special case offset == 0 requires handling
                 * of compatible marks.
                 */
                if (offset == 0) {
                    ZeroUndoItem zeroItem = undo.zeroItem;
                    while (zeroItem != null) {
                        MultiMark mark = zeroItem.mark;
                        if ((mark.flags & MultiMark.REMOVED) == 0) {
                            mark.flags &= ~MultiMark.ZERO;
                        }
                        zeroItem = zeroItem.next;
                    }
                }
            }
            
            undo = null;
            
        } else { // remove performed
            /* Marks with backward bias within the whole removed area
             * will be moved to the offset (before offset gap).
             * It is necessary that all the backwardBias marks
             * within the removed area must be placed before all
             * the forwardBias marks within the removed area
             * in order for the structure to work properly.
             * Additionally the marks with the forward bias are placed
             * after current offset gap.
             * The algorithm goes through the marks in the backward direction.
             * It uses the last-backward-bias-mark-index that
             * is -1 unless the first backward-bias-mark is found
             * then it stores its index. If the forward-bias mark
             * is then found it must be exchanged with the currently
             * last backward-bias mark (if upperBBMIndex != -1) and so on
             * for every forward mark found.
             * In order to perform undo later the algorithm generates
             * the linked list of undo items. As the algorithm goes from the end
             * back (each item has next field) the list of items will
             * finally be ordered ascendingly by offset.
             */
            offset += length; // offset back to original value (length < 0)
            UndoItem item = null; // current item in the undo list
            UndoItem fbItem = null; // current forward bias undo item
            UndoItem bbItem = null; // current backward bias undo item
            UndoItem upperBBItem = null; // upper backward bias undo item
            int upperBBMIndex = -1; // index of last backward bias mark
            int offsetAboveGap = offset + offsetGapLength; // use current (updated) gap size
            ZeroUndoItem zeroItem = null;
            
            if (offset == 0) {
                int offsetGapIndexCopy = offsetGapIndex;
                int markCount = getMarkCount();
                while (offsetGapIndexCopy < markCount) {
                    MultiMark mark = markArray[getRawIndex(offsetGapIndexCopy++)];
                    if (mark.rawOffset == offsetAboveGap) {
                        if ((mark.flags & (MultiMark.COMPATIBLE | MultiMark.ZERO))
                            == MultiMark.COMPATIBLE
                        ) {
                            mark.flags |= MultiMark.ZERO;
                            zeroItem = new ZeroUndoItem(mark, zeroItem);
                        }

                    } else { // higher offset
                        break;
                    }
                }
            }
                
            
            while (offsetGapIndex > 0) {
                MultiMark mark = markArray[getRawIndex(--offsetGapIndex)];
                int markOffset = mark.rawOffset; // under offset gap -> real offset
                boolean backwardBias = ((mark.flags & MultiMark.BACKWARD_BIAS) != 0);
                if (markOffset < offset // (all the marks below gap)
                    || (mark.rawOffset == offset && backwardBias)
                ) { // stop at < offset or == offset and backward bias
                    break;
                }
                
                item = new UndoItem(mark, markOffset, item);

                if (backwardBias) {
                    if (bbItem != null) {
                        bbItem.logicalNext = item;
                    } else { // bbItem is null
                        upperBBItem = item;
                        upperBBMIndex = offsetGapIndex;
                    }
                    bbItem = item;

                    // Move mark to offset - will be before offset gap
                    mark.rawOffset = offset;

                } else { // forward bias
                    item.logicalNext = fbItem;
                    fbItem = item;

                    // Mark will be positioned over the offset gap
                    mark.rawOffset = offsetAboveGap;

                    if (upperBBMIndex >= 0) { // backward bias mark(s) exist
                        // exchange this fb mark with upper bb mark
                        int upperBBMRawIndex = getRawIndex(upperBBMIndex--);
                        markArray[getRawIndex(offsetGapIndex)]
                            = markArray[upperBBMRawIndex];

                        markArray[upperBBMRawIndex] = mark;

                        UndoItem upperNext = upperBBItem.logicalNext;
                        if (upperNext != null) {
                            bbItem.logicalNext = upperBBItem;
                            bbItem = upperBBItem;
                            upperBBItem.logicalNext = null;
                            upperBBItem = upperNext;
                        }
                    }
                }
            }
            
            /* It's necessary to handle compatible marks
             * that haven't the ZERO flag set yet
             * and generate ZeroUndoItem for each of them.
             */
            if (offset == 0 && item != null) {
                UndoItem i = item;
                while (i != null) {
                    MultiMark mark = i.mark;
                    if ((mark.flags & (MultiMark.COMPATIBLE | MultiMark.ZERO))
                        == MultiMark.COMPATIBLE
                    ) {
                        mark.flags |= MultiMark.ZERO;
                        zeroItem = new ZeroUndoItem(mark, zeroItem);
                    }
                    i = i.next;
                }
            }
                
            undo = (item != null || zeroItem != null)
                ? new Undo(item, fbItem, upperBBItem, zeroItem)
                : null;

        }
        
        return undo;
    }
    
    private void removeDisposedMarks() {
        int rawIndex = 0;
        int validInd = -1;
        int gapEnd = gapStart + gapLength;

        while (rawIndex < gapStart) {
            MultiMark mark = markArray[rawIndex];
            if ((mark.flags & MultiMark.VALID) != 0) { // valid mark
                if (rawIndex != ++validInd) {
                    markArray[validInd] = mark;
                }

            } else { // mark not valid
                mark.flags |= MultiMark.REMOVED;
            }
            rawIndex++;
        }
        gapStart = validInd + 1;
        
        rawIndex = markArray.length;
        validInd = rawIndex;
        while (--rawIndex >= gapEnd) {
            MultiMark mark = markArray[rawIndex];
            if ((mark.flags & MultiMark.VALID) != 0) { // valid mark
                if (rawIndex != --validInd) {
                    markArray[validInd] = mark;
                }

            } else { // mark not valid
                mark.flags |= MultiMark.REMOVED;
            }
        }
        gapLength = validInd - gapStart;
        
        disposedMarkCount = 0;
    }

    int getOffset(int rawOffset) {
        /* rawOffset == offsetGapStart for backward bias marks
         * forward bias marks go typically over the end of the offset gap.
         * The offsetGapLength must always stay to be >0.
         */
        return (rawOffset <= offsetGapStart)
            ? rawOffset
            : (rawOffset - offsetGapLength);
    }

    private int getRawIndex(int index) {
        return (index < gapStart)
            ? index
            : index + gapLength;
    }
    
    /** Find the index at which it's valid to perform an insert of the new mark.
     * @param offset offset of the mark
     * @return index >= 0 and <=getMarkCount()
     *  in the markArray where the insert of the mark with the given
     *  offset can be done.
     *  
If there are marks with the same offset as the given one * then there are first all the marks with the backward bias * and then all the marks with the forward bias. The method * will return the index of the first mark with forward bias. */ private int findInsertIndex(int offset) { int low = 0; int high = getMarkCount() - 1; while (low <= high) { int index = (low + high) / 2; // mid in the binary search MultiMark mark = markArray[getRawIndex(index)]; int markOffset = getOffset(mark.rawOffset); if (markOffset < offset) { low = index + 1; } else if (markOffset > offset) { high = index - 1; } else { // exact offset found - use bias for positioning if ((mark.flags & MultiMark.BACKWARD_BIAS) != 0) { // bwd bias low = index + 1; } else { // fwd bias high = index - 1; } } } return low; } private void moveGap(int index) { if (index <= gapStart) { // move gap down int moveSize = gapStart - index; System.arraycopy(markArray, index, markArray, gapStart + gapLength - moveSize, moveSize); gapStart = index; } else { // above gap int moveSize = index - gapStart; System.arraycopy(markArray, gapStart + gapLength, markArray, gapStart, moveSize); gapStart += moveSize; } } private void moveOffsetGap(int index, int newOffsetGapStart) { int rawIndex = getRawIndex(index); int markArrayLength = markArray.length; int offset = offsetGapStart; offsetGapStart = newOffsetGapStart; int length = offsetGapLength; if (rawIndex == markArrayLength || markArray[rawIndex].rawOffset > offset ) { // go down to check and fix the marks are below the offset int bound = (rawIndex < gapStart) ? 0 : (gapStart + gapLength); while (true) { while (--rawIndex >= bound) { MultiMark mark = markArray[rawIndex]; if (mark.rawOffset > offset) { mark.rawOffset -= length; } } if (bound > 0) { // shift the bound bound = 0; rawIndex = gapStart; } else { // update gap bounds and break the loop break; } } } else { // go up to check and fix the marks are above the offset int bound = (rawIndex < gapStart) ? gapStart : markArrayLength; while (true) { while (rawIndex < bound) { MultiMark mark = markArray[rawIndex]; if (mark.rawOffset <= offset) { mark.rawOffset += length; } rawIndex++; } if (bound < markArrayLength) { // shift the bound bound = markArrayLength; rawIndex += gapLength; } else { // update gap bounds and break the loop break; } } } } private void enlargeGap(int extraLength) { int newLength = Math.max(8, markArray.length * 3 / 2 + extraLength); int gapEnd = gapStart + gapLength; int afterGapLength = (markArray.length - gapEnd); int newGapEnd = newLength - afterGapLength; MultiMark[] newMarkArray = new MultiMark[newLength]; System.arraycopy(markArray, 0, newMarkArray, 0, gapStart); System.arraycopy(markArray, gapEnd, newMarkArray, newGapEnd, afterGapLength); markArray = newMarkArray; gapLength = newGapEnd - gapStart; } /** @return the array of objects describing * the state of this object for testing and debug purposes. * They are returned in the following order:
     *   markArray
     *   gapStart
     *   gapLength
     *   offsetGapStart
     *   offsetGapLength
     * 
     */
    Object[] toObjects() {
        return new Object[] {
            markArray.clone(),
            new Integer(gapStart),
            new Integer(gapLength),
            new Integer(offsetGapStart),
            new Integer(offsetGapLength)
        };
    }

    /** Get info about this mark vector. */
    public String toString() {
        return "markCount=" + getMarkCount() // NOI18N
            + ", gapStart=" + gapStart // NOI18N
            + ", gapLength=" + gapLength // NOI18N
            + ", offsetGapStart=" + offsetGapStart // NOI18N
            + ", offsetGapLength=" + offsetGapLength; // NOI18N
    }
    
    /** Undo record holding the info about undo items.
     */
    static final class Undo {
        
        Undo(UndoItem firstItem, UndoItem fbItem, UndoItem bbItem,
        ZeroUndoItem zeroItem) {

            this.firstItem = firstItem;
            this.fbItem = fbItem;
            this.bbItem = bbItem;
            this.zeroItem = zeroItem;
        }
        
        UndoItem firstItem;
        
        UndoItem fbItem;
        
        UndoItem bbItem;
        
        ZeroUndoItem zeroItem;

    }

    /** Class used for holding the offset to which the mark
     * will be restored in case it was inside the area 
     * that was removed.
     */
    static final class UndoItem {
        
        UndoItem(MultiMark mark, int undoOffset, UndoItem next) {
            this.mark = mark;
            this.undoOffset = undoOffset;
            this.next = next;
        }
        
        MultiMark mark;
        
        int undoOffset;
        
        UndoItem next;
        
        UndoItem logicalNext;

    }
    
    static final class ZeroUndoItem {
        
        ZeroUndoItem(MultiMark mark, ZeroUndoItem next) {
            this.mark = mark;
            this.next = next;
        }
        
        final MultiMark mark;
        
        final ZeroUndoItem next;

    }

}
... 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.