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

org.netbeans.editor.BaseDocument 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.Hashtable;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Map;
import java.io.Reader;
import java.io.Writer;
import java.io.IOException;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.util.EventListener;
import java.util.HashMap;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Position;
import javax.swing.text.Element;
import javax.swing.text.AttributeSet;
import javax.swing.text.AbstractDocument;
import javax.swing.text.StyleConstants;
import javax.swing.event.EventListenerList;
import javax.swing.event.DocumentEvent;
import javax.swing.event.UndoableEditEvent;
import javax.swing.text.Segment;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.UndoableEdit;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.CannotRedoException;

/**
* Document implementation
*
* @author Miloslav Metelka
* @version 1.00
*/

public class BaseDocument extends AbstractDocument implements SettingsChangeListener, AtomicLockDocument {

    /** Registry identification property */
    public static final String ID_PROP = "id"; // NOI18N

    /** Line separator property for reading files in */
    public static final String READ_LINE_SEPARATOR_PROP
    = DefaultEditorKit.EndOfLineStringProperty;

    /** Line separator property for writing content into files. If not set
     * the writing defaults to the READ_LINE_SEPARATOR_PROP.
     */
    public static final String WRITE_LINE_SEPARATOR_PROP
    = "write-line-separator"; // NOI18N

    /** File name property */
    public static final String FILE_NAME_PROP = "file-name"; // NOI18N

    /** Wrap search mark property */
    public static final String WRAP_SEARCH_MARK_PROP = "wrap-search-mark"; // NOI18N

    /** Undo manager property. This can be used to implement undo
    * in a simple way. Default undo and redo actions try to get this
    * property and perform undo and redo through it.
    */
    public static final String UNDO_MANAGER_PROP = "undo-manager"; // NOI18N

    /** Kit class property. This can become useful for getting
    * the settings that logicaly belonging to the document.
    */
    public static final String KIT_CLASS_PROP = "kit-class"; // NOI18N

    /** String forward finder property */
    public static final String STRING_FINDER_PROP = "string-finder"; // NOI18N

    /** String backward finder property */
    public static final String STRING_BWD_FINDER_PROP = "string-bwd-finder"; // NOI18N

    /** Highlight search finder property. */
    public static final String BLOCKS_FINDER_PROP = "blocks-finder"; // NOI18N

    /** Maximum line width encountered during the initial read operation.
    * This is filled by Analyzer and used by UI to set the correct initial width
    * of the component.
    * Values: java.lang.Integer
    */
    public static final String LINE_LIMIT_PROP = "line-limit"; // NOI18N


    /** Size of the line batch. Line batch can be used at various places
    * especially when processing lines by syntax scanner.
    */
    public static final String LINE_BATCH_SIZE = "line-batch-size"; // NOI18N

    /** Line separator is marked by CR (Macintosh) */
    public static final String  LS_CR = "\r"; // NOI18N

    /** Line separator is marked by LF (Unix) */
    public static final String  LS_LF = "\n"; // NOI18N

    /** Line separator is marked by CR and LF (Windows) */
    public static final String  LS_CRLF = "\r\n"; // NOI18N

    /** Maximum of concurrent read threads (other will wait until
    * one of these will leave).
    */
    private static final int MAX_READ_THREADS = 10;

    /** Write lock without write lock */
    private static final String WRITE_LOCK_MISSING
    = "extWriteUnlock() without extWriteLock()"; // NOI18N

    private static final Object annotationsLock = new Object();
    private static final Object bookmarksLock = new Object();
    private static final Object getVisColFromPosLock = new Object();
    private static final Object getOffsetFromVisColLock = new Object();

    /** Debug modifications performed on the document */
    private static final boolean debug
        = Boolean.getBoolean("netbeans.debug.editor.document"); // NOI18N
    /** Debug the stack of calling of the insert/remove */
    private static final boolean debugStack
        = Boolean.getBoolean("netbeans.debug.editor.document.stack"); // NOI18N

    /** Debug the StreamDescriptionProperty during read() */
    private static final boolean debugRead
        = Boolean.getBoolean("netbeans.debug.editor.document.read"); // NOI18N
    
    public static final ThreadLocal THREAD_LOCAL_LOCK_DEPTH = new ThreadLocal();
    private static final Integer[] lockIntegers
        = new Integer[] {
            null,
            new Integer(1),
            new Integer(2),
            new Integer(3)
        };

    /** How many spaces should be displayed instead of '\t' character */
    private int tabSize = SettingsDefaults.defaultTabSize.intValue();

    /** Size of one indentation level. If this variable is null (value
     * is not set in Settings, then the default algorithm will be used.
     */
    private Integer shiftWidth;

    /** How many times current writer requested writing */
    private int writeDepth;

    /** How many times atomic writer requested writing */
    private int atomicDepth;

    /* Was the document initialized by reading? */
    protected boolean inited;

    /* Was the document modified by doing inert/remove */
    protected boolean modified;

    /** Listener to changes in find support */
    PropertyChangeListener findSupportListener;

    /** Default element - lazily inited */
    protected Element defaultRootElem;

    private SyntaxSupport syntaxSupport;

    /** Layer list for document level layers */
    private DrawLayerList drawLayerList = new DrawLayerList();

    /** Chain of document level bookmarks */
    private MarkChain bookmarkChain;

    /** Reset merging next created undoable edit to the last one. */
    boolean undoMergeReset;

    /** Kit class stored here */
    Class kitClass;

    /** Undo event for atomic events is fired after the successful
    * atomic operation is finished. The changes are stored in this variable
    * during the atomic operation. If the operation is broken, these edits
    * are used to restore previous state.
    */
    private AtomicCompoundEdit atomicEdits;

    private Acceptor identifierAcceptor;

    private Acceptor whitespaceAcceptor;

    private ArrayList syntaxList = new ArrayList();

    /** List of the positions used by storePosition() */
    private ArrayList posList = new ArrayList();

    /** List of the integers marking the free positions in the posList. */
    private ArrayList posFreeList = new ArrayList();

    /** Root element of line elements representation */
    protected LineRootElement lineRootElement;

    /** Last document event to be undone. The field is filled
     * by the lastly done modification undoable edit.
     * BaseDocumentEvent.canUndo() checks this flag.
     */
    UndoableEdit lastModifyUndoEdit; // #8692 check last modify undo edit

    /** List of annotations for this document. */
    private Annotations annotations;

