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-2000 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.lib.editor.fold;

import org.netbeans.editor.SyntaxUpdateTokens;
import org.openide.ErrorManager;

import javax.swing.text.Document;
import javax.swing.text.BadLocationException;
import javax.swing.text.Position;
import javax.swing.event.DocumentEvent;
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import org.netbeans.api.editor.fold.Fold;
import org.netbeans.api.editor.fold.FoldType;
import org.netbeans.spi.editor.fold.FoldHierarchyTransaction;
import org.netbeans.spi.editor.fold.FoldManager;
import org.netbeans.spi.editor.fold.FoldManagerFactory;
import org.netbeans.spi.editor.fold.FoldOperation;

/**
 * Fold maintainer that creates and updates custom folds.
 *
 * @author Dusan Balek, Miloslav Metelka
 * @version 1.00
 */

final class CustomFoldManager implements FoldManager {
    
    private static final boolean debug = false;
    
    public static final FoldType CUSTOM_FOLD_TYPE = new FoldType("custom-fold"); // NOI18N

    private FoldOperation operation;
    private Document doc;
    private org.netbeans.editor.GapObjectArray markArray = new org.netbeans.editor.GapObjectArray();
    private int minUpdateMarkOffset;
    private int maxUpdateMarkOffset;
    private List removedFoldList;
    
    public void init(FoldOperation operation) {
        this.operation = operation;
    }
    
    private FoldOperation getOperation() {
        return operation;
    }

    public void initFolds(FoldHierarchyTransaction transaction) {
        try {
            doc = getOperation().getHierarchy().getComponent().getDocument();
            updateFolds(SyntaxUpdateTokens.getTokenInfoList(doc), transaction);
        } catch (BadLocationException e) {
            ErrorManager.getDefault().notify(e);
        }
    }