    /** List of bookmarks attached to this document */
    private Bookmarks bookmarks;

    /* Bug #6258 during composing I18N text using input method the Undoable Edit
     * actions must be disabled so only the final push (and not all intermediate
     * ones) of the I18N word will be stored as undoable edit.
     */
     private boolean composedText = false;
    
    /**
     * Map of [multi-mark, Mark-instance] pairs.
     * These multi-marks need to be stored separately and not be undone/redone.
     */
    final Map marks = new HashMap();
    final MarkVector marksStorage = new MarkVector();

    /** Finder for visual x-coord to position conversion */
    private FinderFactory.VisColPosFwdFinder visColPosFwdFinder;

    /** Finder for position to x-coord conversion */
    private FinderFactory.PosVisColFwdFinder posVisColFwdFinder;
    
    /** Atomic lock event instance shared by all the atomic lock firings done for this document */
    private AtomicLockEvent atomicLockEventInstance = new AtomicLockEvent(this);
    
    private FixLineSyntaxState fixLineSyntaxState;
    private UndoableEdit removeUpdateLineUndo;

    private Object[] atomicLockListenerList;

    /** Create base document with a specified syntax.
    * @param kitClass class used to initialize this document with proper settings
    *   category based on the editor kit for which this document is created
    * @param syntax syntax scanner to use with this document
    */
    public BaseDocument(Class kitClass, boolean addToRegistry) {
        super(new DocumentContent());
        this.kitClass = kitClass;

        setDocumentProperties(createDocumentProperties(getDocumentProperties()));

        putProperty(GapStart.class, getDocumentContent());
        
        lineRootElement = new LineRootElement(this);

        settingsChange(null); // initialize variables from settings
        Settings.addSettingsChangeListener(this);

        // Line separators default to platform ones
        putProperty(READ_LINE_SEPARATOR_PROP, Analyzer.getPlatformLS());

        bookmarkChain = new MarkChain(this, DrawLayerFactory.BOOKMARK_LAYER_NAME);

        // Add document draw-layers
        addLayer(new DrawLayerFactory.SyntaxLayer(),
                DrawLayerFactory.SYNTAX_LAYER_VISIBILITY);

        addLayer(new DrawLayerFactory.HighlightSearchLayer(),
                DrawLayerFactory.HIGHLIGHT_SEARCH_LAYER_VISIBILITY);

        addLayer(new DrawLayerFactory.BookmarkLayer(),
                DrawLayerFactory.BOOKMARK_LAYER_VISIBILITY);

        // Additional initialization of the document through the kit
        BaseKit kit = BaseKit.getKit(kitClass);
        if (kit != null) {
            kit.initDocument(this);
        }

        // Possibly add the document to registry
        if (addToRegistry) {
            Registry.addDocument(this); // add if created thru the kit
        }

        // Start listen on find-support
        findSupportListener = new PropertyChangeListener() {
                                  public void propertyChange(PropertyChangeEvent evt) {
                                      findSupportChange(evt);
                                  }
                              };
        FindSupport.getFindSupport().addPropertyChangeListener(findSupportListener);
        findSupportChange(null); // update doc by find settings
    }
    
    private DocumentContent getDocumentContent() {
        return (DocumentContent)getContent();
    }
    
    public CharSeq getText() {
        return getDocumentContent();
    }

    private void findSupportChange(PropertyChangeEvent evt) {
        // set all finders to null
        putProperty(STRING_FINDER_PROP, null);
        putProperty(STRING_BWD_FINDER_PROP, null);
        putProperty(BLOCKS_FINDER_PROP, null);

        DrawLayerFactory.HighlightSearchLayer hsl
        = (DrawLayerFactory.HighlightSearchLayer)findLayer(
              DrawLayerFactory.HIGHLIGHT_SEARCH_LAYER_NAME);

        Boolean b = (Boolean)FindSupport.getFindSupport().getPropertyNoInit(
                        SettingsNames.FIND_HIGHLIGHT_SEARCH);
        hsl.setEnabled((b != null) ? b.booleanValue() : false);

        fireChangedUpdate(createDocumentEvent(0, getLength(),
                                              DocumentEvent.EventType.CHANGE)); // refresh whole document
    }

    /** Called when settings were changed. The method is called
    * also in constructor, so the code must count with the evt being null.
    */
    public void settingsChange(SettingsChangeEvent evt) {
        String settingName = (evt != null) ? evt.getSettingName() : null;

        if (settingName == null || SettingsNames.TAB_SIZE.equals(settingName)) {
            tabSize = SettingsUtil.getPositiveInteger(kitClass, SettingsNames.TAB_SIZE,
                          SettingsDefaults.defaultTabSize);
        }

        if (settingName == null || SettingsNames.INDENT_SHIFT_WIDTH.equals(settingName)) {
            Object shw = Settings.getValue(kitClass, SettingsNames.INDENT_SHIFT_WIDTH);
            if (shw instanceof Integer) { // currently only Integer values are supported
                shiftWidth = (Integer)shw;
            }
        }
        
        if (settingName == null || SettingsNames.READ_BUFFER_SIZE.equals(settingName)) {
            int readBufferSize = SettingsUtil.getPositiveInteger(kitClass,
                                 SettingsNames.READ_BUFFER_SIZE, SettingsDefaults.defaultReadBufferSize);
            putProperty(SettingsNames.READ_BUFFER_SIZE, new Integer(readBufferSize));
        }

        if (settingName == null || SettingsNames.WRITE_BUFFER_SIZE.equals(settingName)) {
            int writeBufferSize = SettingsUtil.getPositiveInteger(kitClass,
                                  SettingsNames.WRITE_BUFFER_SIZE, SettingsDefaults.defaultWriteBufferSize);
            putProperty(SettingsNames.WRITE_BUFFER_SIZE, new Integer(writeBufferSize));
        }

        if (settingName == null || SettingsNames.MARK_DISTANCE.equals(settingName)) {
            int markDistance = SettingsUtil.getPositiveInteger(kitClass,
                               SettingsNames.MARK_DISTANCE, SettingsDefaults.defaultMarkDistance);
            putProperty(SettingsNames.MARK_DISTANCE, new Integer(markDistance));
        }

        if (settingName == null || SettingsNames.MAX_MARK_DISTANCE.equals(settingName)) {
            int maxMarkDistance = SettingsUtil.getPositiveInteger(kitClass,
                                  SettingsNames.MAX_MARK_DISTANCE, SettingsDefaults.defaultMaxMarkDistance);
            putProperty(SettingsNames.MAX_MARK_DISTANCE, new Integer(maxMarkDistance));
        }

        if (settingName == null || SettingsNames.MIN_MARK_DISTANCE.equals(settingName)) {
            int minMarkDistance = SettingsUtil.getPositiveInteger(kitClass,
                                  SettingsNames.MIN_MARK_DISTANCE, SettingsDefaults.defaultMinMarkDistance);
            putProperty(SettingsNames.MIN_MARK_DISTANCE, new Integer(minMarkDistance));
        }

        if (settingName == null || SettingsNames.READ_MARK_DISTANCE.equals(settingName)) {
            int readMarkDistance = SettingsUtil.getPositiveInteger(kitClass,
                                   SettingsNames.READ_MARK_DISTANCE, SettingsDefaults.defaultReadMarkDistance);
            putProperty(SettingsNames.READ_MARK_DISTANCE, new Integer(readMarkDistance));
        }

        if (settingName == null || SettingsNames.SYNTAX_UPDATE_BATCH_SIZE.equals(settingName)) {
            int syntaxUpdateBatchSize = SettingsUtil.getPositiveInteger(kitClass,
                                        SettingsNames.SYNTAX_UPDATE_BATCH_SIZE, SettingsDefaults.defaultSyntaxUpdateBatchSize);
            putProperty(SettingsNames.SYNTAX_UPDATE_BATCH_SIZE, new Integer(syntaxUpdateBatchSize));
        }

        if (settingName == null || SettingsNames.LINE_BATCH_SIZE.equals(settingName)) {
            int lineBatchSize = SettingsUtil.getPositiveInteger(kitClass,
                                SettingsNames.LINE_BATCH_SIZE, SettingsDefaults.defaultLineBatchSize);
            putProperty(SettingsNames.LINE_BATCH_SIZE, new Integer(lineBatchSize));
        }

        if (settingName == null || SettingsNames.IDENTIFIER_ACCEPTOR.equals(settingName)) {
            identifierAcceptor = SettingsUtil.getAcceptor(kitClass,
                                 SettingsNames.IDENTIFIER_ACCEPTOR, AcceptorFactory.LETTER_DIGIT);
        }

        if (settingName == null || SettingsNames.WHITESPACE_ACCEPTOR.equals(settingName)) {
            whitespaceAcceptor = SettingsUtil.getAcceptor(kitClass,
                                 SettingsNames.WHITESPACE_ACCEPTOR, AcceptorFactory.WHITESPACE);
        }

        boolean stopOnEOL = SettingsUtil.getBoolean(kitClass,
                            SettingsNames.WORD_MOVE_NEWLINE_STOP, true);
        if (settingName == null || SettingsNames.NEXT_WORD_FINDER.equals(settingName)) {
            putProperty(SettingsNames.NEXT_WORD_FINDER,
                        SettingsUtil.getValue(kitClass, SettingsNames.NEXT_WORD_FINDER,
                                              new FinderFactory.NextWordFwdFinder(this, stopOnEOL, false)));
        }

        if (settingName == null || SettingsNames.PREVIOUS_WORD_FINDER.equals(settingName)) {
            putProperty(SettingsNames.PREVIOUS_WORD_FINDER,
                        SettingsUtil.getValue(kitClass, SettingsNames.PREVIOUS_WORD_FINDER,
                                              new FinderFactory.PreviousWordBwdFinder(this, stopOnEOL, false)));
        }

    }

    Syntax getFreeSyntax() {
        synchronized (syntaxList) {
            int cnt = syntaxList.size();
            return (cnt > 0) ? (Syntax)syntaxList.remove(cnt - 1)
                   : BaseKit.getKit(kitClass).createSyntax(this);
        }
    }

    void releaseSyntax(Syntax syntax) {
        synchronized (syntaxList) {
            syntaxList.add(syntax);
        }
    }

    /** Get the formatter for this document. */
    public Formatter getFormatter() {
        return Formatter.getFormatter(kitClass);
    }

    public SyntaxSupport getSyntaxSupport() {
        if (syntaxSupport == null) {
            syntaxSupport = BaseKit.getKit(kitClass).createSyntaxSupport(this);
        }
        return syntaxSupport;
    }
    
    /** Perform any generic text processing. The advantage of this method
    * is that it allows the text to processed in line batches. The initial
    * size of the batch is given by the SettingsNames.LINE_BATCH_SIZE.
    * The TextBatchProcessor.processTextBatch() method is called for every
    * text batch. If the method returns true, it means the processing should
    * continue with the next batch of text which will have double line count
    * compared to the previous one. This guarantees there will be not too many
    * batches so the processing should be more efficient.
    * @param tbp text batch processor to be used to process the text batches
    * @param startPos starting position of the processing.
    * @param endPos ending position of the processing. This can be -1 to signal
    *   the end of document. If the endPos is lower than startPos then the batches
    *   are created in the backward direction.
    * @return the returned value from the last tpb.processTextBatch() call.
    *   The -1 will be returned for (startPos == endPos).
    */
    public int processText(TextBatchProcessor tbp, int startPos, int endPos)
    throws BadLocationException {
        if (endPos == -1) {
            endPos = getLength();
        }
        int batchLineCnt = ((Integer)getProperty(SettingsNames.LINE_BATCH_SIZE)).intValue();
        int batchStart = startPos;
        int ret = -1;
        if (startPos < endPos) { // batching in forward direction
            while (ret < 0 && batchStart < endPos) {
                int batchEnd = Math.min(Utilities.getRowStart(this, batchStart, batchLineCnt), endPos);
                if (batchEnd == -1) { // getRowStart() returned -1
                    batchEnd = endPos;
                }
                ret = tbp.processTextBatch(this, batchStart, batchEnd, (batchEnd == endPos));
                batchLineCnt *= 2; // double the scanned area
                batchStart = batchEnd;
            }
        } else {
            while (ret < 0 && batchStart > endPos) {
                int batchEnd = Math.max(Utilities.getRowStart(this, batchStart, -batchLineCnt), endPos);
                ret = tbp.processTextBatch(this, batchStart, batchEnd, (batchEnd == endPos));
                batchLineCnt *= 2; // double the scanned area
                batchStart = batchEnd;
            }
        }
        return ret;
    }