    public void insertUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
        try {
            processRemovedFolds(transaction);
            updateFolds(SyntaxUpdateTokens.getTokenInfoList(evt), transaction);
        } catch (BadLocationException e) {
            ErrorManager.getDefault().notify(e);
        }
    }

    public void removeUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
        try {
            processRemovedFolds(transaction);
            removeAffectedMarks(evt, transaction);
            updateFolds(SyntaxUpdateTokens.getTokenInfoList(evt), transaction);
        } catch (BadLocationException e) {
            ErrorManager.getDefault().notify(e);
        }
    }
    
    public void changedUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
    }
    
    public void removeEmptyNotify(Fold emptyFold) {
        removeFoldNotify(emptyFold);
    }
    
    public void removeDamagedNotify(Fold damagedFold) {
        removeFoldNotify(damagedFold);
    }
    
    public void expandNotify(Fold expandedFold) {
        
    }

    public void release() {

    }
    
    private void removeFoldNotify(Fold removedFold) {
        if (removedFoldList == null) {
            removedFoldList = new ArrayList(3);
        }
        removedFoldList.add(removedFold);
    }
    
    private void removeAffectedMarks(DocumentEvent evt, FoldHierarchyTransaction transaction) {
        int removeOffset = evt.getOffset();
        int markIndex = findMarkIndex(removeOffset);
        if (markIndex < getMarkCount()) {
            FoldMarkInfo mark;
            while (markIndex >= 0 && (mark = getMark(markIndex)).getOffset() == removeOffset) {
                mark.release(false, transaction);
                removeMark(markIndex);
                markIndex--;
            }
        }
    }
    
    private void processRemovedFolds(FoldHierarchyTransaction transaction) {
        if (removedFoldList != null) {
            for (int i = removedFoldList.size() - 1; i >= 0; i--) {
                Fold removedFold = (Fold)removedFoldList.get(i);
                FoldMarkInfo startMark = (FoldMarkInfo)getOperation().getExtraInfo(removedFold);
                FoldMarkInfo endMark = startMark.getPairMark(); // get prior releasing
                if (getOperation().isStartDamaged(removedFold)) { // start mark area was damaged
                    startMark.release(true, transaction); // forced remove
                }
                if (getOperation().isEndDamaged(removedFold)) {
                    endMark.release(true, transaction);
                }
            }
        }
        removedFoldList = null;
    }

    private void markUpdate(FoldMarkInfo mark) {
        markUpdate(mark.getOffset());
    }
    
    private void markUpdate(int offset) {
        if (offset < minUpdateMarkOffset) {
            minUpdateMarkOffset = offset;
        }
        if (offset > maxUpdateMarkOffset) {
            maxUpdateMarkOffset = offset;
        }
    }
    
    private FoldMarkInfo getMark(int index) {
        return (FoldMarkInfo)markArray.getItem(index);
    }
    
    private int getMarkCount() {
        return markArray.getItemCount();
    }
    
    private void removeMark(int index) {
        if (debug) {
            /*DEBUG*/System.err.println("Removing mark from ind=" + index // NOI18N
                + ": " + getMark(index)); // NOI18N
        }
        markArray.remove(index, 1);
    }
    
    private void insertMark(int index, FoldMarkInfo mark) {
        markArray.insertItem(index, mark);
        if (debug) {
            /*DEBUG*/System.err.println("Inserted mark at ind=" + index // NOI18N
                + ": " + mark); // NOI18N
        }
    }

    private int findMarkIndex(int offset) {
        int markCount = getMarkCount();
        int low = 0;
        int high = markCount - 1;
        
        while (low <= high) {
            int mid = (low + high) / 2;
            int midMarkOffset = getMark(mid).getOffset();
            
            if (midMarkOffset < offset) {
                low = mid + 1;
            } else if (midMarkOffset > offset) {
                high = mid - 1;
            } else {
                // mark starting exactly at the given offset found
                // If multiple -> find the one with highest index
                mid++;
                while (mid < markCount && getMark(mid).getOffset() == offset) {
                    mid++;
                }
                mid--;
                return mid;
            }
        }
        return low; // return higher index (e.g. for insert)
    }
    
    private List getMarkList(List tokenList) {
        List markList = null;
        int tokenListSize = tokenList.size();
        if (tokenListSize != 0) {
            for (int i = 0; i < tokenListSize; i++) {
                SyntaxUpdateTokens.TokenInfo tokenInfo = (SyntaxUpdateTokens.TokenInfo) tokenList.get(i);
                FoldMarkInfo info;
                try {
                    info = scanToken(tokenInfo);
                } catch (BadLocationException e) {
                    ErrorManager.getDefault().notify(e);
                    info = null;
                }

                if (info != null) {
                    if (markList == null) {
                        markList = new ArrayList();
                    }
                    markList.add(info);
                }
            }
        }
        return markList;
    }
    
    private void processTokenList(List tokenList, FoldHierarchyTransaction transaction) {
        List markList = getMarkList(tokenList);
        int markListSize;
        if (markList != null && ((markListSize = markList.size()) > 0)) {
            // Find the index for insertion
            int offset = ((FoldMarkInfo)markList.get(0)).getOffset();
            int arrayMarkIndex = findMarkIndex(offset);
            // Remember the corresponding mark in the array as well
            FoldMarkInfo arrayMark;
            int arrayMarkOffset;
            if (arrayMarkIndex < getMarkCount()) {
                arrayMark = getMark(arrayMarkIndex);
                arrayMarkOffset = arrayMark.getOffset();
            } else { // at last mark
                arrayMark = null;
                arrayMarkOffset = Integer.MAX_VALUE;
            }

            for (int i = 0; i < markListSize; i++) {
                FoldMarkInfo listMark = (FoldMarkInfo)markList.get(i);
                int listMarkOffset = listMark.getOffset();
                if (i == 0 || i == markListSize - 1) {
                    // Update the update-offsets by the first and last marks in the list
                    markUpdate(listMarkOffset);
                }
                if (listMarkOffset >= arrayMarkOffset) {
                    if (listMarkOffset == arrayMarkOffset) {
                        // At the same offset - likely the same mark
                        //   -> retain the collapsed state
                        listMark.setCollapsed(arrayMark.isCollapsed());
                    }
                    if (!arrayMark.isReleased()) { // make sure that the mark is released
                        arrayMark.release(false, transaction); 
                    }
                    removeMark(arrayMarkIndex);
                    if (debug) {
                        /*DEBUG*/System.err.println("Removed dup mark from ind="
                            + arrayMarkIndex + ": " + arrayMark); // NOI18N
                    }
                    if (arrayMarkIndex < getMarkCount()) {
                        arrayMark = getMark(arrayMarkIndex);
                        arrayMarkOffset = arrayMark.getOffset();
                    } else { // no more marks
                        arrayMark = null;
                        arrayMarkOffset = Integer.MAX_VALUE;
                    }
                }
                // Insert the listmark
                insertMark(arrayMarkIndex, listMark);
                if (debug) {
                    /*DEBUG*/System.err.println("Inserted mark at ind=" // NOI18N
                        + arrayMarkIndex + ": " + listMark); // NOI18N
                }
                arrayMarkIndex++;
            }
        }
    }
    
    private void updateFolds(List tokenList, FoldHierarchyTransaction transaction)
    throws BadLocationException {
        
        if (tokenList.size() > 0) {
            processTokenList(tokenList, transaction);
        }

        if (maxUpdateMarkOffset == -1) { // no updates
            return;
        }
        
        // Find the first mark to update and init the prevMark and parentMark prior the loop
        int index = findMarkIndex(minUpdateMarkOffset);
        FoldMarkInfo prevMark;
        FoldMarkInfo parentMark;
        if (index == 0) { // start from begining
            prevMark = null;
            parentMark = null;
        } else {
            prevMark = getMark(index - 1);
            parentMark = prevMark.getParentMark();
        }
        
        // Iterate through the changed marks in the mark array 
        int markCount = getMarkCount();
        while (index < markCount) { // process the marks
            FoldMarkInfo mark = getMark(index);

            // If the mark was released then it must be removed
            if (mark.isReleased()) {
                if (debug) {
                    /*DEBUG*/System.err.println("Removing released mark at ind=" // NOI18N
                        + index + ": " + mark); // NOI18N
                }
                removeMark(index);
                markCount--;
                continue;
            }

            // Update mark's status (folds, parentMark etc.)
            if (mark.isStartMark()) { // starting a new fold
                if (prevMark == null || prevMark.isStartMark()) { // new level
                    mark.setParentMark(prevMark); // prevMark == null means root level
                    parentMark = prevMark;

                } // same level => parent to the parent of the prevMark

            } else { // end mark
                if (prevMark != null) {
                    if (prevMark.isStartMark()) { // closing nearest fold
                        prevMark.setEndMark(mark, false, transaction);

                    } else { // prevMark is end mark - closing its parent fold
                        if (parentMark != null) {
                            // mark's parent gets set as well
                            parentMark.setEndMark(mark, false, transaction);
                            parentMark = parentMark.getParentMark();

                        } else { // prevMark's parentMark is null (top level)
                            mark.makeSolitaire(false, transaction);
                        }
                    }
                    
                } else { // prevMark is null
                    mark.makeSolitaire(false, transaction);
                }
            }

            // Set parent mark of the mark
            mark.setParentMark(parentMark);

            
            prevMark = mark;
            index++;
        }
        
        minUpdateMarkOffset = Integer.MAX_VALUE;
        maxUpdateMarkOffset = -1;
        
        if (debug) {
            /*DEBUG*/System.err.println("MARKS DUMP:\n" + this);
        }
    }
    
    public String toString() {
        StringBuffer sb = new StringBuffer();
        int markCount = getMarkCount();
        int markCountDigitCount = Integer.toString(markCount).length();
        for (int i = 0; i < markCount; i++) {
            sb.append("["); // NOI18N
            String iStr = Integer.toString(i);
            appendSpaces(sb, markCountDigitCount - iStr.length());
            sb.append(iStr);
            sb.append("]:"); // NOI18N
            FoldMarkInfo mark = getMark(i);
            
            // Add extra indent regarding the depth in hierarchy
            int indent = 0;
            FoldMarkInfo parentMark = mark.getParentMark();
            while (parentMark != null) {
                indent += 4;
                parentMark = parentMark.getParentMark();
            }
            appendSpaces(sb, indent);

            sb.append(mark);
            sb.append('\n');
        }
        return sb.toString();
    }
    
    private static void appendSpaces(StringBuffer sb, int spaces) {
        while (--spaces >= 0) {
            sb.append(' ');
        }
    }

    private static Pattern pattern = Pattern.compile("(<\\s*editor-fold(?:(?:\\s+defaultstate=\"(\\S*)\")?(?:\\s+desc=\"([\\S \\t]*)\")?)(?:\\s+defaultstate=\"(\\S*)\")?\\s*>)|(?:)"); // NOI18N

    private FoldMarkInfo scanToken(SyntaxUpdateTokens.TokenInfo tokenInfo) throws BadLocationException {
        Matcher matcher = pattern.matcher(doc.getText(tokenInfo.getOffset(), tokenInfo.getLength()));
        if (matcher.find()) {
            if (matcher.group(1) != null) { // fold's start mark found
                String state = matcher.group(2);
                if (state == null)
                    state = matcher.group(4);
                return new FoldMarkInfo(true, tokenInfo.getOffset(), tokenInfo.getLength(), "collapsed".equals(state) ? true : false, matcher.group(3)); // NOI18N
            } else { // fold's end mark found
                return new FoldMarkInfo(false, tokenInfo.getOffset(), tokenInfo.getLength(), false, null);
            }
        }
        return null;
    }

    private final class FoldMarkInfo {

        private boolean startMark;
        private Position pos;
        private int length;
        private boolean collapsed;
        private String description;

        /** Matching pair mark used for fold construction */
        private FoldMarkInfo pairMark;
        
        /** Parent mark defining nesting in the mark hierarchy. */
        private FoldMarkInfo parentMark;
        
        /**
         * Fold that corresponds to this mark (if it's start mark).
         * It can be null if this mark is end mark or if it currently
         * does not have the fold assigned.
         */
        private Fold fold;
        
        private boolean released;
        
        public FoldMarkInfo(boolean startMark, int offset,
        int length, boolean collapsed, String description)
        throws BadLocationException {

            this.startMark = startMark;
            this.pos = doc.createPosition(offset);
            this.length = length;
            this.collapsed = collapsed;
            this.description = description;
        }

        public String getDescription() {
            return description;
        }

        public boolean isStartMark() {
            return startMark;
        }

        public int getLength() {
            return length;
        }

        public int getOffset() {
            return pos.getOffset();
        }
        
        public int getEndOffset() {
            return getOffset() + getLength();
        }

        public boolean isCollapsed() {
            return (fold != null) ? fold.isCollapsed() : collapsed;
        }
        
        public boolean hasFold() {
            return (fold != null);
        }
        
        public void setCollapsed(boolean collapsed) {
            this.collapsed = collapsed;
        }
        
        public boolean isSolitaire() {
            return (pairMark == null);
        }
        
        public void makeSolitaire(boolean forced, FoldHierarchyTransaction transaction) {
            if (!isSolitaire()) {
                if (isStartMark()) {
                    setEndMark(null, forced, transaction);
                } else { // end mark
                    getPairMark().setEndMark(null, forced, transaction);
                }
            }
        }
        
        public boolean isReleased() {
            return released;
        }
        
        /**
         * Release this mark and mark for update.
         */
        public void release(boolean forced, FoldHierarchyTransaction transaction) {
            if (!released) {
                makeSolitaire(forced, transaction);
                released = true;
                markUpdate(this);
            }
        }
        
        public FoldMarkInfo getPairMark() {
            return pairMark;
        }
        
        private void setPairMark(FoldMarkInfo pairMark) {
            this.pairMark = pairMark;
        }

        public void setEndMark(FoldMarkInfo endMark, boolean forced,
        FoldHierarchyTransaction transaction) {
            if (!isStartMark()) {
                throw new IllegalStateException("Not start mark"); // NOI18N
            }
            if (pairMark == endMark) {
                return;
            }
            
            if (pairMark != null) { // is currently paired to an end mark
                releaseFold(forced, transaction);
                pairMark.setPairMark(null);
            }

            pairMark = endMark;
            if (endMark != null) {
                if (!endMark.isSolitaire()) { // make solitaire first
                    endMark.makeSolitaire(false, transaction); // not forced here
                }
                endMark.setPairMark(this);
                endMark.setParentMark(this.getParentMark());
                ensureFoldExists(transaction);
            }
        }
        
        public FoldMarkInfo getParentMark() {
            return parentMark;
        }
        
        public void setParentMark(FoldMarkInfo parentMark) {
            this.parentMark = parentMark;
        }
        
        private void releaseFold(boolean forced, FoldHierarchyTransaction transaction) {
            if (isSolitaire() || !isStartMark()) {
               throw new IllegalStateException();
            }

            if (fold != null) {
                setCollapsed(fold.isCollapsed()); // serialize the collapsed info
                if (!forced) {
                    getOperation().removeFromHierarchy(fold, transaction);
                }
                fold = null;
            }
        }

        public Fold getFold() {
            if (isSolitaire()) {
                return null;
            }
            if (!isStartMark()) {
                return pairMark.getFold();
            }
            return fold;
        }
        
        public void ensureFoldExists(FoldHierarchyTransaction transaction) {
            if (isSolitaire() || !isStartMark()) {
                throw new IllegalStateException();
            }

            if (fold == null) {
                try {
                    if (!startMark) {
                        throw new IllegalStateException("Not start mark: " + this); // NOI18N
                    }
                    if (pairMark == null) {
                        throw new IllegalStateException("No pairMark for mark:" + this); // NOI18N
                    }
                    int startOffset = getOffset();
                    int startGuardedLength = getLength();
                    int endGuardedLength = pairMark.getLength();
                    int endOffset = pairMark.getOffset() + endGuardedLength;
                    fold = getOperation().addToHierarchy(
                        CUSTOM_FOLD_TYPE, getDescription(), collapsed,
                        startOffset, endOffset,
                        startGuardedLength, endGuardedLength,
                        this,
                        transaction
                    );
                } catch (BadLocationException e) {
                    ErrorManager.getDefault().notify(e);
                }
            }
        }
        
        public String toString() {
            StringBuffer sb = new StringBuffer();
            sb.append(isStartMark() ? 'S' : 'E');  // NOI18N
            
            // Check whether this mark (or its pair) has fold
            if (hasFold() || (!isSolitaire() && getPairMark().hasFold())) {
                sb.append("F"); // NOI18N
                
                // Check fold's status
                if (isStartMark() && (isSolitaire()
                        || getOffset() != fold.getStartOffset()
                        || getPairMark().getEndOffset() != fold.getEndOffset())
                ) {
                    sb.append("!!<"); // NOI18N
                    sb.append(fold.getStartOffset());
                    sb.append(","); // NOI18N
                    sb.append(fold.getEndOffset());
                    sb.append(">!!"); // NOI18N
                }
            }

            // Append mark's internal status
            sb.append(" ("); // NOI18N
            sb.append("o="); // NOI18N
            sb.append(pos.getOffset());
            sb.append(", l="); // NOI18N
            sb.append(length);
            sb.append(", d='"); // NOI18N
            sb.append(description);
            sb.append('\'');
            if (getPairMark() != null) {
                sb.append(", <->"); // NOI18N
                sb.append(getPairMark().getOffset());
            }
            if (getParentMark() != null) {
                sb.append(", ^"); // NOI18N
                sb.append(getParentMark().getOffset());
            }
            sb.append(')');
            
            return sb.toString();
        }

    }
        
    public static final class Factory implements FoldManagerFactory {

        public FoldManager createFoldManager() {
            return new CustomFoldManager();
        }
    }
}
... 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.