    public boolean isIdentifierPart(char ch) {
        return identifierAcceptor.accept(ch);
    }

    public boolean isWhitespace(char ch) {
        return whitespaceAcceptor.accept(ch);
    }

    /** Create the mark for the given position
    * and store it in the list. The position can
    * be later retrieved through its ID.
    */
    int storePosition(int pos) throws BadLocationException {
        MultiMark mark = getDocumentContent().createBiasMark(pos, Position.Bias.Forward);
        int ind;
        if (posFreeList.size() > 0) {
            ind = ((Integer)posFreeList.remove(posFreeList.size() - 1)).intValue();
            posList.set(ind, mark);
        } else { // no free indexes
            ind = posList.size();
            posList.add(mark);
        }
        return ind;
    }

    int getStoredPosition(int posID) {
        if (posID < 0 || posID >= posList.size()) {
            return -1;
        }
        MultiMark mark = (MultiMark)posList.get(posID);
        return (mark != null) ? mark.getOffset() : -1;
    }

    void removeStoredPosition(int posID) {
        if (posID >= 0 || posID < posList.size()) {
            Mark mark = (Mark)posList.get(posID);
            posList.set(posID, null); // clear the index
            posFreeList.add(new Integer(posID));

            // Remove the mark #19429
            try {
                mark.remove();
            } catch (InvalidMarkException e) {
            }
        }
    }

    /** Inserts string into document */
    public void insertString(int offset, String text, AttributeSet a)
    throws BadLocationException {
        if (text == null || text.length() == 0) {
            return;
        }

        // Check offset correctness
        if (offset < 0 || offset > getLength()) {
            throw new BadLocationException("Wrong insert position " + offset, offset); // NOI18N
        }

        // possible CR-LF conversion
        text = Analyzer.convertLSToLF(text);

        // Perform the insert
        extWriteLock();
        try {

            preInsertCheck(offset, text, a);

            // Do the real insert into the content
            UndoableEdit edit = getContent().insertString(offset, text);

            if (debug) {
                System.err.println("BaseDocument.insertString(): doc=" + this // NOI18N
                    + (modified ? "" : " - first modification") // NOI18N
                    + ", offset=" + offset // NOI18N
                    + ", text='" + text + "'" // NOI18N
                );
            }
            if (debugStack) {
                Thread.dumpStack();
            }

            BaseDocumentEvent evt = createDocumentEvent(offset, text.length(), DocumentEvent.EventType.INSERT);

            preInsertUpdate(evt, a);

            if (edit != null) {
                evt.addEdit(edit);
                
                lastModifyUndoEdit = edit; // #8692 check last modify undo edit
            }

            modified = true;

            if (atomicDepth > 0) {
                if (atomicEdits == null) {
                    atomicEdits = new AtomicCompoundEdit();
                }
                atomicEdits.addEdit(evt); // will be added
            }

            insertUpdate(evt, a);

            evt.end();

            fireInsertUpdate(evt);

            boolean isComposedText = ((a != null)
                                      && (a.isDefined(StyleConstants.ComposedTextAttribute)));

            if (composedText && !isComposedText)
                composedText = false;
            if (!composedText && isComposedText)
                composedText = true;
            
            if (atomicDepth == 0 && !isComposedText) { // !!! check
                fireUndoableEditUpdate(new UndoableEditEvent(this, evt));
            }
        } finally {
            extWriteUnlock();
        }
    }
    
    /** Removes portion of a document */
    public void remove(int offset, int len) throws BadLocationException {
        if (len > 0) {
            extWriteLock();
            try {
                int docLen = getLength();
                if (offset < 0 || offset > docLen) {
                    throw new BadLocationException("Wrong remove position " + offset, offset); // NOI18N
                }
                if (offset + len > docLen) {
                    throw new BadLocationException("End offset of removed text " // NOI18N
                        + (offset + len) + " > getLength()=" + docLen, // NOI18N
                        offset + len
                    ); // NOI18N
                }

                preRemoveCheck(offset, len);

                BaseDocumentEvent evt = createDocumentEvent(offset, len, DocumentEvent.EventType.REMOVE);

                removeUpdate(evt);

                UndoableEdit edit = getContent().remove(offset, len);
                if (edit != null) {
                    evt.addEdit(edit);
                
                    lastModifyUndoEdit = edit; // #8692 check last modify undo edit
                }

                if (debug) {
                    System.err.println("BaseDocument.remove(): doc=" + this // NOI18N
                        + ", offset=" + offset + ", len=" + len); // NOI18N
                }
                if (debugStack) {
                    Thread.dumpStack();
                }

                if (atomicDepth > 0) { // add edits as soon as possible
                    if (atomicEdits == null) {
                        atomicEdits = new AtomicCompoundEdit();
                    }
                    atomicEdits.addEdit(evt); // will be added
                }

                postRemoveUpdate(evt);

                evt.end();

                fireRemoveUpdate(evt);
                if (atomicDepth == 0 && !composedText) {
                    fireUndoableEditUpdate(new UndoableEditEvent(this, evt));
                }
            } finally {
                extWriteUnlock();
            }
        }
    }

    /** This method is called automatically before the document
    * insertion occurs and can be used to revoke the insertion before it occurs
    * by throwing the BadLocationException.
    * @param offset position where the insertion will be done
    * @param text string to be inserted
    * @param a attributes of the inserted text
    */
    protected void preInsertCheck(int offset, String text, AttributeSet a)
    throws BadLocationException {
    }

    /** This method is called automatically before the document
    * removal occurs and can be used to revoke the removal before it occurs
    * by throwing the BadLocationException.
    * @param offset position where the insertion will be done
    * @param len length of the removal
    */
    protected void preRemoveCheck(int offset, int len)
    throws BadLocationException {
    }

    protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
        super.insertUpdate(chng, attr);
    
        MarksStorageUndo marksStorageUndo = new MarksStorageUndo(chng);
        marksStorageUndo.updateMarksStorage();
        chng.addEdit(marksStorageUndo); // fix compatible marks

        UndoableEdit lineUndo = lineRootElement.insertUpdate(chng.getOffset(), chng.getLength());
        if (lineUndo != null) {
            chng.addEdit(lineUndo);
        }
        
        fixLineSyntaxState.update(false);
        chng.addEdit(fixLineSyntaxState.createAfterLineUndo());
        fixLineSyntaxState = null;
    }
    
    protected void preInsertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
        fixLineSyntaxState = new FixLineSyntaxState(chng);
        chng.addEdit(fixLineSyntaxState.createBeforeLineUndo());
    }

    protected void removeUpdate(DefaultDocumentEvent chng) {
        super.removeUpdate(chng);

        // Remember the line changes here but add them to chng during postRemoveUpdate()
        removeUpdateLineUndo = lineRootElement.removeUpdate(chng.getOffset(), chng.getLength());
        
        fixLineSyntaxState = new FixLineSyntaxState(chng);
        chng.addEdit(fixLineSyntaxState.createBeforeLineUndo());
    }

    protected void postRemoveUpdate(DefaultDocumentEvent chng) {
        super.postRemoveUpdate(chng);

        MarksStorageUndo marksStorageUndo = new MarksStorageUndo(chng);
        marksStorageUndo.updateMarksStorage();
        chng.addEdit(marksStorageUndo); // fix compatible marks

        if (removeUpdateLineUndo != null) {
            chng.addEdit(removeUpdateLineUndo);
            removeUpdateLineUndo = null;
        }
        
        fixLineSyntaxState.update(false);
        chng.addEdit(fixLineSyntaxState.createAfterLineUndo());
        fixLineSyntaxState = null;
    }

    public String getText(int[] block) throws BadLocationException {
        return getText(block[0], block[1] - block[0]);
    }

    /**
     * @param pos position of the first character to get.
     * @param len number of characters to obtain.
     * @return array with the requested characters.
     */
    public char[] getChars(int pos, int len) throws BadLocationException {
        char[] chars = new char[len];
        getChars(pos, chars, 0, len);
        return chars;
    }

    /**
     * @param block two-element array with starting and ending offset
     * @return array with the requested characters.
     */
    public char[] getChars(int[] block) throws BadLocationException {
        return getChars(block[0], block[1] - block[0]);
    }

    /**
     * @param pos position of the first character to get.
     * @param ret destination array
     * @param offset offset in the destination array.
     * @param len number of characters to obtain.
     * @return array with the requested characters.
     */
    public void getChars(int pos, char ret[], int offset, int len)
    throws BadLocationException {
        DocumentUtilities.copyText(this, pos, pos + len, ret, offset);
    }

    /** Find something in document using a finder.
    * @param finder finder to be used for the search
    * @param startPos position in the document where the search will start
    * @param limitPos position where the search will be end with reporting
    *   that nothing was found.
    */
    public int find(Finder finder, int startPos, int limitPos)
    throws BadLocationException {
        int docLen = getLength();
        if (limitPos == -1) {
            limitPos = docLen;
        }
        if (startPos == -1) {
            startPos = docLen;
        }

        if (finder instanceof AdjustFinder) {
            if (startPos == limitPos) { // stop immediately
                finder.reset(); // reset() should be called in all the cases
                return -1; // must stop here because wouldn't know if fwd/bwd search?
            }

            boolean forwardAdjustedSearch = (startPos < limitPos);
            startPos = ((AdjustFinder)finder).adjustStartPos(this, startPos);
            limitPos = ((AdjustFinder)finder).adjustLimitPos(this, limitPos);
            boolean voidSearch = (forwardAdjustedSearch ? (startPos >= limitPos) : (startPos <= limitPos));
            if (voidSearch) {
                finder.reset();
                return -1;
            }
        }

        finder.reset();
        if (startPos == limitPos) {
            return -1;
        }

        Segment text = DocumentUtilities.SEGMENT_CACHE.getSegment();
        try {
            int gapStart = DocumentUtilities.getGapStart(this);
            if (gapStart == -1) {
                throw new IllegalStateException("Cannot get gapStart"); // NOI18N
            }

            int pos = startPos; // pos at which the search starts (continues)
            boolean fwdSearch = (startPos <= limitPos); // forward search
            if (fwdSearch) {
                while (pos >= startPos && pos < limitPos) {
                    int p0; // low bound
                    int p1; // upper bound
                    if (pos < gapStart) { // part below gap
                        p0 = startPos;
                        p1 = Math.min(gapStart, limitPos);
                    } else { // part above gap
                        p0 = Math.max(gapStart, startPos);
                        p1 = limitPos;
                    }
                    
                    getText(p0, p1 - p0, text);
                    pos = finder.find(p0 - text.offset, text.array,
                            text.offset, text.offset + text.count, pos, limitPos);
                    
                    if (finder.isFound()) {
                        return pos;
                    }
                }

            } else { // backward search limitPos < startPos
                pos--; // start one char below the upper bound
                while (limitPos <= pos && pos <= startPos) {
                    int p0; // low bound
                    int p1; // upper bound
                    if (pos < gapStart) { // part below gap
                        p0 = limitPos;
                        p1 = Math.min(gapStart, startPos);
                    } else { // part above gap
                        p0 = Math.max(gapStart, limitPos);
                        p1 = startPos;
                    }
                    
                    getText(p0, p1 - p0, text);
                    pos = finder.find(p0 - text.offset, text.array,
                            text.offset, text.offset + text.count, pos, limitPos);
                    
                    if (finder.isFound()) {
                        return pos;
                    }
                }
            }
            
            return -1; // position outside bounds => not found
    
        } finally {
            DocumentUtilities.SEGMENT_CACHE.releaseSegment(text);
        }
    }

    /** Fire the change event to repaint the given block of text. */
    public void repaintBlock(int startOffset, int endOffset) {
        BaseDocumentEvent evt = createDocumentEvent(startOffset,
                endOffset - startOffset, DocumentEvent.EventType.CHANGE);
        fireChangedUpdate(evt);
    }

    public void print(PrintContainer container) {
        print(container, true, true,0,getLength());
    }

    /**
     * Print into given container.
     *
     * @param container printing container into which the printing will be done.
     * @param usePrintColoringMap use printing coloring settings instead
     *  of the regular ones.
     * @param lineNumberEnabled if set to false the line numbers will not be printed.
     *  If set to true the visibility of line numbers depends on the settings
     *  for the line number visibility.
     * @param startOffset start offset of text to print
     * @param endOffset end offset of text to print
     */
    public void print(PrintContainer container, boolean usePrintColoringMap, boolean lineNumberEnabled, int startOffset,
                      int endOffset) {
        readLock();
        try {
            EditorUI editorUI = BaseKit.getKit(kitClass).createPrintEditorUI(this,
                usePrintColoringMap, lineNumberEnabled);

            DrawGraphics.PrintDG printDG = new DrawGraphics.PrintDG(container);
            DrawEngine.getDrawEngine().draw(printDG, editorUI, startOffset, endOffset, 0, 0, Integer.MAX_VALUE);
        } catch (BadLocationException e) {
            e.printStackTrace();
        } finally {
            readUnlock();
        }
    }

    /** Create biased position in document */
    public Position createPosition(int offset, Position.Bias bias)
    throws BadLocationException {
        return getDocumentContent().createBiasPosition(offset, bias);
    }
    
    MultiMark createMark(int offset) throws BadLocationException {
        return getDocumentContent().createMark(offset);
    }
    
    MultiMark createBiasMark(int offset, Position.Bias bias) throws BadLocationException {
        return getDocumentContent().createBiasMark(offset, bias);
    }
    
    /** Return array of root elements - usually only one */
    public Element[] getRootElements() {
        Element[] elems = new Element[1];
        elems[0] = getDefaultRootElement();
        return elems;
    }

    /** Return default root element */
    public Element getDefaultRootElement() {
        if (defaultRootElem == null) {
            defaultRootElem = getLineRootElement();
        }
        return defaultRootElem;
    }

    /** Runs the runnable under read lock. */
    public void render(Runnable r) {
        readLock();
        assert incrementThreadLocalLockDepth();
        try {
            r.run();
        } finally {
            assert decrementThreadLocalLockDepth();
            readUnlock();
        }
    }
    
    private boolean incrementThreadLocalLockDepth() {
        Integer depthInteger = (Integer)THREAD_LOCAL_LOCK_DEPTH.get();
        if (depthInteger == null) {
            depthInteger = lockIntegers[1];
        } else {
            int newDepth = depthInteger.intValue() + 1;
            depthInteger = (newDepth < lockIntegers.length)
                    ? lockIntegers[newDepth]
                    : new Integer(newDepth);
        }
        THREAD_LOCAL_LOCK_DEPTH.set(depthInteger);
        return true;
    }
    
    private boolean decrementThreadLocalLockDepth() {
        Integer depthInteger = (Integer)THREAD_LOCAL_LOCK_DEPTH.get();
        assert (depthInteger != null);
        int newDepth = depthInteger.intValue() - 1;
        assert (newDepth >= 0);
        THREAD_LOCAL_LOCK_DEPTH.set(
            (newDepth < lockIntegers.length)
                ? lockIntegers[newDepth]
                : new Integer(newDepth)
        );
        return true;
    }

    /** Runs the runnable under write lock. This is a stronger version
    * of the runAtomicAsUser() method, because if there any locked sections
    * in the documents this methods breaks the modification locks and modifies
    * the document.
    * If there are any excpeptions thrown during the processing of the runnable,
    * all the document modifications are rolled back automatically.
    */
    public void runAtomic(Runnable r) {
        runAtomicAsUser(r);
    }

    /** Runs the runnable under write lock.
    * If there are any excpeptions thrown during the processing of the runnable,
    * all the document modifications are rolled back automatically.
    */
    public void runAtomicAsUser(Runnable r) {
        boolean completed = false;
        atomicLock();
        try {
            r.run();
            completed = true;
        } finally {
            try {
                if (!completed) {
                    breakAtomicLock();
                }
            } finally {
                atomicUnlock();
            }
        }
    }

    /** Insert contents of reader at specified position into document.
    * @param reader reader from which data will be read
    * @param pos on which position that data will be inserted
    */
    public void read(Reader reader, int pos)
    throws IOException, BadLocationException {
        extWriteLock();
        try {

            if (pos < 0 || pos > getLength()) {
                throw new BadLocationException("BaseDocument.read()", pos); // NOI18N
            }

            if (inited || modified) { // was the document already initialized?
                Analyzer.read(this, reader, pos);
            } else { // not initialized yet, we can use initialRead()
                Analyzer.initialRead(this, reader, true);
                inited = true; // initialized but not modified
            }
            if (debugRead) {
                System.err.println("BaseDocument.read(): StreamDescriptionProperty: "+getProperty(StreamDescriptionProperty));
            }
        } finally {
            extWriteUnlock();
        }
    }

    /** Write part of the document into specified writer.
    * @param writer writer into which data will be written.
    * @param pos from which position get the data
    * @param len how many characters write
    */
    public void write(Writer writer, int pos, int len)
    throws IOException, BadLocationException {
        readLock();
        try {

            if ((pos < 0) || ((pos + len) > getLength())) {
                throw new BadLocationException("BaseDocument.write()", pos); // NOI18N
            }
            Analyzer.write(this, writer, pos, len);
            writer.flush();
        } finally {
            readUnlock();
        }
    }

    /** Invalidate the state-infos in all the syntax-marks
     * in the whole document. The Syntax can call this method
     * if it changes its internal state in the way that affects
     * the future returned tokens. The syntax-state-info in all
     * the marks is reset and it will be lazily restored when necessary.
     */
    public void invalidateSyntaxMarks() {
        extWriteLock();
        try {
            FixLineSyntaxState.invalidateAllSyntaxStateInfos(this);
            repaintBlock(0, getLength());
        } finally {
          extWriteUnlock();
        }
    }

    /** Get the number of spaces the TAB character ('\t') visually represents. 
     * This is related to SettingsNames.TAB_SIZE setting.
     */
    public int getTabSize() {
        return tabSize;
    }
    
    /** Get the width of one indentation level.
     * The algorithm first checks whether there's a value for the INDENT_SHIFT_WIDTH
     * setting. If so it uses it, otherwise it uses formatter.getSpacesPerTab().
     * 
     * @see getTabSize()
     * @see Formatter.getSpacesPerTab()
     */
    public int getShiftWidth() {
        if (shiftWidth != null) {
            return shiftWidth.intValue();

        } else {
            return getFormatter().getSpacesPerTab();
        }
    }

    public final Class getKitClass() {
        return kitClass;
    }

    /** This method prohibits merging of the next document modification
    * with the previous one even if it would be normally possible.
    */
    public void resetUndoMerge() {
        undoMergeReset = true;
    }

    /* Defined because of the hack for undo()
     * in the BaseDocumentEvent.
     */
    protected void fireChangedUpdate(DocumentEvent e) {
        super.fireChangedUpdate(e);
    }
    protected void fireInsertUpdate(DocumentEvent e) {
        super.fireInsertUpdate(e);
    }
    protected void fireRemoveUpdate(DocumentEvent e) {
        super.fireRemoveUpdate(e);
    }

    protected void fireUndoableEditUpdate(UndoableEditEvent e) {
	// Fire to the list of listeners that was used before the atomic lock started
        // This fixes issue #47881 and appears to be somewhat more logical
        // than the default approach to fire all the current listeners
	Object[] listeners = (atomicLockListenerList != null)
            ? atomicLockListenerList
            : listenerList.getListenerList();

	for (int i = listeners.length - 2; i >= 0; i -= 2) {
	    if (listeners[i] == UndoableEditListener.class) {
		((UndoableEditListener)listeners[i + 1]).undoableEditHappened(e);
	    }	       
	}
    }

    /** Extended write locking of the document allowing
    * reentrant write lock acquiring.
    */
    public synchronized final void extWriteLock() {
        if (Thread.currentThread() != getCurrentWriter()) {
            super.writeLock();
            assert incrementThreadLocalLockDepth();
        } else { // inner locking block
            writeDepth++; // only increase write deepness
        }
    }

    /** Extended write unlocking.
    * @see extWriteLock()
    */
    public synchronized final void extWriteUnlock() {
        if (Thread.currentThread() != getCurrentWriter()) {
            throw new RuntimeException(WRITE_LOCK_MISSING);
        }

        if (writeDepth == 0) { // most outer locking block
            assert decrementThreadLocalLockDepth();
            super.writeUnlock();
        } else { // just inner locking block
            writeDepth--;
        }
    }

    public synchronized final void atomicLock() {
        extWriteLock();
        atomicDepth++;
        if (atomicDepth == 1) { // lock really started
            fireAtomicLock(atomicLockEventInstance);
            // Copy the listener list - will be used for firing undo
            atomicLockListenerList = listenerList.getListenerList();
        }
    }

    public synchronized final void atomicUnlock() {
        atomicUnlockImpl(false);
    }

    public synchronized final void atomicUnlockImpl(boolean inUndoOrRedo) {
        extWriteUnlock();
        if (atomicDepth == 0) {
            throw new IllegalStateException("atomicUnlock() without atomicLock()"); // NOI18N
        }
        
        
        if (--atomicDepth == 0) { // lock really ended
            fireAtomicUnlock(atomicLockEventInstance);

            if (!inUndoOrRedo && atomicEdits != null && atomicEdits.size() > 0) {
                atomicEdits.end();
                fireUndoableEditUpdate(new UndoableEditEvent(this, atomicEdits));
                atomicEdits = null;
            }
            atomicLockListenerList = null;
        }
    }

    /** Is the document currently atomically locked?
    * It's not synced as this method must be called only from writer thread.
    */
    public final boolean isAtomicLock() {
        return (atomicDepth > 0);
    }

    /** Break the atomic lock so that doc is no longer in atomic mode.
    * All the performed changes are rolled back automatically.
    * Even after calling this method, the atomicUnlock() must still be called.
    * This method is not synced as it must be called only from writer thread.
    */
    public final void breakAtomicLock() {
        if (atomicEdits != null && atomicEdits.size() > 0) {
            atomicEdits.end();
            atomicEdits.undo();
            atomicEdits = null;
        }
    }

    public void atomicUndo() {
        breakAtomicLock();
    }
    
    public void addAtomicLockListener(AtomicLockListener l) {
        listenerList.add(AtomicLockListener.class, l);
    }
    
    public void removeAtomicLockListener(AtomicLockListener l) {
        listenerList.remove(AtomicLockListener.class, l);
    }
    
    private void fireAtomicLock(AtomicLockEvent evt) {
        EventListener[] listeners = listenerList.getListeners(AtomicLockListener.class);
        int cnt = listeners.length;
        for (int i = 0; i < cnt; i++) {
            ((AtomicLockListener)listeners[i]).atomicLock(evt);
        }
    }
    
    private void fireAtomicUnlock(AtomicLockEvent evt) {
        EventListener[] listeners = listenerList.getListeners(AtomicLockListener.class);
        int cnt = listeners.length;
        for (int i = 0; i < cnt; i++) {
            ((AtomicLockListener)listeners[i]).atomicUnlock(evt);
        }
    }
    
    protected final int getAtomicDepth() {
        return atomicDepth;
    }

    protected BaseDocumentEvent createDocumentEvent(int pos, int length,
            DocumentEvent.EventType type) {
        return new BaseDocumentEvent(this, pos, length, type);
    }

    /** Was the document modified by either insert/remove
    * but not the initial read)?
    */
    public boolean isModified() {
        return modified;
    }

    /** Get the layer with the specified name */
    public DrawLayer findLayer(String layerName) {
        return drawLayerList.findLayer(layerName);
    }

    public boolean addLayer(DrawLayer layer, int visibility) {
        if (drawLayerList.add(layer, visibility)) {
            BaseDocumentEvent evt = createDocumentEvent(0, 0, DocumentEvent.EventType.CHANGE);
            evt.addEdit(new BaseDocumentEvent.DrawLayerChange(layer.getName(), visibility));
            fireChangedUpdate(evt);
            return true;
        } else {
            return false;
        }
    }

    final DrawLayerList getDrawLayerList() {
        return drawLayerList;
    }

    /** Toggle the bookmark for the current line */
    public boolean toggleBookmark(int pos) throws BadLocationException {
        pos = Utilities.getRowStart(this, pos);
        boolean marked = bookmarkChain.toggleMark(pos);
        BaseDocumentEvent evt = createDocumentEvent(pos, 0, DocumentEvent.EventType.CHANGE);
        fireChangedUpdate(evt);
        return marked;
    }

    /** Get the position of the next bookmark.
    * @pos position from which to search
    * @wrap wrap around the end of document
    * @return position of the next bookmark or -1 if there is no mark
    */
    public int getNextBookmark(int pos, boolean wrap)
    throws BadLocationException {
        try {
            pos = Utilities.getRowStart(this, pos);
            int rel = bookmarkChain.compareMark(pos);
            MarkFactory.ChainDrawMark mark = bookmarkChain.getCurMark();
            if (rel <= 0) { // right at this line, go next
                if (mark != null) {
                    if (mark.next != null) {
                        return mark.next.getOffset();
                    } else { // last bookmark
                        return (wrap && bookmarkChain.chain != null) ?
                               bookmarkChain.chain.getOffset() : -1;
                    }
                } else { // no marks
                    return -1;
                }
            } else { // mark after pos
                return mark.getOffset();
            }
        } catch (InvalidMarkException e) {
            Utilities.annotateLoggable(e);
            return 0;
        }
    }

    private LineRootElement getLineRootElement() {
        return lineRootElement;
    }

    public Element getParagraphElement(int pos) {
        return getLineRootElement().getElement(
                   getLineRootElement().getElementIndex(pos));
    }

    /** Returns object which represent list of annotations which are
     * attached to this document. 
     * @return object which represent attached annotations
     */
    public Annotations getAnnotations() {
        synchronized (annotationsLock) {
            if (annotations == null) {
                annotations = new Annotations(this);
            }
            return annotations;
        }
    }
    

    /** Returns object which represent list of annotations which are
     * attached to this document. 
     * @return object which represent attached annotations
     */
    public Bookmarks getBookmarks() {
        synchronized (bookmarksLock) {
            if (bookmarks == null) {
                bookmarks = new Bookmarks();
            }
            return bookmarks;
        }
    }
    
    /**
     * @see LineRootElement#prepareSyntax()
     */
    void prepareSyntax(Segment text, Syntax syntax, int reqPos, int reqLen,
    boolean forceLastBuffer, boolean forceNotLastBuffer) throws BadLocationException {
        FixLineSyntaxState.prepareSyntax(this, text, syntax, reqPos, reqLen,
            forceLastBuffer, forceNotLastBuffer);
    }

    int getTokenSafeOffset(int offset) {
        return FixLineSyntaxState.getTokenSafeOffset(this, offset);
    }

    /** Get position on line from visual column. This method can be used
    * only for superfixed font i.e. all characters of all font styles
    * have the same width.
    * @param visCol visual column
    * @param startLinePos position of line start
    * @return position on line for particular x-coord
    */
    int getOffsetFromVisCol(int visCol, int startLinePos)
    throws BadLocationException {
        
        synchronized (getOffsetFromVisColLock) {
            if (startLinePos < 0 || startLinePos >= getLength()) {
                throw new BadLocationException("Invalid start line offset", startLinePos); // NOI18N
            }
            if (visCol <= 0) {
                return startLinePos;
            }
            if (visColPosFwdFinder == null) {
                visColPosFwdFinder = new FinderFactory.VisColPosFwdFinder();
            }
            visColPosFwdFinder.setVisCol(visCol);
            visColPosFwdFinder.setTabSize(getTabSize());
            int pos = find(visColPosFwdFinder, startLinePos, -1);
            return (pos != -1)
                ? pos
                : Utilities.getRowEnd(this, startLinePos);
        }
    }

    /** Get visual column from position. This method can be used
    * only for superfixed font i.e. all characters of all font styles
    * have the same width.
    * @param pos position for which the visual column should be returned
    *   the function itself computes the begining of the line first
    */ 
    int getVisColFromPos(int pos) throws BadLocationException {
        synchronized (getVisColFromPosLock) {
            if (pos < 0 || pos > getLength()) {
                throw new BadLocationException("Invalid offset", pos); // NOI18N
            }

            if (posVisColFwdFinder == null) {
                posVisColFwdFinder = new FinderFactory.PosVisColFwdFinder();
            }

            int startLinePos = Utilities.getRowStart(this, pos);
            posVisColFwdFinder.setTabSize(getTabSize());
            find(posVisColFwdFinder, startLinePos, pos);
            return posVisColFwdFinder.getVisCol();
        }
    }
    
    protected Dictionary createDocumentProperties(Dictionary origDocumentProperties) {
        return new LazyPropertyMap(origDocumentProperties);
    }

    public String toString() {
        return super.toString() + ", kitClass=" + getKitClass() // NOI18N
            + ", docLen=" + getLength(); // NOI18N
    }
    
    /** Detailed debug info about the document */
    public String toStringDetail() {
        return toString();
    }

    /** Compound edit that write-locks the document for the whole processing
     * of its undo operation.
     */
    class AtomicCompoundEdit extends CompoundEdit {
        
        public void undo() throws CannotUndoException {
            atomicLock();
            try {
                super.undo();
            } finally {
                atomicUnlockImpl(true);
            }
        }
        
        public void redo() throws CannotRedoException {
            atomicLock();
            try {
                super.redo();
            } finally {
                atomicUnlockImpl(true);
            }
        }
        
        public int size() {
            return edits.size();
        }

    }
    
    /** Property evaluator is useful for lazy evaluation
     * of properties of the document when
     * {@link javax.swing.text.Document#getProperty(java.lang.String)}
     * is called.
     */
    public interface PropertyEvaluator {
        
        /** Get the real value of the property */
        public Object getValue();
        
    }
    
    protected static class LazyPropertyMap extends Hashtable {
        
        protected LazyPropertyMap(Dictionary dict) {
            super(5);
            
            Enumeration en = dict.keys();
            while (en.hasMoreElements()) {
                Object key = en.nextElement();
                put(key, dict.get(key));
            }
        }
        
        public Object get(Object key) {
            Object val = super.get(key);
            if (val instanceof PropertyEvaluator) {
                val = ((PropertyEvaluator)val).getValue();
            }
            
            return val;
        }
        
    }

    private static final class MarksStorageUndo extends AbstractUndoableEdit {
        
        private DocumentEvent evt;
        
        MarksStorageUndo(DocumentEvent evt) {
            this.evt = evt;
        }
        
        private int getLength() {
            int length = evt.getLength();
            if (evt.getType() == DocumentEvent.EventType.REMOVE) {
                length = -length;
            }
            return length;
        }
        
        void updateMarksStorage() {
            BaseDocument doc = (BaseDocument)evt.getDocument();
            // Update document's compatible marks storage - no undo
            doc.marksStorage.update(evt.getOffset(), getLength(), null);
        }
        
        public void undo() throws CannotUndoException {
            BaseDocument doc = (BaseDocument)evt.getDocument();
            // Update document's compatible marks storage - no undo
            doc.marksStorage.update(evt.getOffset(), -getLength(), null);
            super.undo();
        }
        
        public void redo() throws CannotRedoException {
            updateMarksStorage();
            super.redo();
        }
        

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy