This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 21237
Collapse All | Expand All

(-)a/editor.lib/src/org/netbeans/editor/BaseDocument.java (-190 / +277 lines)
Lines 73-78 Link Here
73
import javax.swing.text.BadLocationException;
73
import javax.swing.text.BadLocationException;
74
import javax.swing.text.DefaultEditorKit;
74
import javax.swing.text.DefaultEditorKit;
75
import javax.swing.text.Document;
75
import javax.swing.text.Document;
76
import javax.swing.text.DocumentFilter;
76
import javax.swing.text.Position;
77
import javax.swing.text.Position;
77
import javax.swing.text.Element;
78
import javax.swing.text.Element;
78
import javax.swing.text.AttributeSet;
79
import javax.swing.text.AttributeSet;
Lines 336-341 Link Here
336
337
337
    private DocumentListener postModificationDocumentListener;
338
    private DocumentListener postModificationDocumentListener;
338
339
340
    private DocumentEvent postModificationEvent;
341
339
    private ListenerList<DocumentListener> postModificationDocumentListenerList = new ListenerList<DocumentListener>();
342
    private ListenerList<DocumentListener> postModificationDocumentListenerList = new ListenerList<DocumentListener>();
340
343
341
    private ListenerList<DocumentListener> updateDocumentListenerList = new ListenerList<DocumentListener>();
344
    private ListenerList<DocumentListener> updateDocumentListenerList = new ListenerList<DocumentListener>();
Lines 352-357 Link Here
352
    
355
    
353
    private UndoableEdit removeUpdateLineUndo;
356
    private UndoableEdit removeUpdateLineUndo;
354
357
358
    private DocumentFilter.FilterBypass filterBypass;
359
355
    private Preferences prefs;
360
    private Preferences prefs;
356
    private final PreferenceChangeListener prefsListener = new PreferenceChangeListener() {
361
    private final PreferenceChangeListener prefsListener = new PreferenceChangeListener() {
357
        public @Override void preferenceChange(PreferenceChangeEvent evt) {
362
        public @Override void preferenceChange(PreferenceChangeEvent evt) {
Lines 724-730 Link Here
724
    }
729
    }
725
730
726
    /** Inserts string into document */
731
    /** Inserts string into document */
727
    public @Override void insertString(int offset, String text, AttributeSet a)
732
    public @Override void insertString(int offset, String text, AttributeSet attrs)
728
    throws BadLocationException {
733
    throws BadLocationException {
729
        if (LOG_EDT.isLoggable(Level.FINE)) { // Only permit operations in EDT
734
        if (LOG_EDT.isLoggable(Level.FINE)) { // Only permit operations in EDT
730
            if (!SwingUtilities.isEventDispatchThread()) {
735
            if (!SwingUtilities.isEventDispatchThread()) {
Lines 733-847 Link Here
733
            }
738
            }
734
        }
739
        }
735
        
740
        
736
        if (text == null || text.length() == 0) {
741
        boolean notifyMod = notifyModifyCheckStart(offset, "insertString() vetoed"); // NOI18N
737
            return;
742
        boolean modFinished = false; // Whether modification succeeded
738
        }
739
740
        // Check offset correctness
741
        if (offset < 0 || offset > getLength()) {
742
            throw new BadLocationException("Wrong insert position " + offset, offset); // NOI18N
743
        }
744
745
        // possible CR-LF conversion
746
        text = ReadWriteUtils.convertToNewlines(text);
747
748
        // Check whether there is an active postModificationDocumentListener
743
        // Check whether there is an active postModificationDocumentListener
749
        // and if so then start an atomic transaction.
744
        // and if so then start an atomic transaction.
750
        boolean activePostModification;
745
        boolean activePostModification;
751
        DocumentEvent postModificationEvent = null;
752
        synchronized (this) {
746
        synchronized (this) {
753
            activePostModification = (postModificationDocumentListener != null
747
            activePostModification = (postModificationDocumentListener != null
754
                    || postModificationDocumentListenerList.getListenerCount() > 0);
748
                    || postModificationDocumentListenerList.getListenerCount() > 0);
755
            if (activePostModification) {
749
            if (activePostModification) {
756
                atomicLockImpl ();
750
                atomicLockImpl ();
751
            } else {
752
                extWriteLock();
757
            }
753
            }
758
        }
754
        }
759
        try {
755
        try {
760
756
            DocumentFilter filter = getDocumentFilter();
761
        // Perform the insert
757
            if (filter != null) {
762
        boolean notifyMod = notifyModifyCheckStart(offset, "insertString() vetoed"); // NOI18N
758
                filter.insertString(getFilterBypass(), offset, text, attrs);
763
        boolean modFinished = false; // Whether modification succeeded
759
            } else {
764
        extWriteLock();
760
                handleInsertString(offset, text, attrs);
765
        try {
766
           incrementDocVersion();
767
768
            /*
769
            boolean checkSpaces = inited && org.netbeans.lib.editor.util.swing.DocumentUtilities.isTypingModification(this);
770
            if (checkSpaces) {
771
                Position offsPosition = createPosition(offset);
772
                checkTrailingSpaces(offset);
773
                offset = offsPosition.getOffset();
774
            }
775
             */
776
777
            preInsertCheck(offset, text, a);
778
779
            // Do the real insert into the content
780
            UndoableEdit edit = getContent().insertString(offset, text);
781
782
            /*
783
            if (checkSpaces) {
784
                lastPositionEditedByTyping = createPosition(offset);
785
            }
786
             */
787
788
            if (LOG.isLoggable(Level.FINE)) {
789
                String msg = "BaseDocument.insertString(): doc=" + this // NOI18N
790
                    + (modified ? "" : " - first modification") // NOI18N
791
                    + ", offset=" + Utilities.offsetToLineColumnString(this, offset) // NOI18N
792
                    + (debugNoText ? "" : (", text='" + text + "'")); // NOI18N
793
794
                if (debugStack) {
795
                    LOG.log(Level.FINE, msg, new Throwable(msg));
796
                } else {
797
                    LOG.log(Level.FINE, msg);
798
                }
799
            }
800
801
            BaseDocumentEvent evt = getDocumentEvent(offset, text.length(), DocumentEvent.EventType.INSERT, a);
802
803
            preInsertUpdate(evt, a);
804
805
            if (edit != null) {
806
                evt.addEdit(edit);
807
808
                lastModifyUndoEdit = edit; // #8692 check last modify undo edit
809
            }
810
811
            modified = true;
812
813
            if (atomicDepth > 0) {
814
                ensureAtomicEditsInited();
815
                atomicEdits.addEdit(evt); // will be added
816
            }
817
818
            insertUpdate(evt, a);
819
820
            evt.end();
821
822
            fireInsertUpdate(evt);
823
824
            boolean isComposedText = ((a != null)
825
                                      && (a.isDefined(StyleConstants.ComposedTextAttribute)));
826
827
            if (composedText && !isComposedText)
828
                composedText = false;
829
            if (!composedText && isComposedText)
830
                composedText = true;
831
832
            if (atomicDepth == 0 && !isComposedText) { // !!! check
833
                fireUndoableEditUpdate(new UndoableEditEvent(this, evt));
834
            }
761
            }
835
            modFinished = true;
762
            modFinished = true;
836
            postModificationEvent = evt;
837
        } finally {
838
            extWriteUnlock();
839
            // Notify no mod done if notified mod but mod did not succeeded
840
            if (notifyMod) {
841
                notifyModifyCheckEnd(modFinished);
842
            }
843
        }
844
845
        } finally { // for post modification
763
        } finally { // for post modification
846
            if (activePostModification) {
764
            if (activePostModification) {
847
                try {
765
                try {
Lines 856-864 Link Here
856
                } finally {
774
                } finally {
857
                    atomicUnlockImpl();
775
                    atomicUnlockImpl();
858
                }
776
                }
777
            } else {
778
                extWriteUnlock();
779
            }
780
            // Notify no mod done if notified mod but mod did not succeeded
781
            if (notifyMod) {
782
                notifyModifyCheckEnd(modFinished);
859
            }
783
            }
860
        }
784
        }
861
    }
785
    }
786
    
787
    void handleInsertString(int offset, String text, AttributeSet attrs) throws BadLocationException {
788
        if (text == null || text.length() == 0) {
789
            return;
790
        }
791
792
        // Check offset correctness
793
        if (offset < 0 || offset > getLength()) {
794
            throw new BadLocationException("Wrong insert position " + offset, offset); // NOI18N
795
        }
796
797
        // possible CR-LF to LF conversion
798
        text = ReadWriteUtils.convertToNewlines(text);
799
800
        postModificationEvent = null;
801
        incrementDocVersion();
802
803
        preInsertCheck(offset, text, attrs);
804
805
        UndoableEdit edit = getContent().insertString(offset, text);
806
807
        if (LOG.isLoggable(Level.FINE)) {
808
            String msg = "BaseDocument.insertString(): doc=" + this // NOI18N
809
                    + (modified ? "" : " - first modification") // NOI18N
810
                    + ", offset=" + Utilities.offsetToLineColumnString(this, offset) // NOI18N
811
                    + (debugNoText ? "" : (", text='" + text + "'")); // NOI18N
812
813
            if (debugStack) {
814
                LOG.log(Level.FINE, msg, new Throwable(msg));
815
            } else {
816
                LOG.log(Level.FINE, msg);
817
            }
818
        }
819
820
        BaseDocumentEvent evt = getDocumentEvent(offset, text.length(), DocumentEvent.EventType.INSERT, attrs);
821
822
        preInsertUpdate(evt, attrs);
823
824
        if (edit != null) {
825
            evt.addEdit(edit);
826
827
            lastModifyUndoEdit = edit; // #8692 check last modify undo edit
828
        }
829
830
        modified = true;
831
832
        if (atomicDepth > 0) {
833
            ensureAtomicEditsInited();
834
            atomicEdits.addEdit(evt); // will be added
835
        }
836
837
        insertUpdate(evt, attrs);
838
839
        evt.end();
840
841
        fireInsertUpdate(evt);
842
843
        boolean isComposedText = ((attrs != null)
844
                && (attrs.isDefined(StyleConstants.ComposedTextAttribute)));
845
846
        if (composedText && !isComposedText) {
847
            composedText = false;
848
        }
849
        if (!composedText && isComposedText) {
850
            composedText = true;
851
        }
852
853
        if (atomicDepth == 0 && !isComposedText) { // !!! check
854
            fireUndoableEditUpdate(new UndoableEditEvent(this, evt));
855
        }
856
        postModificationEvent = evt;
857
    }
862
858
863
    public void checkTrailingSpaces(int offset) {
859
    public void checkTrailingSpaces(int offset) {
864
        try {
860
        try {
Lines 896-1001 Link Here
896
    }
892
    }
897
893
898
    /** Removes portion of a document */
894
    /** Removes portion of a document */
899
    public @Override void remove(int offset, int len) throws BadLocationException {
895
    public @Override void remove(int offset, int length) throws BadLocationException {
900
        if (LOG_EDT.isLoggable(Level.FINE)) { // Only permit operations in EDT
896
        if (LOG_EDT.isLoggable(Level.FINE)) { // Only permit operations in EDT
901
            if (!SwingUtilities.isEventDispatchThread()) {
897
            if (!SwingUtilities.isEventDispatchThread()) {
902
                throw new IllegalStateException("BaseDocument.insertString not in EDT: offset=" + // NOI18N
898
                throw new IllegalStateException("BaseDocument.insertString not in EDT: offset=" + // NOI18N
903
                        offset + ", len=" + len); // NOI18N
899
                        offset + ", len=" + length); // NOI18N
904
            }
900
            }
905
        }
901
        }
906
902
907
        if (len > 0) {
903
        boolean notifyMod = notifyModifyCheckStart(offset, "remove() vetoed"); // NOI18N
908
            if (offset < 0) {
904
        boolean modFinished = false; // Whether modification succeeded
909
                throw new BadLocationException("Wrong remove position " + offset, offset); // NOI18N
905
        // Check whether there is an active postModificationDocumentListener
906
        // and if so then start an atomic transaction.
907
        boolean activePostModification;
908
        synchronized (this) {
909
            activePostModification = (postModificationDocumentListener != null
910
                    || postModificationDocumentListenerList.getListenerCount() > 0);
911
            if (activePostModification) {
912
                atomicLockImpl ();
913
            } else {
914
                extWriteLock();
910
            }
915
            }
911
916
        }
912
            // Check whether there is an active postModificationDocumentListener
917
        try {
913
            // and if so then start an atomic transaction.
918
            DocumentFilter filter = getDocumentFilter();
914
            boolean activePostModification;
919
            if (filter != null) {
915
            DocumentEvent postModificationEvent = null;
920
                filter.remove(getFilterBypass(), offset, length);
916
            synchronized (this) {
921
            } else {
917
                activePostModification = (postModificationDocumentListener != null
922
                handleRemove(offset, length);
918
                        || postModificationDocumentListenerList.getListenerCount() > 0);
919
                if (activePostModification) {
920
                    atomicLockImpl ();
921
                }
922
            }
923
            }
923
            try {
924
            modFinished = true;
924
925
        } finally { // for post modification
925
            boolean notifyMod = notifyModifyCheckStart(offset, "remove() vetoed"); // NOI18N
926
            if (activePostModification) {
926
            boolean modFinished = false; // Whether modification succeeded
927
                try {
927
            extWriteLock();
928
            try {
929
                incrementDocVersion();
930
931
                int docLen = getLength();
932
                if (offset < 0 || offset > docLen) {
933
                    throw new BadLocationException("Wrong remove position " + offset, offset); // NOI18N
934
                }
935
                if (offset + len > docLen) {
936
                    throw new BadLocationException("End offset of removed text " // NOI18N
937
                        + (offset + len) + " > getLength()=" + docLen, // NOI18N
938
                        offset + len
939
                    ); // NOI18N
940
                }
941
942
                preRemoveCheck(offset, len);
943
944
                BaseDocumentEvent evt = getDocumentEvent(offset, len, DocumentEvent.EventType.REMOVE, null);
945
                // Store modification text as an event's property
946
                org.netbeans.lib.editor.util.swing.DocumentUtilities.addEventPropertyStorage(evt);
947
                String removedText = getText(offset, len);
948
                org.netbeans.lib.editor.util.swing.DocumentUtilities.putEventProperty(evt, String.class, removedText);
949
950
                removeUpdate(evt);
951
952
                UndoableEdit edit = ((EditorDocumentContent)getContent()).remove(offset, removedText);
953
                if (edit != null) {
954
                    evt.addEdit(edit);
955
956
                    lastModifyUndoEdit = edit; // #8692 check last modify undo edit
957
                }
958
959
                if (LOG.isLoggable(Level.FINE)) {
960
                    String msg = "BaseDocument.remove(): doc=" + this // NOI18N
961
                        + ", origDocLen=" + docLen // NOI18N
962
                        + ", offset=" + Utilities.offsetToLineColumnString(this, offset) // NOI18N
963
                        + ", len=" + len // NOI18N
964
                        + (debugNoText ? "" : (", removedText='" + ((ContentEdit)edit).getText() + "'")); //NOI18N
965
966
                    if (debugStack) {
967
                        LOG.log(Level.FINE, msg, new Throwable(msg));
968
                    } else {
969
                        LOG.log(Level.FINE, msg);
970
                    }
971
                }
972
973
                if (atomicDepth > 0) { // add edits as soon as possible
974
                    ensureAtomicEditsInited();
975
                    atomicEdits.addEdit(evt); // will be added
976
                }
977
978
                postRemoveUpdate(evt);
979
980
                evt.end();
981
982
                fireRemoveUpdate(evt);
983
                if (atomicDepth == 0 && !composedText) {
984
                    fireUndoableEditUpdate(new UndoableEditEvent(this, evt));
985
                }
986
987
                modFinished = true;
988
                postModificationEvent = evt;
989
            } finally {
990
                extWriteUnlock();
991
                // Notify no mod done if notified mod but mod did not succeeded
992
                if (notifyMod) {
993
                    notifyModifyCheckEnd(modFinished);
994
                }
995
            }
996
997
            } finally { // for post modification
998
                if (activePostModification) {
999
                    if (postModificationEvent != null) { // Modification finished successfully
928
                    if (postModificationEvent != null) { // Modification finished successfully
1000
                        if (postModificationDocumentListener != null) {
929
                        if (postModificationDocumentListener != null) {
1001
                            postModificationDocumentListener.removeUpdate(postModificationEvent);
930
                            postModificationDocumentListener.removeUpdate(postModificationEvent);
Lines 1004-1014 Link Here
1004
                            listener.removeUpdate(postModificationEvent);
933
                            listener.removeUpdate(postModificationEvent);
1005
                        }
934
                        }
1006
                    }
935
                    }
936
                } finally {
1007
                    atomicUnlockImpl ();
937
                    atomicUnlockImpl ();
1008
                }
938
                }
939
            } else {
940
                extWriteUnlock();
1009
            }
941
            }
942
            // Notify no mod done if notified mod but mod did not succeeded
943
            if (notifyMod) {
944
                notifyModifyCheckEnd(modFinished);
945
            }
946
        }
947
    }
948
    
949
    void handleRemove(int offset, int length) throws BadLocationException {
950
        if (length == 0) {
951
            return;
952
        }
953
        if (length < 0) {
954
            throw new IllegalArgumentException("len=" + length + " < 0"); // NOI18N
955
        }
956
        if (offset < 0) {
957
            throw new BadLocationException("Wrong remove position " + offset + " < 0", offset); // NOI18N
958
        }
959
        if (offset + length > getLength()) {
960
            throw new BadLocationException("Wrong (offset+length)=" + (offset+length) +
961
                    " > getLength()=" + getLength(), offset + length); // NOI18N
962
        }
1010
963
964
        postModificationEvent = null;
965
        incrementDocVersion();
966
967
        int docLen = getLength();
968
        if (offset < 0 || offset > docLen) {
969
            throw new BadLocationException("Wrong remove position " + offset, offset); // NOI18N
1011
        }
970
        }
971
        if (offset + length > docLen) {
972
            throw new BadLocationException("End offset of removed text " // NOI18N
973
                    + (offset + length) + " > getLength()=" + docLen, // NOI18N
974
                    offset + length); // NOI18N
975
        }
976
977
        preRemoveCheck(offset, length);
978
979
        BaseDocumentEvent evt = getDocumentEvent(offset, length, DocumentEvent.EventType.REMOVE, null);
980
        // Store modification text as an event's property
981
        org.netbeans.lib.editor.util.swing.DocumentUtilities.addEventPropertyStorage(evt);
982
        String removedText = getText(offset, length);
983
        org.netbeans.lib.editor.util.swing.DocumentUtilities.putEventProperty(evt, String.class, removedText);
984
985
        removeUpdate(evt);
986
987
        UndoableEdit edit = ((EditorDocumentContent) getContent()).remove(offset, removedText);
988
        if (edit != null) {
989
            evt.addEdit(edit);
990
991
            lastModifyUndoEdit = edit; // #8692 check last modify undo edit
992
        }
993
994
        if (LOG.isLoggable(Level.FINE)) {
995
            String msg = "BaseDocument.remove(): doc=" + this // NOI18N
996
                    + ", origDocLen=" + docLen // NOI18N
997
                    + ", offset=" + Utilities.offsetToLineColumnString(this, offset) // NOI18N
998
                    + ", len=" + length // NOI18N
999
                    + (debugNoText ? "" : (", removedText='" + ((ContentEdit) edit).getText() + "'")); //NOI18N
1000
1001
            if (debugStack) {
1002
                LOG.log(Level.FINE, msg, new Throwable(msg));
1003
            } else {
1004
                LOG.log(Level.FINE, msg);
1005
            }
1006
        }
1007
1008
        if (atomicDepth > 0) { // add edits as soon as possible
1009
            ensureAtomicEditsInited();
1010
            atomicEdits.addEdit(evt); // will be added
1011
        }
1012
1013
        postRemoveUpdate(evt);
1014
1015
        evt.end();
1016
1017
        fireRemoveUpdate(evt);
1018
        if (atomicDepth == 0 && !composedText) {
1019
            fireUndoableEditUpdate(new UndoableEditEvent(this, evt));
1020
        }
1021
        postModificationEvent = evt;
1022
    }
1023
1024
    public void replace(int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
1025
        boolean notifyMod = notifyModifyCheckStart(offset, "replace() vetoed"); // NOI18N
1026
        boolean modFinished = false; // Whether modification succeeded
1027
        // Check whether there is an active postModificationDocumentListener
1028
        // and if so then start an atomic transaction.
1029
        boolean activePostModification;
1030
        synchronized (this) {
1031
            activePostModification = (postModificationDocumentListener != null
1032
                    || postModificationDocumentListenerList.getListenerCount() > 0);
1033
            if (activePostModification) {
1034
                atomicLockImpl ();
1035
            } else {
1036
                extWriteLock();
1037
            }
1038
        }
1039
        try {
1040
            DocumentFilter filter = getDocumentFilter();
1041
            if (filter != null) {
1042
                filter.replace(getFilterBypass(), offset, length, text, attrs);
1043
            } else {
1044
                handleRemove(offset, length);
1045
                handleInsertString(offset, text, attrs);
1046
            }
1047
            modFinished = true;
1048
        } finally { // for post modification
1049
            if (activePostModification) {
1050
                try {
1051
                    if (postModificationEvent != null) { // Modification finished successfully
1052
                        if (postModificationDocumentListener != null) {
1053
                            postModificationDocumentListener.insertUpdate(postModificationEvent);
1054
                        }
1055
                        for (DocumentListener listener: postModificationDocumentListenerList.getListeners()) {
1056
                            listener.insertUpdate(postModificationEvent);
1057
                        }
1058
                    }
1059
                } finally {
1060
                    atomicUnlockImpl();
1061
                }
1062
            } else {
1063
                extWriteUnlock();
1064
            }
1065
            // Notify no mod done if notified mod but mod did not succeeded
1066
            if (notifyMod) {
1067
                notifyModifyCheckEnd(modFinished);
1068
            }
1069
        }
1070
    }
1071
1072
    private DocumentFilter.FilterBypass getFilterBypass() {
1073
        if (filterBypass == null) {
1074
            filterBypass = new FilterBypassImpl();
1075
        }
1076
        return filterBypass;
1012
    }
1077
    }
1013
1078
1014
    /**
1079
    /**
Lines 1672-1677 Link Here
1672
		((UndoableEditListener)listeners[i + 1]).undoableEditHappened(e);
1737
		((UndoableEditListener)listeners[i + 1]).undoableEditHappened(e);
1673
	    }
1738
	    }
1674
	}
1739
	}
1740
1741
        // Since UndoManager may only do um.addEdit() the original resetting
1742
        // in AtomicCompoundEdit.replaceEdit() would not be called in such case.
1743
        undoMergeReset = false;
1675
    }
1744
    }
1676
1745
1677
    /** Extended write locking of the document allowing
1746
    /** Extended write locking of the document allowing
Lines 2301-2307 Link Here
2301
                    }
2370
                    }
2302
                }
2371
                }
2303
            }
2372
            }
2304
            undoMergeReset = false;
2305
            return false;
2373
            return false;
2306
        }
2374
        }
2307
2375
Lines 2538-2541 Link Here
2538
        }
2606
        }
2539
    } // End of PlainEditorKit class
2607
    } // End of PlainEditorKit class
2540
2608
2609
    class FilterBypassImpl extends DocumentFilter.FilterBypass {
2610
2611
        public Document getDocument() {
2612
            return BaseDocument.this;
2613
        }
2614
2615
        public void remove(int offset, int length) throws BadLocationException {
2616
            handleRemove(offset, length);
2617
        }
2618
2619
        public void insertString(int offset, String string, AttributeSet attrs) throws BadLocationException {
2620
            handleInsertString(offset, string, attrs);
2621
        }
2622
2623
        public void replace(int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
2624
            handleRemove(offset, length);
2625
            handleInsertString(offset, text, attrs);
2626
        }
2627
    }
2541
}
2628
}
(-)a/openide.awt/src/org/openide/awt/UndoRedo.java (-58 / +7 lines)
Lines 139-229 Link Here
139
139
140
        private final ChangeSupport cs = new ChangeSupport(this);
140
        private final ChangeSupport cs = new ChangeSupport(this);
141
141
142
        /** vector of Edits to run */
143
        private final LinkedList<UndoableEditEvent> runus = new LinkedList<UndoableEditEvent>(); // for fix of #8692
144
145
        /** Called from undoableEditHappened() inner class */
146
        private void superUndoableEditHappened(UndoableEditEvent ue) {
147
            super.undoableEditHappened(ue);
148
        }
149
150
        /** Called from discardAllEdits() inner class */
151
        private void superDiscardAllEdits() {
152
            super.discardAllEdits();
153
        }
154
155
        /** Consume an undoable edit.
142
        /** Consume an undoable edit.
156
        * Delegates to superclass and notifies listeners.
143
        * Delegates to superclass and notifies listeners.
157
        * @param ue the edit
144
        * @param ue the edit
158
        */
145
        */
159
        @Override
146
        @Override
160
        public void undoableEditHappened(final UndoableEditEvent ue) {
147
        public void undoableEditHappened(final UndoableEditEvent ue) {
161
            /* Edits are posted to request processor and the deadlock
148
            super.undoableEditHappened(ue);
162
             * in #8692 between undoredo and document that fires
149
            cs.fireChange();
163
             * the undoable edit should be avoided this way.
164
             */
165
            synchronized (runus) {
166
                runus.add(ue);
167
            }
168
169
            updateTask();
170
        }
150
        }
171
151
172
        /** Discard all the existing edits from the undomanager. */
152
        /** Discard all the existing edits from the undomanager. */
173
        @Override
153
        @Override
174
        public void discardAllEdits() {
154
        public void discardAllEdits() {
175
            synchronized (runus) {
155
            super.discardAllEdits();
176
                runus.add(null);
156
            cs.fireChange();
177
            }
178
179
            updateTask();
180
        }
157
        }
181
158
182
        @Override
159
        @Override
183
        public void undo() throws CannotUndoException {
160
        public void undo() throws CannotUndoException {
184
            super.undo();
161
            super.undo();
185
            updateTask();
162
            cs.fireChange();
186
        }
163
        }
187
164
188
        @Override
165
        @Override
189
        public void redo() throws CannotRedoException {
166
        public void redo() throws CannotRedoException {
190
            super.redo();
167
            super.redo();
191
            updateTask();
168
            cs.fireChange();
192
        }
169
        }
193
170
194
        @Override
171
        @Override
195
        public void undoOrRedo() throws CannotRedoException, CannotUndoException {
172
        public void undoOrRedo() throws CannotRedoException, CannotUndoException {
196
            super.undoOrRedo();
173
            super.undoOrRedo(); // cs.fireChange() either in undo() or redo()
197
            updateTask();
198
        }
174
        }
199
175
200
        private void updateTask() {
201
            for (;;) {
202
                UndoableEditEvent ue;
203
204
                synchronized (runus) {
205
                    if (runus.isEmpty()) {
206
                        break;
207
                    }
208
209
                    ue = runus.removeFirst();
210
                }
211
212
                if (ue == null) {
213
                    superDiscardAllEdits();
214
                } else {
215
                    superUndoableEditHappened(ue);
216
                }
217
            }
218
            cs.fireChange();
219
        }
220
221
        /* Attaches change listener to the this object.
222
        * The listener is notified everytime the undo/redo
223
        * ability of this object changes.
224
        */
225
226
        //#32313 - synchronization of this method was removed
227
        @Override
176
        @Override
228
        public void addChangeListener(ChangeListener l) {
177
        public void addChangeListener(ChangeListener l) {
229
            cs.addChangeListener(l);
178
            cs.addChangeListener(l);
(-)a/openide.text/src/org/openide/text/CloneableEditorSupport.java (-909 / +150 lines)
Lines 215-221 Link Here
215
    private Listener listener;
215
    private Listener listener;
216
216
217
    /** the undo/redo manager to use for this document */
217
    /** the undo/redo manager to use for this document */
218
    private UndoRedo.Manager undoRedo;
218
    private UndoRedoManager undoRedo;
219
219
220
    /** lines set for this object */
220
    /** lines set for this object */
221
    private Line.Set lineSet;
221
    private Line.Set lineSet;
Lines 261-268 Link Here
261
     * <br>
261
     * <br>
262
     * Also set when document is being reloaded.
262
     * Also set when document is being reloaded.
263
     */
263
     */
264
    private boolean revertingUndoOrReloading;
264
    private boolean documentReloading;
265
    private boolean justRevertedToNotModified;
266
    private volatile int documentStatus = DOCUMENT_NO;
265
    private volatile int documentStatus = DOCUMENT_NO;
267
    private Throwable prepareDocumentRuntimeException;
266
    private Throwable prepareDocumentRuntimeException;
268
267
Lines 271-280 Link Here
271
     */
270
     */
272
    private Map<Line,Reference<Line>> lineSetWHM;
271
    private Map<Line,Reference<Line>> lineSetWHM;
273
    private boolean annotationsLoaded;
272
    private boolean annotationsLoaded;
273
    
274
    private DocFilter docFilter;
274
275
275
    /** Classes that have been warned about overriding asynchronousOpen() */
276
    /** Classes that have been warned about overriding asynchronousOpen() */
276
    private static final Set<Class> warnedClasses = new WeakSet<Class>();
277
    private static final Set<Class> warnedClasses = new WeakSet<Class>();
277
278
    
278
    /** Creates new CloneableEditorSupport attached to given environment.
279
    /** Creates new CloneableEditorSupport attached to given environment.
279
    *
280
    *
280
    * @param env environment that is source of all actions around the
281
    * @param env environment that is source of all actions around the
Lines 385-395 Link Here
385
        }
386
        }
386
        
387
        
387
        if (undoRedo == null) {
388
        if (undoRedo == null) {
388
            undoRedo = createUndoRedoManager();
389
            UndoRedo.Manager mgr = createUndoRedoManager();
390
            if (!(mgr instanceof UndoRedoManager)) {
391
                ERR.info("createUndoRedoManager(): ignoring created instance of class " + // NOI18N
392
                        mgr.getClass() + " since CloneableEditorSupport requires instance of " + // NOI18N"
393
                        UndoRedoManager.class.getName() + "\n"); // NOI18N
394
                mgr = new UndoRedoManager(this);
395
            }
396
            undoRedo = (UndoRedoManager) mgr;
389
        }
397
        }
390
398
391
        return undoRedo;
399
        return undoRedo;
392
    }
400
    }
401
    
402
    UndoRedoManager getUndoRedoManager() {
403
        return (UndoRedoManager) getUndoRedo();
404
    }
393
405
394
    /** Provides access to position manager for the document.
406
    /** Provides access to position manager for the document.
395
    * It maintains a set of positions even the document is in memory
407
    * It maintains a set of positions even the document is in memory
Lines 728-733 Link Here
728
                                                               // atomic action has finished
740
                                                               // atomic action has finished
729
                                                               // definitively sooner than leaving lock section
741
                                                               // definitively sooner than leaving lock section
730
                                                               // and notifying al waiters, see #47022
742
                                                               // and notifying al waiters, see #47022
743
                                                               getUndoRedoManager().markSavepoint();
731
                                                               getDoc().addUndoableEditListener(getUndoRedo());
744
                                                               getDoc().addUndoableEditListener(getUndoRedo());
732
                                                               d = getDoc();
745
                                                               d = getDoc();
733
                                                           } catch (DelegateIOExc t) {
746
                                                           } catch (DelegateIOExc t) {
Lines 782-793 Link Here
782
            } else {
795
            } else {
783
                d.putProperty("modificationListener", null); // NOI18N
796
                d.putProperty("modificationListener", null); // NOI18N
784
            }
797
            }
785
        } else {
798
        }
786
            if (add) {
799
787
                d.addDocumentListener(getListener());
800
        if (add) {
788
            } else {
801
            if (d instanceof AbstractDocument) {
789
                d.removeDocumentListener(getListener());
802
                AbstractDocument aDoc = (AbstractDocument) d;
803
                DocumentFilter origFilter = aDoc.getDocumentFilter();
804
                docFilter = new DocFilter(origFilter);
805
                aDoc.setDocumentFilter(docFilter);
806
            } else { // Put property for non-AD
807
                DocumentFilter origFilter = (DocumentFilter) d.getProperty(DocumentFilter.class);
808
                docFilter = new DocFilter(origFilter);
809
                d.putProperty(DocumentFilter.class, docFilter);
790
            }
810
            }
811
            d.addDocumentListener(getListener());
812
813
814
        } else { // remove filter
815
            if (docFilter != null) {
816
                if (d instanceof AbstractDocument) {
817
                    AbstractDocument aDoc = (AbstractDocument) d;
818
                    aDoc.setDocumentFilter(docFilter.origFilter);
819
                } else { // Put property for non-AD
820
                    d.putProperty(DocumentFilter.class, docFilter.origFilter);
821
                }
822
                docFilter = null;
823
            }
824
            d.removeDocumentListener(getListener());
791
        }
825
        }
792
    }
826
    }
793
827
Lines 1076-1083 Link Here
1076
                        }
1110
                        }
1077
                    }
1111
                    }
1078
1112
1079
                    // Insert before-save undo event to enable unmodifying undo
1113
                    getUndoRedoManager().markSavepoint();
1080
                    getUndoRedo().undoableEditHappened(new UndoableEditEvent(this, new BeforeSaveEdit(lastSaveTime)));
1081
1114
1082
                    // update cached info about lines
1115
                    // update cached info about lines
1083
                    updateLineSet(true);
1116
                    updateLineSet(true);
Lines 1101-1107 Link Here
1101
        // Run before-save actions
1134
        // Run before-save actions
1102
        Runnable beforeSaveRunnable = (Runnable) myDoc.getProperty("beforeSaveRunnable");
1135
        Runnable beforeSaveRunnable = (Runnable) myDoc.getProperty("beforeSaveRunnable");
1103
        if (beforeSaveRunnable != null) {
1136
        if (beforeSaveRunnable != null) {
1104
            beforeSaveRunnable.run();
1137
            undoRedo.setPerformingSaveActions(true);
1138
            try {
1139
                beforeSaveRunnable.run();
1140
            } finally {
1141
                undoRedo.setPerformingSaveActions(false);
1142
            }
1105
        }
1143
        }
1106
1144
1107
        SaveAsReader saveAsReader = new SaveAsReader();
1145
        SaveAsReader saveAsReader = new SaveAsReader();
Lines 1505-1518 Link Here
1505
    /** Test whether the document is ready.
1543
    /** Test whether the document is ready.
1506
    * @return <code>true</code> if document is ready
1544
    * @return <code>true</code> if document is ready
1507
    */
1545
    */
1508
    private boolean isDocumentReady() {
1546
    boolean isDocumentReady() {
1509
        CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
1547
        CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
1510
        if (redirect != null) {
1548
        if (redirect != null) {
1511
            return redirect.isDocumentReady();
1549
            return redirect.isDocumentReady();
1512
        }
1550
        }
1513
        return documentStatus == DOCUMENT_READY;
1551
        return documentStatus == DOCUMENT_READY;
1514
    }
1552
    }
1515
1553
    
1516
    /**
1554
    /**
1517
    * Set the MIME type for the document.
1555
    * Set the MIME type for the document.
1518
    * @param s the new MIME type
1556
    * @param s the new MIME type
Lines 1600-1606 Link Here
1600
    * @return the undo/redo manager
1638
    * @return the undo/redo manager
1601
    */
1639
    */
1602
    protected UndoRedo.Manager createUndoRedoManager() {
1640
    protected UndoRedo.Manager createUndoRedoManager() {
1603
        return new CESUndoRedoManager(this);
1641
        return new UndoRedoManager(this);
1604
    }
1642
    }
1605
1643
1606
    /** Returns an InputStream which reads the current data from this editor, taking into
1644
    /** Returns an InputStream which reads the current data from this editor, taking into
Lines 1808-1813 Link Here
1808
                                                 // XXX do this from AWT???
1846
                                                 // XXX do this from AWT???
1809
                                                 ERR.fine("task-discardAllEdits");
1847
                                                 ERR.fine("task-discardAllEdits");
1810
                                                 getUndoRedo().discardAllEdits();
1848
                                                 getUndoRedo().discardAllEdits();
1849
                                                 getUndoRedoManager().markSavepoint();
1811
                                                 ERR.fine("task-check already modified");
1850
                                                 ERR.fine("task-check already modified");
1812
                                                 // #57104 - if modified previously now it should become unmodified
1851
                                                 // #57104 - if modified previously now it should become unmodified
1813
                                                 if (isAlreadyModified()) {
1852
                                                 if (isAlreadyModified()) {
Lines 1925-1954 Link Here
1925
     * @return true if the modification was allowed, false if it should be prohibited
1964
     * @return true if the modification was allowed, false if it should be prohibited
1926
     */
1965
     */
1927
    final boolean callNotifyModified() {
1966
    final boolean callNotifyModified() {
1928
        // #57104 - when reverting undo the revertingUndoOrReloading flag is set
1967
        if (!isAlreadyModified() && !documentReloading) {
1929
        // to prevent infinite undoing which could happen now due to fix #56963
1930
        // (undoable edit being undone in the document notifies
1931
        // document's modification listener to mark the file as modified).
1932
        // Maybe clearing alreadyModified flag
1933
        // AFTER revertPreviousOrUpcomingUndo() could suffice as well
1934
        // instead of the revertingUndoOrReloading flag.
1935
        // Also notifyModified() is not called during reloadDocument()
1936
        // to prevent situation when output stream is taken from the file
1937
        // (for which CloneableEditorSupport exists) under file's lock
1938
        // and once closed (still under file's lock) the CES is trying to reload
1939
        // the file calling notifyModified() that tries to grab the lock
1940
        // and fails leading to undoing of the file's content to the one
1941
        // before the reload.
1942
        if (!isAlreadyModified() && !revertingUndoOrReloading) {
1943
            setAlreadyModified(true);
1968
            setAlreadyModified(true);
1944
            
1969
            
1945
            if (!notifyModified()) {
1970
            if (!notifyModified()) {
1946
                ERR.log(Level.INFO,"callNotifyModified notifyModified returns false this:" + getClass().getName());
1947
                setAlreadyModified(false);
1971
                setAlreadyModified(false);
1948
                revertingUndoOrReloading = true;
1949
                revertPreviousOrUpcomingUndo();
1950
                revertingUndoOrReloading = false;
1951
1952
                return false;
1972
                return false;
1953
            }
1973
            }
1954
        }
1974
        }
Lines 2036-2116 Link Here
2036
        return true;
2056
        return true;
2037
    }
2057
    }
2038
2058
2039
    /** Resets listening on <code>UndoRedo</code>,
2040
     * and in case next undo edit comes, schedules processesing of it.
2041
     * Used to revert modification e.g. of document of [read-only] env. */
2042
    private void revertPreviousOrUpcomingUndo() {
2043
        UndoRedo.Manager ur = getUndoRedo();
2044
        Listener l = getListener();
2045
2046
        if (Boolean.TRUE.equals(getDocument().getProperty("supportsModificationListener"))) { // NOI18N
2047
2048
            // revert undos now
2049
            SearchBeforeModificationEdit edit = new SearchBeforeModificationEdit();
2050
2051
            try {
2052
                for (;;) {
2053
                    edit.delegate = null;
2054
                    ur.undoableEditHappened(new UndoableEditEvent(getDocument(), edit));
2055
2056
                    if (edit.delegate == null) break; // no previous edit
2057
                    
2058
                    if (edit.delegate instanceof BeforeModificationEdit) {
2059
                        if (edit.delegate != null) {
2060
                            // undo anyway
2061
                            ur.undo();
2062
                        }
2063
2064
                        // and exit
2065
                        break;
2066
                    }
2067
2068
                    if (edit.delegate instanceof BeforeSaveEdit) {
2069
                        break;
2070
                    }
2071
2072
                    // otherwise remove the edit 
2073
                    ur.undo();
2074
                }
2075
            } catch (CannotUndoException ex) {
2076
                // ok, cannot undo, just ignore this
2077
            }
2078
        } else {
2079
            // revert upcomming undo
2080
            l.setUndoTask(new Runnable() {
2081
                    public void run() {
2082
                        undoAll();
2083
                    }
2084
                }
2085
            );
2086
            ur.addChangeListener(l);
2087
        }
2088
    }
2089
2090
    /** Creates <code>Runnable</code> which tries to make one undo. Helper method.
2091
     * @see #revertUpcomingUndo */
2092
    final void undoAll() {
2093
        StyledDocument sd = getDoc();
2094
2095
        if (sd == null) {
2096
            // #20883, doc can be null(!), doCloseDocument was faster.
2097
            return;
2098
        }
2099
2100
        UndoRedo ur = getUndoRedo();
2101
        addRemoveDocListener(sd, false);
2102
2103
        try {
2104
            if (ur.canUndo()) {
2105
                ur.undo();
2106
            }
2107
        } catch (CannotUndoException cne) {
2108
            ERR.log(Level.INFO, null, cne);
2109
        } finally {
2110
            addRemoveDocListener(sd, true);
2111
        }
2112
    }
2113
2114
    /** Method that is called when all components of the support are
2059
    /** Method that is called when all components of the support are
2115
    * closed. The default implementation closes the document.
2060
    * closed. The default implementation closes the document.
2116
    *
2061
    *
Lines 2844-2861 Link Here
2844
    /** The listener that this support uses to communicate with
2789
    /** The listener that this support uses to communicate with
2845
     * document, environment and also temporarilly on undoredo.
2790
     * document, environment and also temporarilly on undoredo.
2846
     */
2791
     */
2847
    private final class Listener extends Object implements ChangeListener, DocumentListener, PropertyChangeListener,
2792
    private final class Listener extends Object implements PropertyChangeListener, DocumentListener,
2848
        Runnable, java.beans.VetoableChangeListener {
2793
        Runnable, java.beans.VetoableChangeListener {
2794
2795
        /** Stores exception from loadDocument, can be set in run method */
2796
        private IOException loadExc;
2797
2849
        /** revert modification if asked */
2798
        /** revert modification if asked */
2850
        private boolean revertModifiedFlag;
2799
        private boolean revertModifiedFlag;
2851
2800
2852
        /** Stores exception from loadDocument, can be set in run method */
2853
        private IOException loadExc;
2854
2855
        /** Stores temporarilly undo task for reverting prohibited changes.
2856
         * @see CloneableEditorSupport#createUndoTask */
2857
        private Runnable undoTask;
2858
2859
        Listener() {
2801
        Listener() {
2860
        }
2802
        }
2861
2803
Lines 2868-2895 Link Here
2868
            //            loadExc = null;
2810
            //            loadExc = null;
2869
            return ret;
2811
            return ret;
2870
        }
2812
        }
2871
2813
        
2872
        /** Sets undo task used to revert prohibited change. */
2814
        public void insertUpdate(DocumentEvent evt) {
2873
        public void setUndoTask(Runnable undoTask) {
2815
            callNotifyModified();
2874
            this.undoTask = undoTask;
2816
            revertModifiedFlag = false;
2875
        }
2817
        }
2876
2818
2877
        /** Schedules reverting(undoing) of prohibited change.
2819
        public void removeUpdate(DocumentEvent evt) {
2878
         * Implements <code>ChangeListener</code>.
2820
            callNotifyModified();
2879
         * @see #revertUpcomingUndo */
2821
            revertModifiedFlag = false;
2880
        public void stateChanged(ChangeEvent evt) {
2881
            getUndoRedo().removeChangeListener(this);
2882
            undoTask.run();
2883
2884
            //SwingUtilities.invokeLater(undoTask);
2885
            undoTask = null;
2886
        }
2822
        }
2887
2823
        
2888
        /** Gives notification that an attribute or set of attributes changed.
2824
        public void changedUpdate(DocumentEvent evt) {
2889
        * @param ev event describing the action
2890
        */
2891
        public void changedUpdate(DocumentEvent ev) {
2892
            //modified(); (bugfix #1492)
2893
        }
2825
        }
2894
2826
2895
        public void vetoableChange(PropertyChangeEvent evt)
2827
        public void vetoableChange(PropertyChangeEvent evt)
Lines 2912-2933 Link Here
2912
            }
2844
            }
2913
        }
2845
        }
2914
2846
2915
        /** Gives notification that there was an insert into the document.
2916
        * @param ev event describing the action
2917
        */
2918
        public void insertUpdate(DocumentEvent ev) {
2919
            callNotifyModified();
2920
            revertModifiedFlag = false;
2921
        }
2922
2923
        /** Gives notification that a portion of the document has been removed.
2924
        * @param ev event describing the action
2925
        */
2926
        public void removeUpdate(DocumentEvent ev) {
2927
            callNotifyModified();
2928
            revertModifiedFlag = false;
2929
        }
2930
2931
        /** Listener to changes in the Env.
2847
        /** Listener to changes in the Env.
2932
        */
2848
        */
2933
        public void propertyChange(PropertyChangeEvent ev) {
2849
        public void propertyChange(PropertyChangeEvent ev) {
Lines 2963-2971 Link Here
2963
                                    }
2879
                                    }
2964
2880
2965
                                    // #57104 - avoid notifyModified() which takes file lock
2881
                                    // #57104 - avoid notifyModified() which takes file lock
2966
                                    revertingUndoOrReloading = true;
2882
                                    documentReloading = true;
2967
                                    NbDocument.runAtomic(sd, this);
2883
                                    NbDocument.runAtomic(sd, this);
2968
                                    revertingUndoOrReloading = false; // #57104
2884
                                    documentReloading = false; // #57104
2969
2885
2970
                                    return;
2886
                                    return;
2971
                                }
2887
                                }
Lines 3022-3030 Link Here
3022
2938
3023
            setLastSaveTime(cesEnv().getTime().getTime());
2939
            setLastSaveTime(cesEnv().getTime().getTime());
3024
2940
3025
            // Insert before-save undo event to enable unmodifying undo
3026
            getUndoRedo().undoableEditHappened(new UndoableEditEvent(this, new BeforeSaveEdit(lastSaveTime)));
3027
3028
            // Start listening on changes in document
2941
            // Start listening on changes in document
3029
            addRemoveDocListener(getDoc(), true);
2942
            addRemoveDocListener(getDoc(), true);
3030
        }
2943
        }
Lines 3032-3787 Link Here
3032
        //        }
2945
        //        }
3033
    }
2946
    }
3034
2947
3035
    /** Generic undoable edit that delegates to the given undoable edit. */
3036
    private class FilterUndoableEdit
3037
            implements UndoableEdit, UndoGroupManager.SeparateEdit
3038
    {
3039
        protected UndoableEdit delegate;
3040
3041
        FilterUndoableEdit() {
3042
        }
3043
3044
        public void undo() throws CannotUndoException {
3045
            if (delegate != null) {
3046
                delegate.undo();
3047
            }
3048
        }
3049
3050
        public boolean canUndo() {
3051
            if (delegate != null) {
3052
                return delegate.canUndo();
3053
            } else {
3054
                return false;
3055
            }
3056
        }
3057
3058
        public void redo() throws CannotRedoException {
3059
            if (delegate != null) {
3060
                delegate.redo();
3061
            }
3062
        }
3063
3064
        public boolean canRedo() {
3065
            if (delegate != null) {
3066
                return delegate.canRedo();
3067
            } else {
3068
                return false;
3069
            }
3070
        }
3071
3072
        public void die() {
3073
            if (delegate != null) {
3074
                delegate.die();
3075
            }
3076
        }
3077
3078
        public boolean addEdit(UndoableEdit anEdit) {
3079
            if (delegate != null) {
3080
                return delegate.addEdit(anEdit);
3081
            } else {
3082
                return false;
3083
            }
3084
        }
3085
3086
        public boolean replaceEdit(UndoableEdit anEdit) {
3087
            if (delegate != null) {
3088
                return delegate.replaceEdit(anEdit);
3089
            } else {
3090
                return false;
3091
            }
3092
        }
3093
3094
        public boolean isSignificant() {
3095
            if (delegate != null) {
3096
                return delegate.isSignificant();
3097
            } else {
3098
                return true;
3099
            }
3100
        }
3101
3102
        public String getPresentationName() {
3103
            if (delegate != null) {
3104
                return delegate.getPresentationName();
3105
            } else {
3106
                return ""; // NOI18N
3107
            }
3108
        }
3109
3110
        public String getUndoPresentationName() {
3111
            if (delegate != null) {
3112
                return delegate.getUndoPresentationName();
3113
            } else {
3114
                return ""; // NOI18N
3115
            }
3116
        }
3117
3118
        public String getRedoPresentationName() {
3119
            if (delegate != null) {
3120
                return delegate.getRedoPresentationName();
3121
            } else {
3122
                return ""; // NOI18N
3123
            }
3124
        }
3125
    }
3126
3127
    /** Undoable edit that is put before the savepoint. Its replaceEdit()
3128
     * method will consume and wrap the edit that precedes the save.
3129
     * If the edit is added to the begining of the queue then
3130
     * the isSignificant() implementation guarantees that the edit
3131
     * will not be removed from the queue.
3132
     * When redone it marks the document as not modified.
3133
     */
3134
    private class BeforeSaveEdit extends FilterUndoableEdit {
3135
        private long saveTime;
3136
3137
        BeforeSaveEdit(long saveTime) {
3138
            this.saveTime = saveTime;
3139
        }
3140
3141
        @Override
3142
        public boolean replaceEdit(UndoableEdit anEdit) {
3143
            if (delegate == null) {
3144
                delegate = anEdit;
3145
3146
                return true; // signal consumed
3147
            }
3148
3149
            return false;
3150
        }
3151
3152
        @Override
3153
        public boolean addEdit(UndoableEdit anEdit) {
3154
            if (!(anEdit instanceof BeforeModificationEdit) && !(anEdit instanceof SearchBeforeModificationEdit)) {
3155
                /* UndoRedo.addEdit() must not be done lazily
3156
                 * because the edit must be "inserted" before the current one.
3157
                 */
3158
                getUndoRedo().addEdit(new BeforeModificationEdit(saveTime, anEdit));
3159
3160
                return true;
3161
            }
3162
3163
            return false;
3164
        }
3165
3166
        @Override
3167
        public void redo() {
3168
            super.redo();
3169
3170
            if (saveTime == lastSaveTime) {
3171
                justRevertedToNotModified = true;
3172
            }
3173
        }
3174
3175
        @Override
3176
        public boolean isSignificant() {
3177
            return (delegate != null);
3178
        }
3179
    }
3180
3181
    /** Edit that is created by wrapping the given edit.
3182
     * When undone it marks the document as not modified.
3183
     */
3184
    private class BeforeModificationEdit extends FilterUndoableEdit {
3185
        private long saveTime;
3186
3187
        BeforeModificationEdit(long saveTime, UndoableEdit delegate) {
3188
            this.saveTime = saveTime;
3189
            this.delegate = delegate;
3190
            ERR.log(Level.FINEST, null, new Exception("new BeforeModificationEdit(" + saveTime +")")); // NOI18N
3191
        }
3192
3193
        @Override
3194
        public boolean addEdit(UndoableEdit anEdit) {
3195
            if ((delegate == null) && !(anEdit instanceof SearchBeforeModificationEdit)) {
3196
                delegate = anEdit;
3197
3198
                return true;
3199
            }
3200
3201
            return delegate.addEdit(anEdit);
3202
        }
3203
3204
        @Override
3205
        public void undo() {
3206
            super.undo();
3207
3208
            boolean res = saveTime == lastSaveTime;
3209
            ERR.fine("Comparing saveTime and lastSaveTime: " + saveTime + "==" + lastSaveTime + " is " + res); // NOI18N
3210
            if (res) {
3211
                justRevertedToNotModified = true;
3212
            }
3213
        }
3214
    }
3215
3216
    /** This edit is used to search for BeforeModificationEdit in UndoRedo
3217
     * manager. This is not much nice solution, but well, there is not
3218
     * much other chances to get inside UndoRedo.
3219
     */
3220
    private class SearchBeforeModificationEdit extends FilterUndoableEdit {
3221
        SearchBeforeModificationEdit() {
3222
        }
3223
3224
        @Override
3225
        public boolean replaceEdit(UndoableEdit anEdit) {
3226
            if (delegate == null) {
3227
                delegate = anEdit;
3228
3229
                return true; // signal consumed
3230
            }
3231
3232
            return false;
3233
        }
3234
    }
3235
3236
    /** An improved version of UndoRedo manager that locks document before
3237
     * doing any other operations.
3238
     */
3239
    private final static class CESUndoRedoManager extends UndoGroupManager {
3240
        private CloneableEditorSupport support;
3241
3242
        public CESUndoRedoManager(CloneableEditorSupport c) {
3243
            this.support = c;
3244
            super.setLimit(1000);
3245
        }
3246
3247
        @Override
3248
        public void redo() throws javax.swing.undo.CannotRedoException {
3249
            final StyledDocument myDoc = support.getDocument();
3250
3251
            if (myDoc == null) {
3252
                throw new javax.swing.undo.CannotRedoException(); // NOI18N
3253
            }
3254
            
3255
            support.justRevertedToNotModified = false;
3256
            new RenderUndo(0, myDoc);
3257
3258
            if (support.justRevertedToNotModified && support.isAlreadyModified()) {
3259
                support.callNotifyUnmodified();
3260
            }
3261
        }
3262
3263
        @Override
3264
        public void undo() throws javax.swing.undo.CannotUndoException {
3265
            final StyledDocument myDoc = support.getDocument();
3266
3267
            if (myDoc == null) {
3268
                throw new javax.swing.undo.CannotUndoException(); // NOI18N
3269
            }
3270
3271
            support.justRevertedToNotModified = false;
3272
            new RenderUndo(1, myDoc);
3273
3274
            if (support.justRevertedToNotModified && support.isAlreadyModified()) {
3275
                support.callNotifyUnmodified();
3276
            }
3277
        }
3278
3279
        @Override
3280
        public boolean canRedo() {
3281
            final StyledDocument myDoc = support.getDocument();
3282
3283
            return new RenderUndo(2, myDoc, 0, true).booleanResult;
3284
        }
3285
3286
        @Override
3287
        public boolean canUndo() {
3288
            final StyledDocument myDoc = support.getDocument();
3289
3290
            return new RenderUndo(3, myDoc, 0, true).booleanResult;
3291
        }
3292
3293
        @Override
3294
        public int getLimit() {
3295
            final StyledDocument myDoc = support.getDocument();
3296
3297
            return new RenderUndo(4, myDoc).intResult;
3298
        }
3299
3300
        @Override
3301
        public void discardAllEdits() {
3302
            final StyledDocument myDoc = support.getDocument();
3303
            new RenderUndo(5, myDoc);
3304
            // Insert before-save undo event to enable unmodifying undo
3305
            undoableEditHappened(new UndoableEditEvent(support, support.new BeforeSaveEdit(support.lastSaveTime)));
3306
        }
3307
3308
        @Override
3309
        public void setLimit(int l) {
3310
            final StyledDocument myDoc = support.getDocument();
3311
            new RenderUndo(6, myDoc, l);
3312
        }
3313
3314
        @Override
3315
        public boolean canUndoOrRedo() {
3316
            final StyledDocument myDoc = support.getDocument();
3317
3318
            return new RenderUndo(7, myDoc, 0, true).booleanResult;
3319
        }
3320
3321
        @Override
3322
        public java.lang.String getUndoOrRedoPresentationName() {
3323
            if (support.isDocumentReady()) {
3324
                final StyledDocument myDoc = support.getDocument();
3325
                return new RenderUndo(8, myDoc, 0, true).stringResult;
3326
            } else {
3327
                return "";
3328
            }
3329
        }
3330
3331
        @Override
3332
        public java.lang.String getRedoPresentationName() {
3333
            if (support.isDocumentReady()) {
3334
                final StyledDocument myDoc = support.getDocument();
3335
                return new RenderUndo(9, myDoc, 0, true).stringResult;
3336
            } else {
3337
                return "";
3338
            }
3339
        }
3340
3341
        @Override
3342
        public java.lang.String getUndoPresentationName() {
3343
            if (support.isDocumentReady()) {
3344
                final StyledDocument myDoc = support.getDocument();
3345
                return new RenderUndo(10, myDoc, 0, true).stringResult;
3346
            } else {
3347
                return "";
3348
            }
3349
        }
3350
3351
        @Override
3352
        public void undoOrRedo() throws javax.swing.undo.CannotUndoException, javax.swing.undo.CannotRedoException {
3353
            final StyledDocument myDoc = support.getDocument();
3354
3355
            if (myDoc == null) {
3356
                throw new javax.swing.undo.CannotUndoException(); // NOI18N
3357
            }
3358
3359
            support.justRevertedToNotModified = false;
3360
            new RenderUndo(11, myDoc);
3361
3362
            if (support.justRevertedToNotModified && support.isAlreadyModified()) {
3363
                support.callNotifyUnmodified();
3364
            }
3365
        }
3366
3367
        private final class RenderUndo implements Runnable {
3368
            private final int type;
3369
            public boolean booleanResult;
3370
            public int intResult;
3371
            public String stringResult;
3372
            private final boolean readonly;
3373
3374
            public RenderUndo(int type, StyledDocument doc) {
3375
                this(type, doc, 0);
3376
            }
3377
3378
            public RenderUndo(int type, StyledDocument doc, int intValue) {
3379
                this(type, doc, intValue, false);
3380
            }
3381
3382
            public RenderUndo(int type, StyledDocument doc, int intValue, boolean readonly) {
3383
                this.type = type;
3384
                this.intResult = intValue;
3385
                this.readonly = readonly;
3386
3387
                if (!readonly && (doc instanceof NbDocument.WriteLockable)) {
3388
                    ((NbDocument.WriteLockable) doc).runAtomic(this);
3389
                } else {
3390
                    if (readonly && doc != null) {
3391
                        doc.render(this);
3392
                    } else {
3393
                        // if the document is not one of "NetBeans ready"
3394
                        // that supports locking we do not have many
3395
                        // chances to do something. Maybe check for AbstractDocument
3396
                        // and call writeLock using reflection, but better than
3397
                        // that, let's leave this simple for now and wait for
3398
                        // bug reports (if any appear)
3399
                        run();
3400
                    }
3401
                }
3402
            }
3403
3404
            public void run() {
3405
                switch (type) {
3406
                case 0:
3407
                    CESUndoRedoManager.super.redo();
3408
3409
                    break;
3410
3411
                case 1:
3412
                    CESUndoRedoManager.super.undo();
3413
3414
                    break;
3415
3416
                case 2:
3417
                    booleanResult = CESUndoRedoManager.super.canRedo();
3418
3419
                    break;
3420
3421
                case 3:
3422
                    booleanResult = CESUndoRedoManager.super.canUndo();
3423
3424
                    break;
3425
3426
                case 4:
3427
                    intResult = CESUndoRedoManager.super.getLimit();
3428
3429
                    break;
3430
3431
                case 5:
3432
                    CESUndoRedoManager.super.discardAllEdits();
3433
3434
                    break;
3435
3436
                case 6:
3437
                    CESUndoRedoManager.super.setLimit(intResult);
3438
3439
                    break;
3440
3441
                case 7:
3442
                    CESUndoRedoManager.super.canUndoOrRedo();
3443
3444
                    break;
3445
3446
                case 8:
3447
                    stringResult = CESUndoRedoManager.super.getUndoOrRedoPresentationName();
3448
3449
                    break;
3450
3451
                case 9:
3452
                    stringResult = CESUndoRedoManager.super.getRedoPresentationName();
3453
3454
                    break;
3455
3456
                case 10:
3457
                    stringResult = CESUndoRedoManager.super.getUndoPresentationName();
3458
3459
                    break;
3460
3461
                case 11:
3462
                    CESUndoRedoManager.super.undoOrRedo();
3463
3464
                    break;
3465
3466
                default:
3467
                    throw new IllegalArgumentException("Unknown type: " + type);
3468
                }
3469
            }
3470
        }
3471
    }
3472
3473
    /**
3474
     * <tt>UndoGroupManager</tt> extends {@link UndoManager}
3475
     * and allows explicit control of what
3476
     * <tt>UndoableEdit</tt>s are coalesced into compound edits,
3477
     * rather than using the rules defined by the edits themselves.
3478
     * Groups are defined using BEGIN_COMMIT_GROUP and END_COMMIT_GROUP.
3479
     * Send these to UndoableEditListener. These must always be paired.
3480
     * <p>
3481
     * These use cases are supported.
3482
     * </p>
3483
     * <ol>
3484
     * <li> Default behavior is defined by {@link UndoManager}.</li>
3485
     * <li> <tt>UnddoableEdit</tt>s issued between {@link #BEGIN_COMMIT_GROUP}
3486
     * and {@link #END_COMMIT_GROUP} are placed into a single
3487
     * {@link CompoundEdit}.
3488
     * Thus <tt>undo()</tt> and <tt>redo()</tt> treat them 
3489
     * as a single undo/redo.</li>
3490
     * <li>BEGIN/END nest.</li>
3491
     * <li> Issue MARK_COMMIT_GROUP to commit accumulated
3492
     * <tt>UndoableEdit</tt>s into a single <tt>CompoundEdit</tt>
3493
     * and to continue accumulating;
3494
     * an application could do this at strategic points, such as EndOfLine
3495
     * input or cursor movement.</li>
3496
     * </ol>
3497
     * @see UndoManager
3498
     */
3499
    private static class UndoGroupManager extends UndoRedo.Manager {
3500
        /** signals that edits are being accumulated */
3501
        private int buildUndoGroup;
3502
        /** accumulate edits here in undoGroup */
3503
        private CompoundEdit undoGroup;
3504
        /**
3505
         * Signal that nested group started and that current undo group
3506
         * must be committed if edit is added. Then can avoid doing the commit
3507
         * if the nested group turns out to be empty.
3508
         */
3509
        private int needsNestingCommit;
3510
3511
        /**
3512
         * Start a group of edits which will be committed as a single edit
3513
         * for purpose of undo/redo.
3514
         * Nesting semantics are that any BEGIN_COMMIT_GROUP and
3515
         * END_COMMIT_GROUP delimits a commit-group, unless the group is
3516
         * empty in which case the begin/end is ignored.
3517
         * While coalescing edits, any undo/redo/save implicitly delimits
3518
         * a commit-group.
3519
         */
3520
        static final UndoableEdit BEGIN_COMMIT_GROUP = new CommitGroupEdit();
3521
        /** End a group of edits. */
3522
        static final UndoableEdit END_COMMIT_GROUP = new CommitGroupEdit();
3523
        /**
3524
         * Any coalesced edits become a commit-group and a new commit-group
3525
         * is started.
3526
         */
3527
        static final UndoableEdit MARK_COMMIT_GROUP = new CommitGroupEdit();
3528
3529
        /** SeparateEdit tags an UndoableEdit so the
3530
         * UndoGroupManager does not coalesce it.
3531
         */
3532
        interface SeparateEdit {
3533
        }
3534
3535
        private static class CommitGroupEdit extends AbstractUndoableEdit {
3536
            @Override
3537
            public boolean isSignificant() {
3538
                return false;
3539
            }
3540
3541
            @Override
3542
            public boolean canRedo()
3543
            {
3544
                return true;
3545
            }
3546
3547
            @Override
3548
            public boolean canUndo()
3549
            {
3550
                return true;
3551
            }
3552
        }
3553
3554
        @Override
3555
        public void undoableEditHappened(UndoableEditEvent ue)
3556
        {
3557
            if(ue.getEdit() == BEGIN_COMMIT_GROUP) {
3558
                beginUndoGroup();
3559
            } else if(ue.getEdit() == END_COMMIT_GROUP) {
3560
                endUndoGroup();
3561
            } else if(ue.getEdit() == MARK_COMMIT_GROUP) {
3562
                commitUndoGroup();
3563
            } else {
3564
                super.undoableEditHappened(ue);
3565
            }
3566
        }
3567
3568
        /**
3569
         * Direct this <tt>UndoGroupManager</tt> to begin coalescing any
3570
         * <tt>UndoableEdit</tt>s that are added into a <tt>CompoundEdit</tt>.
3571
         * <p>If edits are already being coalesced and some have been 
3572
         * accumulated, they are flagged for commitment as an atomic group and
3573
         * a new group will be started.
3574
         * @see #addEdit
3575
         * @see #endUndoGroup
3576
         */
3577
        private synchronized void beginUndoGroup() {
3578
            if(undoGroup != null)
3579
                needsNestingCommit++;
3580
            ERR.log(Level.FINE, "beginUndoGroup: nesting {0}", buildUndoGroup);
3581
            buildUndoGroup++;
3582
        }
3583
3584
        /**
3585
         * Direct this <tt>UndoGroupManager</tt> to stop coalescing edits.
3586
         * Until <tt>beginUndoGroupManager</tt> is invoked,
3587
         * any received <tt>UndoableEdit</tt>s are added singly.
3588
         * <p>
3589
         * This has no effect if edits are not being coalesced, for example
3590
         * if <tt>beginUndoGroup</tt> has not been called.
3591
         */
3592
        private synchronized void endUndoGroup() {
3593
            buildUndoGroup--;
3594
            ERR.log(Level.FINE, "endUndoGroup: nesting {0}", buildUndoGroup);
3595
            if(buildUndoGroup < 0) {
3596
                ERR.log(Level.INFO, null, new Exception("endUndoGroup without beginUndoGroup"));
3597
                // slam buildUndoGroup to 0 to disable nesting
3598
                buildUndoGroup = 0;
3599
            }
3600
            if(needsNestingCommit <= 0)
3601
                commitUndoGroup();
3602
            if(--needsNestingCommit < 0)
3603
                needsNestingCommit = 0;
3604
        }
3605
3606
        /**
3607
         * Commit any accumulated <tt>UndoableEdit</tt>s as an atomic
3608
         * <tt>undo</tt>/<tt>redo</tt> group. {@link CompoundEdit#end}
3609
         * is invoked on the <tt>CompoundEdit</tt> and it is added as a single
3610
         * <tt>UndoableEdit</tt> to this <tt>UndoManager</tt>.
3611
         * <p>
3612
         * If edits are currently being coalesced, a new undo group is started.
3613
         * This has no effect if edits are not being coalesced, for example
3614
         * <tt>beginUndoGroup</tt> has not been called.
3615
         */
3616
        private synchronized void commitUndoGroup() {
3617
            if(undoGroup == null) {
3618
                return;
3619
            }
3620
3621
            // undoGroup is being set to null,
3622
            // needsNestingCommit has no meaning now
3623
            needsNestingCommit = 0;
3624
3625
            // super.addEdit may end up in this.addEdit,
3626
            // so buildUndoGroup must be false
3627
            int saveBuildUndoGroup = buildUndoGroup;
3628
            buildUndoGroup = 0;
3629
3630
            undoGroup.end();
3631
            super.addEdit(undoGroup);
3632
            undoGroup = null;
3633
3634
            buildUndoGroup = saveBuildUndoGroup;
3635
        }
3636
3637
        /** Add this edit separately, not part of a group.
3638
         * @return super.addEdit
3639
         */
3640
        private boolean commitAddEdit(UndoableEdit anEdit) {
3641
            commitUndoGroup();
3642
3643
            int saveBuildUndoGroup = buildUndoGroup;
3644
            buildUndoGroup = 0;
3645
            boolean f = super.addEdit(anEdit);
3646
            //boolean f = addEdit(undoGroup);
3647
            buildUndoGroup = saveBuildUndoGroup;
3648
            return f;
3649
        }
3650
3651
        /**
3652
         * If there's a pending undo group that needs to be committed
3653
         * then commit it.
3654
         * If this <tt>UndoManager</tt> is coalescing edits then add
3655
         * <tt>anEdit</tt> to the accumulating <tt>CompoundEdit</tt>.
3656
         * Otherwise, add it to this UndoManager. In either case the
3657
         * edit is saved for later <tt>undo</tt> or <tt>redo</tt>.
3658
         * @return {@inheritDoc}
3659
         * @see #beginUndoGroup
3660
         * @see #endUndoGroup
3661
         */
3662
        @Override
3663
        public synchronized boolean addEdit(UndoableEdit anEdit) {
3664
            if(!isInProgress())
3665
                return false;
3666
3667
            if(needsNestingCommit > 0) {
3668
                commitUndoGroup();
3669
            }
3670
3671
            if(buildUndoGroup > 0) {
3672
                if(anEdit instanceof SeparateEdit)
3673
                    return commitAddEdit(anEdit);
3674
                if(undoGroup == null)
3675
                    undoGroup = new CompoundEdit();
3676
                return undoGroup.addEdit(anEdit);
3677
            } else {
3678
                return super.addEdit(anEdit);
3679
            }
3680
        }
3681
3682
        @Override
3683
        public synchronized void discardAllEdits() {
3684
            commitUndoGroup();
3685
            super.discardAllEdits();
3686
        }
3687
3688
        //
3689
        // TODO: limits
3690
        //
3691
3692
        @Override
3693
        public synchronized void undoOrRedo() {
3694
            commitUndoGroup();
3695
            super.undoOrRedo();
3696
        }
3697
3698
        @Override
3699
        public synchronized boolean canUndoOrRedo() {
3700
            if(undoGroup != null)
3701
                return true;
3702
            return super.canUndoOrRedo();
3703
        }
3704
3705
        @Override
3706
        public synchronized void undo() {
3707
            commitUndoGroup();
3708
            super.undo();
3709
        }
3710
3711
        @Override
3712
        public synchronized boolean canUndo() {
3713
            if(undoGroup != null)
3714
                return true;
3715
            return super.canUndo();
3716
        }
3717
3718
        @Override
3719
        public synchronized void redo() {
3720
            if(undoGroup != null)
3721
                throw new CannotRedoException();
3722
            super.redo();
3723
        }
3724
3725
        @Override
3726
        public synchronized boolean canRedo() {
3727
            if(undoGroup != null)
3728
                return false;
3729
            return super.canRedo();
3730
        }
3731
3732
        @Override
3733
        public synchronized void end() {
3734
            commitUndoGroup();
3735
            super.end();
3736
        }
3737
3738
        @Override
3739
        public synchronized String getUndoOrRedoPresentationName() {
3740
            if(undoGroup != null)
3741
                return undoGroup.getUndoPresentationName();
3742
            return super.getUndoOrRedoPresentationName();
3743
        }
3744
3745
        @Override
3746
        public synchronized String getUndoPresentationName() {
3747
            if(undoGroup != null)
3748
                return undoGroup.getUndoPresentationName();
3749
            return super.getUndoPresentationName();
3750
        }
3751
3752
        @Override
3753
        public synchronized String getRedoPresentationName() {
3754
            if(undoGroup != null)
3755
                return undoGroup.getRedoPresentationName();
3756
            return super.getRedoPresentationName();
3757
        }
3758
3759
        @Override
3760
        public boolean isSignificant() {
3761
            if(undoGroup != null && undoGroup.isSignificant()) {
3762
                return true;
3763
            }
3764
            return super.isSignificant();
3765
        }
3766
3767
        @Override
3768
        public synchronized void die() {
3769
            commitUndoGroup();
3770
            super.die();
3771
        }
3772
3773
        @Override
3774
        public String getPresentationName() {
3775
            if(undoGroup != null)
3776
                return undoGroup.getPresentationName();
3777
            return super.getPresentationName();
3778
        }
3779
3780
        // The protected methods are only accessed from
3781
        // synchronized methods that do commitUndoGroup
3782
        // so they do not need to be overridden in this class
3783
    }
3784
3785
    /** Special runtime exception that holds the original I/O failure.
2948
    /** Special runtime exception that holds the original I/O failure.
3786
     */
2949
     */
3787
    static final class DelegateIOExc extends IllegalStateException {
2950
    static final class DelegateIOExc extends IllegalStateException {
Lines 3791-3794 Link Here
3791
        }
2954
        }
3792
    }
2955
    }
3793
2956
2957
    private final class DocFilter extends DocumentFilter {
2958
        
2959
        final DocumentFilter origFilter;
2960
        
2961
        DocFilter(DocumentFilter origFilter) {
2962
            this.origFilter = origFilter;
2963
        }
2964
2965
        @Override
2966
        public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException {
2967
            boolean origModified = checkModificationAllowed(offset);
2968
            boolean success = false;
2969
            try {
2970
                if (origFilter != null) {
2971
                    origFilter.insertString(fb, offset, string, attr);
2972
                } else {
2973
                    super.insertString(fb, offset, string, attr);
2974
                }
2975
                success = true;
2976
            } finally {
2977
                if (!success) {
2978
                    if (!origModified) {
2979
                        callNotifyUnmodified();
2980
                    }
2981
                }
2982
            }
2983
        }
2984
2985
        @Override
2986
        public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
2987
            boolean origModified = checkModificationAllowed(offset);
2988
            boolean success = false;
2989
            try {
2990
                if (origFilter != null) {
2991
                    origFilter.remove(fb, offset, length);
2992
                } else {
2993
                    super.remove(fb, offset, length);
2994
                }
2995
                success = true;
2996
            } finally {
2997
                if (!success) {
2998
                    if (!origModified) {
2999
                        callNotifyUnmodified();
3000
                    }
3001
                }
3002
            }
3003
        }
3004
3005
        @Override
3006
        public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
3007
            boolean origModified = checkModificationAllowed(offset);
3008
            boolean success = false;
3009
            try {
3010
                if (origFilter != null) {
3011
                    origFilter.replace(fb, offset, length, text, attrs);
3012
                } else {
3013
                    super.replace(fb, offset, length, text, attrs);
3014
                }
3015
                success = true;
3016
            } finally {
3017
                if (!success) {
3018
                    if (!origModified) {
3019
                        callNotifyUnmodified();
3020
                    }
3021
                }
3022
            }
3023
        }
3024
        
3025
        private boolean checkModificationAllowed(int offset) throws BadLocationException {
3026
            boolean alreadyModified = isAlreadyModified();
3027
            if (!callNotifyModified()) {
3028
                throw new BadLocationException("Modification not allowed", offset); // NOI18N
3029
            }
3030
            return alreadyModified;
3031
        }
3032
3033
    }
3034
3794
}
3035
}
(-)a/openide.text/src/org/openide/text/FilterDocument.java (-1 / +8 lines)
Lines 99-110 Link Here
99
99
100
    /* Gets document property by key */
100
    /* Gets document property by key */
101
    public Object getProperty(Object key) {
101
    public Object getProperty(Object key) {
102
        if (key == DocumentFilter.class && original instanceof AbstractDocument) {
103
            return ((AbstractDocument)original).getDocumentFilter();
104
        }
102
        return original.getProperty(key);
105
        return original.getProperty(key);
103
    }
106
    }
104
107
105
    /* Puts new property of document */
108
    /* Puts new property of document */
106
    public void putProperty(Object key, Object value) {
109
    public void putProperty(Object key, Object value) {
107
        original.putProperty(key, value);
110
        if (key == DocumentFilter.class && original instanceof AbstractDocument) {
111
            ((AbstractDocument)original).setDocumentFilter((DocumentFilter)value);
112
        } else {
113
            original.putProperty(key, value);
114
        }
108
    }
115
    }
109
116
110
    /* Removes portion of a document */
117
    /* Removes portion of a document */
(-)a/openide.text/src/org/openide/text/UndoGroupManager.java (+373 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * Contributor(s):
28
 *
29
 * The Original Software is NetBeans. The Initial Developer of the Original
30
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
31
 * Microsystems, Inc. All Rights Reserved.
32
 *
33
 * If you wish your version of this file to be governed by only the CDDL
34
 * or only the GPL Version 2, indicate your decision by adding
35
 * "[Contributor] elects to include this software in this distribution
36
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
37
 * single choice of license, a recipient has the option to distribute
38
 * your version of this file under either the CDDL, the GPL Version 2 or
39
 * to extend the choice of license to its licensees as provided above.
40
 * However, if you add GPL Version 2 code and therefore, elected the GPL
41
 * Version 2 license, then the option applies only if the new code is
42
 * made subject to such option by the copyright holder.
43
 */
44
45
package org.openide.text;
46
47
import java.util.logging.Level;
48
import java.util.logging.Logger;
49
import javax.swing.event.UndoableEditEvent;
50
import javax.swing.undo.*;
51
import org.openide.awt.UndoRedo;
52
53
54
/**
55
 * <tt>UndoGroupManager</tt> extends {@link UndoManager}
56
 * and allows explicit control of what
57
 * <tt>UndoableEdit</tt>s are coalesced into compound edits,
58
 * rather than using the rules defined by the edits themselves.
59
 * Groups are defined using BEGIN_COMMIT_GROUP and END_COMMIT_GROUP.
60
 * Send these to UndoableEditListener. These must always be paired.
61
 * <p>
62
 * These use cases are supported.
63
 * </p>
64
 * <ol>
65
 * <li> Default behavior is defined by {@link UndoManager}.</li>
66
 * <li> <tt>UnddoableEdit</tt>s issued between {@link #BEGIN_COMMIT_GROUP}
67
 * and {@link #END_COMMIT_GROUP} are placed into a single
68
 * {@link CompoundEdit}.
69
 * Thus <tt>undo()</tt> and <tt>redo()</tt> treat them 
70
 * as a single undo/redo.</li>
71
 * <li>BEGIN/END nest.</li>
72
 * <li> Issue MARK_COMMIT_GROUP to commit accumulated
73
 * <tt>UndoableEdit</tt>s into a single <tt>CompoundEdit</tt>
74
 * and to continue accumulating;
75
 * an application could do this at strategic points, such as EndOfLine
76
 * input or cursor movement.</li>
77
 * </ol>
78
 * @see UndoManager
79
 * @author Ernie Rael
80
 */
81
class UndoGroupManager extends UndoRedo.Manager {
82
83
    // -J-Dorg.openide.text.UndoGroupManager.level=FINE
84
    private static final Logger LOG = Logger.getLogger(UndoGroupManager.class.getName());
85
86
    /** signals that edits are being accumulated */
87
    private int buildUndoGroup;
88
89
    /** accumulate edits here in undoGroup */
90
    private CompoundEdit undoGroup;
91
92
    /**
93
     * Signal that nested group started and that current undo group
94
     * must be committed if edit is added. Then can avoid doing the commit
95
     * if the nested group turns out to be empty.
96
     */
97
    private int needsNestingCommit;
98
99
    /**
100
     * Start a group of edits which will be committed as a single edit
101
     * for purpose of undo/redo.
102
     * Nesting semantics are that any BEGIN_COMMIT_GROUP and
103
     * END_COMMIT_GROUP delimits a commit-group, unless the group is
104
     * empty in which case the begin/end is ignored.
105
     * While coalescing edits, any undo/redo/save implicitly delimits
106
     * a commit-group.
107
     */
108
    static final UndoableEdit BEGIN_COMMIT_GROUP = new CommitGroupEdit();
109
110
    /** End a group of edits. */
111
    static final UndoableEdit END_COMMIT_GROUP = new CommitGroupEdit();
112
    
113
    /**
114
     * Any coalesced edits become a commit-group and a new commit-group
115
     * is started.
116
     */
117
    static final UndoableEdit MARK_COMMIT_GROUP = new CommitGroupEdit();
118
119
    private static class CommitGroupEdit extends AbstractUndoableEdit {
120
        @Override
121
        public boolean isSignificant() {
122
            return false;
123
        }
124
125
        @Override
126
        public boolean canRedo()
127
        {
128
            return true;
129
        }
130
131
        @Override
132
        public boolean canUndo()
133
        {
134
            return true;
135
        }
136
    }
137
138
    @Override
139
    public void undoableEditHappened(UndoableEditEvent ue)
140
    {
141
        if(ue.getEdit() == BEGIN_COMMIT_GROUP) {
142
            beginUndoGroup();
143
        } else if(ue.getEdit() == END_COMMIT_GROUP) {
144
            endUndoGroup();
145
        } else if(ue.getEdit() == MARK_COMMIT_GROUP) {
146
            commitUndoGroup();
147
        } else {
148
            super.undoableEditHappened(ue);
149
        }
150
    }
151
152
    /**
153
     * Direct this <tt>UndoGroupManager</tt> to begin coalescing any
154
     * <tt>UndoableEdit</tt>s that are added into a <tt>CompoundEdit</tt>.
155
     * <p>If edits are already being coalesced and some have been 
156
     * accumulated, they are flagged for commitment as an atomic group and
157
     * a new group will be started.
158
     * @see #addEdit
159
     * @see #endUndoGroup
160
     */
161
    private synchronized void beginUndoGroup() {
162
        if(undoGroup != null)
163
            needsNestingCommit++;
164
        LOG.log(Level.FINE, "beginUndoGroup: nesting {0}", buildUndoGroup);
165
        buildUndoGroup++;
166
    }
167
168
    /**
169
     * Direct this <tt>UndoGroupManager</tt> to stop coalescing edits.
170
     * Until <tt>beginUndoGroupManager</tt> is invoked,
171
     * any received <tt>UndoableEdit</tt>s are added singly.
172
     * <p>
173
     * This has no effect if edits are not being coalesced, for example
174
     * if <tt>beginUndoGroup</tt> has not been called.
175
     */
176
    private synchronized void endUndoGroup() {
177
        buildUndoGroup--;
178
        LOG.log(Level.FINE, "endUndoGroup: nesting {0}", buildUndoGroup);
179
        if(buildUndoGroup < 0) {
180
            LOG.log(Level.INFO, null, new Exception("endUndoGroup without beginUndoGroup"));
181
            // slam buildUndoGroup to 0 to disable nesting
182
            buildUndoGroup = 0;
183
        }
184
        if(needsNestingCommit <= 0)
185
            commitUndoGroup();
186
        if(--needsNestingCommit < 0)
187
            needsNestingCommit = 0;
188
    }
189
190
    /**
191
     * Commit any accumulated <tt>UndoableEdit</tt>s as an atomic
192
     * <tt>undo</tt>/<tt>redo</tt> group. {@link CompoundEdit#end}
193
     * is invoked on the <tt>CompoundEdit</tt> and it is added as a single
194
     * <tt>UndoableEdit</tt> to this <tt>UndoManager</tt>.
195
     * <p>
196
     * If edits are currently being coalesced, a new undo group is started.
197
     * This has no effect if edits are not being coalesced, for example
198
     * <tt>beginUndoGroup</tt> has not been called.
199
     */
200
    private synchronized void commitUndoGroup() {
201
        if(undoGroup == null) {
202
            return;
203
        }
204
205
        // undoGroup is being set to null,
206
        // needsNestingCommit has no meaning now
207
        needsNestingCommit = 0;
208
209
        // super.addEdit may end up in this.addEdit,
210
        // so buildUndoGroup must be false
211
        int saveBuildUndoGroup = buildUndoGroup;
212
        buildUndoGroup = 0;
213
214
        undoGroup.end();
215
        super.addEdit(undoGroup);
216
        undoGroup = null;
217
218
        buildUndoGroup = saveBuildUndoGroup;
219
    }
220
221
    /** Add this edit separately, not part of a group.
222
     * @return super.addEdit
223
     */
224
    private boolean commitAddEdit(UndoableEdit anEdit) {
225
        commitUndoGroup();
226
227
        int saveBuildUndoGroup = buildUndoGroup;
228
        buildUndoGroup = 0;
229
        boolean f = super.addEdit(anEdit);
230
        //boolean f = addEdit(undoGroup);
231
        buildUndoGroup = saveBuildUndoGroup;
232
        return f;
233
    }
234
235
    /**
236
     * If there's a pending undo group that needs to be committed
237
     * then commit it.
238
     * If this <tt>UndoManager</tt> is coalescing edits then add
239
     * <tt>anEdit</tt> to the accumulating <tt>CompoundEdit</tt>.
240
     * Otherwise, add it to this UndoManager. In either case the
241
     * edit is saved for later <tt>undo</tt> or <tt>redo</tt>.
242
     * @return {@inheritDoc}
243
     * @see #beginUndoGroup
244
     * @see #endUndoGroup
245
     */
246
    @Override
247
    public synchronized boolean addEdit(UndoableEdit anEdit) {
248
        if(!isInProgress())
249
            return false;
250
251
        if(needsNestingCommit > 0) {
252
            commitUndoGroup();
253
        }
254
255
        if(buildUndoGroup > 0) {
256
            if(anEdit instanceof WrapUndoEdit)
257
                return commitAddEdit(anEdit);
258
            if(undoGroup == null)
259
                undoGroup = new CompoundEdit();
260
            return undoGroup.addEdit(anEdit);
261
        } else {
262
            return super.addEdit(anEdit);
263
        }
264
    }
265
266
    @Override
267
    public synchronized void discardAllEdits() {
268
        commitUndoGroup();
269
        super.discardAllEdits();
270
    }
271
272
    //
273
    // TODO: limits
274
    //
275
276
    @Override
277
    public synchronized void undoOrRedo() {
278
        commitUndoGroup();
279
        super.undoOrRedo();
280
    }
281
282
    @Override
283
    public synchronized boolean canUndoOrRedo() {
284
        if(undoGroup != null)
285
            return true;
286
        return super.canUndoOrRedo();
287
    }
288
289
    @Override
290
    public synchronized void undo() {
291
        commitUndoGroup();
292
        super.undo();
293
    }
294
295
    @Override
296
    public synchronized boolean canUndo() {
297
        if(undoGroup != null)
298
            return true;
299
        return super.canUndo();
300
    }
301
302
    @Override
303
    public synchronized void redo() {
304
        if(undoGroup != null)
305
            throw new CannotRedoException();
306
        super.redo();
307
    }
308
309
    @Override
310
    public synchronized boolean canRedo() {
311
        if(undoGroup != null)
312
            return false;
313
        return super.canRedo();
314
    }
315
316
    @Override
317
    public synchronized void end() {
318
        commitUndoGroup();
319
        super.end();
320
    }
321
322
    @Override
323
    public synchronized String getUndoOrRedoPresentationName() {
324
        if(undoGroup != null)
325
            return undoGroup.getUndoPresentationName();
326
        return super.getUndoOrRedoPresentationName();
327
    }
328
329
    @Override
330
    public synchronized String getUndoPresentationName() {
331
        if(undoGroup != null)
332
            return undoGroup.getUndoPresentationName();
333
        return super.getUndoPresentationName();
334
    }
335
336
    @Override
337
    public synchronized String getRedoPresentationName() {
338
        if(undoGroup != null)
339
            return undoGroup.getRedoPresentationName();
340
        return super.getRedoPresentationName();
341
    }
342
343
    @Override
344
    public boolean isSignificant() {
345
        if(undoGroup != null && undoGroup.isSignificant()) {
346
            return true;
347
        }
348
        return super.isSignificant();
349
    }
350
351
    @Override
352
    public synchronized void die() {
353
        commitUndoGroup();
354
        super.die();
355
    }
356
357
    @Override
358
    public String getPresentationName() {
359
        if(undoGroup != null)
360
            return undoGroup.getPresentationName();
361
        return super.getPresentationName();
362
    }
363
364
    @Override
365
    public final WrapUndoEdit lastEdit() { // make lastEdit() accessible
366
        return (WrapUndoEdit) super.lastEdit();
367
    }
368
369
    // The protected methods are only accessed from
370
    // synchronized methods that do commitUndoGroup
371
    // so they do not need to be overridden in this class
372
373
}
(-)a/openide.text/src/org/openide/text/UndoRedoManager.java (+527 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * Contributor(s):
28
 *
29
 * The Original Software is NetBeans. The Initial Developer of the Original
30
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
31
 * Microsystems, Inc. All Rights Reserved.
32
 *
33
 * If you wish your version of this file to be governed by only the CDDL
34
 * or only the GPL Version 2, indicate your decision by adding
35
 * "[Contributor] elects to include this software in this distribution
36
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
37
 * single choice of license, a recipient has the option to distribute
38
 * your version of this file under either the CDDL, the GPL Version 2 or
39
 * to extend the choice of license to its licensees as provided above.
40
 * However, if you add GPL Version 2 code and therefore, elected the GPL
41
 * Version 2 license, then the option applies only if the new code is
42
 * made subject to such option by the copyright holder.
43
 */
44
45
package org.openide.text;
46
47
import java.util.logging.Level;
48
import java.util.logging.Logger;
49
import javax.swing.text.StyledDocument;
50
import javax.swing.undo.*;
51
52
53
/**
54
 * An improved version of UndoRedo manager that locks document before
55
 * doing any other operations.
56
 * <br/>
57
 * It supports grouping of undoable edits by extending UndoGroupManager.
58
 * <br/>
59
 * It supports save actions that produce a compound undoable edit.
60
 * <br/>
61
 * 
62
 * <p>
63
 * Following requirements should be met:
64
 * <ul>
65
 *   <li>When saving document extra save actions are performed producing compound saveActionsEdit..</li>
66
 *   <li>When undoing just performed save the saveActionsEdit should be undone at once with the last
67
 *     performed edit.</li>
68
 *   <li>When save of the document was just performed a next edit must not be merged with last edit
69
 *     so that the savepoint can be retained.</li>
70
 *   <li></li>
71
 * </ul>
72
 * </p>
73
 * 
74
 * @author Miloslav Metelka
75
 * @author Jaroslav Tulach
76
 */
77
final class UndoRedoManager extends UndoGroupManager {
78
    
79
    // -J-Dorg.openide.text.UndoRedoManager.level=FINE
80
    private static final Logger LOG = Logger.getLogger(UndoRedoManager.class.getName());
81
82
    /**
83
     * Marker edit for the state when undo manager is right at the savepoint.
84
     * <br/>
85
     * Next performed edit will set afterSaveEdit field.
86
     */
87
    static final UndoableEdit SAVEPOINT = new CompoundEdit();
88
89
    CloneableEditorSupport support;
90
    
91
    /**
92
     * Undo edit that is right after/before "save point" in undo manager's queue.
93
     * <br/>
94
     * Subsequent addEdit(), undo() and redo() operations will modify
95
     * the field to point to neighbor edit of the save point.
96
     * <br/>
97
     * The field may be set to SAVEPOINT special value in case the undo manager
98
     * is set right to the savepoint.
99
     */
100
    UndoableEdit savepointEdit;
101
    
102
    /**
103
     * Whether undo manager's undo()/redo() operations currently operate before/after
104
     * the savepoint (when right at savepoint the value is undefined).
105
     */
106
    boolean beforeSavepoint;
107
    
108
    /**
109
     * Edit that was added by last performed operation (undo clears this field).
110
     */
111
    UndoableEdit lastAddedOrRedoneEdit;
112
113
    /**
114
     * Undoable edit created as result of running save actions by using
115
     * "beforeSaveRunnable" document property (See CloneableEditorSupport).
116
     * <br/>
117
     * If saving occurs right at UM.edits end (i.e. either UM.addEdit() was just performed
118
     * or lastAddedEdit.redo() was performed) then the save actions edit
119
     * is merged with the last added edit.
120
     * <br/>
121
     * If UM.edits is empty (it could possibly happen when document is not saved
122
     * and discardAllEdits() was called for some reason) then the save actions edit
123
     * is not added to UM.edits (just edit.die() is called).
124
     * <br/>
125
     * When the save actions edit is done in any other situation then it must be
126
     * <ul>
127
     *   <li>undone when the UM is at savepoint and UM.undo() or UM.redo() is done.</li>
128
     *   <li>redone when edit right before savepoint is redone.</li>
129
     *   <li>redone when edit right after savepoint is undone.</li>
130
     * </ul>
131
     */
132
    private CompoundEdit saveActionsEdit;
133
    
134
    /**
135
     * Set to true when the CES gave control to save actions that may produce
136
     * undoable edits which need to be coallesced into a special compoound edit.
137
     */
138
    private boolean performingSaveActions;
139
    
140
    /**
141
     * Flag to check whether support.notifyUnmodified() should be called
142
     * - it's necessary to do it outside of atomicLock acquired by DocLockedRun.
143
     */
144
    private boolean callNotifyUnmodified;
145
146
147
    public UndoRedoManager(CloneableEditorSupport support) {
148
        this.support = support;
149
        super.setLimit(1000);
150
    }
151
    
152
    void setPerformingSaveActions(boolean performingSaveActions) {
153
        if (performingSaveActions != this.performingSaveActions) {
154
            this.performingSaveActions = performingSaveActions;
155
            if (performingSaveActions) {
156
                clearSaveActionsEdit();
157
                saveActionsEdit = new CompoundEdit();
158
                checkLogOp("    NEW-saveActionsEdit", saveActionsEdit); // NOI18N
159
            } else { // Stop performing save actions
160
                saveActionsEdit.end();
161
                checkLogOp("    COMPLETED-saveActionsEdit", saveActionsEdit); // NOI18N
162
                UndoableEdit lastEdit = lastEdit();
163
                checkLogOp("        lastEdit", lastEdit); // NOI18N
164
                if (lastAddedOrRedoneEdit == lastEdit) { // At UM.edits end
165
                    if (lastEdit != null) { // Concatenate with last one
166
                        WrapUndoEdit lastWrapEdit = (WrapUndoEdit) lastEdit;
167
                        CompoundEdit compoundEdit = new CompoundEdit();
168
                        compoundEdit.addEdit(lastWrapEdit.delegate());
169
                        compoundEdit.addEdit(saveActionsEdit);
170
                        compoundEdit.end();
171
                        lastWrapEdit.setDelegate(compoundEdit);
172
                        checkLogOp("    compoundEdit", compoundEdit); // NOI18N
173
174
                    } else { // No edits present in UM.edits
175
                        // This state might happen when e.g. discardAllEdits() was called
176
                        // but the document was not at savepoint (otherwise the document
177
                        // would be saved so no save actions would be performed).
178
                        // To not make the save actions an extra edit in the UM.edits
179
                        // the save actions are not added to the queue.
180
                        saveActionsEdit.die();
181
                        checkLogOp("    null-lastEdit->saveActionsEdit.die", saveActionsEdit); // NOI18N
182
                    }
183
                    saveActionsEdit = null; // Save actions serviced
184
185
                } else { // Save occurred somewhere inside existing UM.edits
186
                    // saveActionsEdit contains changes done before save operation.
187
                    // To enable redo of existing changes in UM.edits
188
                    // this extra edit must be undone when doing undo or redo from savepoint
189
                    // and redone when doing undo or redo to savepoint.
190
                }
191
            }
192
        }
193
    }
194
    
195
    void markSavepoint() {
196
        savepointEdit = SAVEPOINT;
197
    }
198
    
199
    boolean isAtSavepoint() {
200
        return (savepointEdit == SAVEPOINT);
201
    }
202
    
203
    private void markSavepointAndUnmodified() {
204
        savepointEdit = SAVEPOINT;
205
        callNotifyUnmodified = true;
206
    }
207
    
208
    private void checkCallNotifyUnmodified() {
209
        if (callNotifyUnmodified) {
210
            callNotifyUnmodified = false;
211
            if (support.isAlreadyModified()) {
212
                support.callNotifyUnmodified();
213
            }
214
        }
215
    }
216
    
217
    void beforeUndoCheck(WrapUndoEdit edit) {
218
        if (isAtSavepoint()) { // Undoing edit right before savepoint.
219
            checkLogOp("beforeUndoCheck-atSavepoint", edit); // NOI18N
220
            if (saveActionsEdit != null) {
221
                checkLogOp("    saveActionsEdit.undo()", saveActionsEdit); // NOI18N
222
                saveActionsEdit.undo();
223
            }
224
        }
225
    }
226
    
227
    void afterUndoCheck(WrapUndoEdit edit) {
228
        if (isAtSavepoint()) { // Undoing edit right before savepoint.
229
            checkLogOp("afterUndoCheck-atSavepoint", edit); // NOI18N
230
            // saveActionsEdit already processed by checkSavepointBeforeUndo()
231
            beforeSavepoint = true;
232
            savepointEdit = edit;
233
234
        } else if (savepointEdit == edit) { // Undone to savepoint
235
            if (saveActionsEdit != null) {
236
                checkLogOp("    saveActionsEdit.redo()", saveActionsEdit); // NOI18N
237
                saveActionsEdit.redo();
238
            }
239
            checkLogOp("afterUndoCheck-becomesSavepoint-markUnmodified", edit); // NOI18N
240
            assert (!beforeSavepoint) : "Expected to be behind savepoint"; // NOI18N
241
            markSavepointAndUnmodified();
242
        }
243
        lastAddedOrRedoneEdit = null;
244
    }
245
    
246
    void beforeRedoCheck(WrapUndoEdit edit) {
247
        if (isAtSavepoint()) { // Redoing edit right before savepoint.
248
            checkLogOp("beforeRedoCheck-atSavepoint", edit); // NOI18N
249
            if (saveActionsEdit != null) {
250
                checkLogOp("    saveActionsEdit.undo()", saveActionsEdit); // NOI18N
251
                saveActionsEdit.undo();
252
            }
253
        }
254
    }
255
256
    void afterRedoCheck(WrapUndoEdit edit) {
257
        if (isAtSavepoint()) { // Redoing edit right before savepoint.
258
            checkLogOp("afterRedoCheck-atSavepoint", edit); // NOI18N
259
            // saveActionsEdit already processed by checkSavepointBeforeUndo()
260
            beforeSavepoint = false;
261
            savepointEdit = edit;
262
263
        } else if (savepointEdit == edit) { // Redone to savepoint
264
            if (saveActionsEdit != null) {
265
                checkLogOp("    saveActionsEdit.redo()", saveActionsEdit); // NOI18N
266
                saveActionsEdit.redo();
267
            }
268
            checkLogOp("afterRedoCheck-becomesSavepoint", edit); // NOI18N
269
            assert (beforeSavepoint) : "Expected to be before savepoint"; // NOI18N
270
            markSavepointAndUnmodified();
271
        }
272
        lastAddedOrRedoneEdit = edit;
273
    }
274
    
275
    void checkReplaceSavepointEdit(WrapUndoEdit origEdit, WrapUndoEdit newEdit) {
276
        if (savepointEdit == origEdit) {
277
            checkLogOp("checkReplaceSavepointEdit-replacedSavepointEdit", origEdit); // NOI18N
278
            savepointEdit = newEdit;
279
        }
280
    }
281
    
282
    void notifyWrapEditDie(UndoableEdit edit) {
283
        if (edit == savepointEdit) { // Savepoint neighbour died => no longer a savepoint
284
            checkLogOp("notifyWrapEditDie-savepoint-die", edit); // NOI18N
285
            savepointEdit = null;
286
            clearSaveActionsEdit();
287
        }
288
    }
289
    
290
    @Override
291
    public synchronized boolean addEdit(UndoableEdit edit) {
292
        // This should already be called under document's lock so DocLockedRun not necessary
293
        assert (edit != null) : "Cannot add null edit"; // NOI18N
294
        if (performingSaveActions) {
295
            checkLogOp("addEdit-performingSaveActions", edit); // NOI18N
296
            boolean added = saveActionsEdit.addEdit(edit);
297
            if (!added) {
298
                throw new IllegalStateException("Cannot add to saveActionsEdit"); // NOI18N
299
            }
300
            return added;
301
        }
302
        WrapUndoEdit wrapEdit = new WrapUndoEdit(this, edit); // Wrap the edit
303
        boolean added = super.addEdit(wrapEdit);
304
        if (isAtSavepoint()) {
305
            checkLogOp("addEdit-atSavepoint", wrapEdit); // NOI18N
306
            beforeSavepoint = false;
307
            savepointEdit = wrapEdit;
308
        } else {
309
            checkLogOp("addEdit", wrapEdit); // NOI18N
310
        }
311
        lastAddedOrRedoneEdit = wrapEdit;
312
        return added;
313
    }
314
    
315
    // replaceEdit() not overriden - it should return false
316
317
    @Override
318
    public void redo() throws javax.swing.undo.CannotRedoException {
319
        final StyledDocument doc = support.getDocument();
320
        if (doc == null) {
321
            throw new javax.swing.undo.CannotRedoException(); // NOI18N
322
        }
323
        new DocLockedRun(0, doc);
324
        checkCallNotifyUnmodified();
325
    }
326
327
    @Override
328
    public void undo() throws javax.swing.undo.CannotUndoException {
329
        final StyledDocument doc = support.getDocument();
330
        if (doc == null) {
331
            throw new javax.swing.undo.CannotUndoException(); // NOI18N
332
        }
333
        new DocLockedRun(1, doc);
334
        checkCallNotifyUnmodified();
335
    }
336
337
    @Override
338
    public boolean canRedo() {
339
        return new DocLockedRun(2, support.getDocument(), 0, true).booleanResult;
340
    }
341
342
    @Override
343
    public boolean canUndo() {
344
        return new DocLockedRun(3, support.getDocument(), 0, true).booleanResult;
345
    }
346
347
    @Override
348
    public int getLimit() {
349
        return new DocLockedRun(4, support.getDocument()).intResult;
350
    }
351
352
    @Override
353
    public void discardAllEdits() {
354
        new DocLockedRun(5, support.getDocument());
355
    }
356
    
357
    private void clearSaveActionsEdit() {
358
        if (saveActionsEdit != null) {
359
            checkLogOp("    saveActionsEdit-die", saveActionsEdit); // NOI18N
360
            saveActionsEdit.die();
361
            saveActionsEdit = null;
362
        }
363
    }
364
365
    @Override
366
    public void setLimit(int l) {
367
        new DocLockedRun(6, support.getDocument(), l);
368
    }
369
370
    @Override
371
    public boolean canUndoOrRedo() {
372
        return new DocLockedRun(7, support.getDocument(), 0, true).booleanResult;
373
    }
374
375
    @Override
376
    public java.lang.String getUndoOrRedoPresentationName() {
377
        if (support.isDocumentReady()) {
378
            return new DocLockedRun(8, support.getDocument(), 0, true).stringResult;
379
        } else {
380
            return "";
381
        }
382
    }
383
384
    @Override
385
    public java.lang.String getRedoPresentationName() {
386
        if (support.isDocumentReady()) {
387
            return new DocLockedRun(9, support.getDocument(), 0, true).stringResult;
388
        } else {
389
            return "";
390
        }
391
    }
392
393
    @Override
394
    public java.lang.String getUndoPresentationName() {
395
        if (support.isDocumentReady()) {
396
            return new DocLockedRun(10, support.getDocument(), 0, true).stringResult;
397
        } else {
398
            return "";
399
        }
400
    }
401
402
    @Override
403
    public void undoOrRedo() throws javax.swing.undo.CannotUndoException, javax.swing.undo.CannotRedoException {
404
        super.undoOrRedo();
405
    }
406
    
407
    static String editToString(UndoableEdit edit) {
408
        if (edit instanceof WrapUndoEdit) {
409
            return toStringTerse(edit) + "->" + toStringTerse(((WrapUndoEdit)edit).delegate()); // NOI18N
410
        } else {
411
            return toStringTerse(edit);
412
        }
413
    }
414
    
415
    static String toStringTerse(Object o) {
416
        if (o != null) {
417
            String clsName = o.getClass().getName();
418
            return clsName.substring(clsName.lastIndexOf('.') + 1) + "@" + System.identityHashCode(o); // NOI18N
419
        } else {
420
            return "null"; // NOI18N
421
        }
422
    }
423
    
424
    static void checkLogOp(String op, UndoableEdit edit) {
425
        if (LOG.isLoggable(Level.FINE)) {
426
            LOG.fine(op + ": " + editToString(edit) + '\n');
427
        }
428
    }
429
430
    private final class DocLockedRun implements Runnable {
431
        
432
        private final int type;
433
        
434
        boolean booleanResult;
435
        
436
        int intResult;
437
        
438
        String stringResult;
439
440
        public DocLockedRun(int type, StyledDocument doc) {
441
            this(type, doc, 0);
442
        }
443
444
        public DocLockedRun(int type, StyledDocument doc, int intValue) {
445
            this(type, doc, intValue, false);
446
        }
447
448
        public DocLockedRun(int type, StyledDocument doc, int intValue, boolean readLock) {
449
            this.type = type;
450
            this.intResult = intValue;
451
452
            if (!readLock && (doc instanceof NbDocument.WriteLockable)) {
453
                ((NbDocument.WriteLockable) doc).runAtomic(this);
454
            } else {
455
                if (readLock && doc != null) {
456
                    doc.render(this);
457
                } else {
458
                    // if the document is not one of "NetBeans ready"
459
                    // that supports locking we do not have many
460
                    // chances to do something. Maybe check for AbstractDocument
461
                    // and call writeLock using reflection, but better than
462
                    // that, let's leave this simple for now and wait for
463
                    // bug reports (if any appear)
464
                    run();
465
                }
466
            }
467
        }
468
469
        public void run() {
470
            switch (type) {
471
            case 0:
472
                UndoRedoManager.super.redo();
473
                break;
474
475
            case 1:
476
                UndoRedoManager.super.undo();
477
                break;
478
479
            case 2:
480
                booleanResult = UndoRedoManager.super.canRedo();
481
                break;
482
483
            case 3:
484
                booleanResult = UndoRedoManager.super.canUndo();
485
                break;
486
487
            case 4:
488
                intResult = UndoRedoManager.super.getLimit();
489
                break;
490
491
            case 5:
492
                UndoRedoManager.super.discardAllEdits();
493
                clearSaveActionsEdit();
494
                lastAddedOrRedoneEdit = null;
495
                break;
496
497
            case 6:
498
                UndoRedoManager.super.setLimit(intResult);
499
                break;
500
501
            case 7:
502
                UndoRedoManager.super.canUndoOrRedo();
503
                break;
504
505
            case 8:
506
                stringResult = UndoRedoManager.super.getUndoOrRedoPresentationName();
507
                break;
508
509
            case 9:
510
                stringResult = UndoRedoManager.super.getRedoPresentationName();
511
                break;
512
513
            case 10:
514
                stringResult = UndoRedoManager.super.getUndoPresentationName();
515
                break;
516
517
            case 11:
518
                UndoRedoManager.super.undoOrRedo();
519
                break;
520
521
            default:
522
                throw new IllegalArgumentException("Unknown type: " + type);
523
            }
524
        }
525
    }
526
527
}
(-)a/openide.text/src/org/openide/text/WrapUndoEdit.java (+171 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common Development and
11
 * Distribution License("CDDL") (collectively, the "License"). You may not use
12
 * this file except in compliance with the License. You can obtain a copy of
13
 * the License at http://www.netbeans.org/cddl-gplv2.html or
14
 * nbbuild/licenses/CDDL-GPL-2-CP. See the License for the specific language
15
 * governing permissions and limitations under the License. When distributing
16
 * the software, include this License Header Notice in each file and include
17
 * the License file at nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
18
 * particular file as subject to the "Classpath" exception as provided by
19
 * Oracle in the GPL Version 2 section of the License file that accompanied
20
 * this code. If applicable, add the following below the License Header, with
21
 * the fields enclosed by brackets [] replaced by your own identifying
22
 * information: "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * If you wish your version of this file to be governed by only the CDDL or
25
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
26
 * elects to include this software in this distribution under the [CDDL or GPL
27
 * Version 2] license." If you do not indicate a single choice of license, a
28
 * recipient has the option to distribute your version of this file under
29
 * either the CDDL, the GPL Version 2 or to extend the choice of license to its
30
 * licensees as provided above. However, if you add GPL Version 2 code and
31
 * therefore, elected the GPL Version 2 license, then the option applies only
32
 * if the new code is made subject to such option by the copyright holder.
33
 *
34
 * Contributor(s):
35
 *
36
 * Portions Copyrighted 2011 Sun Microsystems, Inc.
37
 */
38
package org.openide.text;
39
40
import javax.swing.undo.CannotRedoException;
41
import javax.swing.undo.CannotUndoException;
42
import javax.swing.undo.UndoableEdit;
43
44
/**
45
 * Undoable edit that wraps each undoable edit added to CloneableEditorSupport's undo manager.
46
 * <br/>
47
 * It is needed to
48
 * <ul>
49
 *   <li>Prohibit addEdit() operation on last edit when the undo manager is at savepoint.</li>
50
 *   <li>Prohibit replaceEdit() operation on last edit when the undo manager is at savepoint.</li>
51
 *   <li>Identify non-significant edits that are otherwise not reported by editToBeUndone()
52
 *       and editToBeRedone().</li>
53
 * </ul>
54
 * 
55
 * @author Miloslav Metelka
56
 * @since
57
 */
58
final class WrapUndoEdit implements UndoableEdit {
59
60
    /**
61
     * Support for which this edit is created.
62
     */
63
    final UndoRedoManager undoRedoManager; // 8=Object + 4 = 12 bytes
64
65
    /**
66
     * Real undoable edit passed to undoableEditHappened().
67
     */
68
    private UndoableEdit delegate; // 12 + 4 = 16 bytes
69
70
    WrapUndoEdit(UndoRedoManager undoRedoManager, UndoableEdit delegate) {
71
        assert (delegate != null) : "Delegate is null"; // NOI18N
72
        this.undoRedoManager = undoRedoManager;
73
        this.delegate = delegate;
74
    }
75
    
76
    UndoableEdit delegate() {
77
        return delegate;
78
    }
79
80
    void setDelegate(UndoableEdit delegate) {
81
        this.delegate = delegate;
82
    }
83
    
84
    @Override
85
    public void undo() throws CannotUndoException {
86
        UndoRedoManager.checkLogOp("WrapUndoEdit.undo", this);
87
        undoRedoManager.beforeUndoCheck(this);
88
        delegate.undo();
89
        // This will only happen if delegate.undo() does not throw CannotUndoException
90
        undoRedoManager.afterUndoCheck(this);
91
    }
92
93
    @Override
94
    public boolean canUndo() {
95
        return delegate.canUndo();
96
    }
97
98
    @Override
99
    public void redo() throws CannotRedoException {
100
        UndoRedoManager.checkLogOp("WrapUndoEdit.redo", this);
101
        undoRedoManager.beforeRedoCheck(this);
102
        delegate.redo();
103
        // This will only happen if delegate.redo() does not throw CannotRedoException
104
        undoRedoManager.afterRedoCheck(this);
105
    }
106
107
    @Override
108
    public boolean canRedo() {
109
        return delegate.canRedo();
110
    }
111
112
    @Override
113
    public void die() {
114
        UndoRedoManager.checkLogOp("WrapUndoEdit.die", this);
115
        delegate.die();
116
        undoRedoManager.notifyWrapEditDie(this);
117
    }
118
119
    @Override
120
    public boolean addEdit(UndoableEdit anEdit) {
121
        if (undoRedoManager.isAtSavepoint()) {
122
            // Prohibit adding to existing edit at savepoint since undo to savepoint could no longer be done
123
            // when merging with another edit.
124
            // Since addEdit() will fail the anEdit.replaceEdit() will be attempted; but it will not succeed too
125
            // (see impl) so anEdit will be added as a fresh edit to end of UM.edits.
126
            // However undoRedoManager.savepointEdit must be set by undoRedoManager.addEdit() since
127
            // UM.edits may be empty in which case WUE.addEdit() is not called.
128
            return false;
129
        }
130
        // anEdit is already wrapped for possible regular addition
131
        WrapUndoEdit wrapEdit = (WrapUndoEdit) anEdit;
132
        boolean added = delegate.addEdit(wrapEdit.delegate);
133
        return added;
134
    }
135
136
    @Override
137
    public boolean replaceEdit(UndoableEdit anEdit) {
138
        if (undoRedoManager.isAtSavepoint()) { // Prohibit replacing at savepoint
139
            return false;
140
        }
141
        WrapUndoEdit wrapEdit = (WrapUndoEdit) anEdit;
142
        boolean replaced = delegate.replaceEdit(wrapEdit.delegate);
143
        UndoRedoManager.checkLogOp("WrapUndoEdit.replaceEdit=" + replaced, anEdit);
144
        if (replaced) {
145
            undoRedoManager.checkReplaceSavepointEdit(wrapEdit, this);
146
        }
147
        return replaced;
148
    }
149
150
    @Override
151
    public boolean isSignificant() {
152
        return delegate.isSignificant();
153
    }
154
155
    @Override
156
    public String getPresentationName() {
157
        return delegate.getPresentationName();
158
    }
159
160
    @Override
161
    public String getUndoPresentationName() {
162
        return delegate.getUndoPresentationName();
163
    }
164
165
    @Override
166
    public String getRedoPresentationName() {
167
        return delegate.getRedoPresentationName();
168
    }
169
170
}
171
(-)a/openide.text/test/unit/src/org/openide/text/CloneableEditorSupportRedirectorTest.java (-6 / +7 lines)
Lines 38-43 Link Here
38
import java.lang.ref.WeakReference;
38
import java.lang.ref.WeakReference;
39
import java.util.Arrays;
39
import java.util.Arrays;
40
import javax.swing.SwingUtilities;
40
import javax.swing.SwingUtilities;
41
import javax.swing.text.BadLocationException;
41
import javax.swing.text.Document;
42
import javax.swing.text.Document;
42
import javax.swing.text.EditorKit;
43
import javax.swing.text.EditorKit;
43
import org.netbeans.junit.MockServices;
44
import org.netbeans.junit.MockServices;
Lines 157-168 Link Here
157
        assertFalse ("Nothing to undo", red.master.getUndoRedo ().canUndo ());
158
        assertFalse ("Nothing to undo", red.master.getUndoRedo ().canUndo ());
158
        
159
        
159
        // this should not be allowed
160
        // this should not be allowed
160
        doc.insertString (0, "Kuk", null);
161
        try {
161
        
162
            doc.insertString (0, "Kuk", null);
162
        String modifiedForAWhile = doc.getText (0, 3);
163
            fail("Modification should not proceed");
163
        //assertEquals ("For a while the test really starts with Kuk", "Kuk", doc.getText (0, 3));
164
        } catch (BadLocationException ex) {
164
        
165
            // Expected
165
        assertFalse ("The document cannot be modified", red.master.getUndoRedo ().canUndo ());
166
        }
166
        
167
        
167
        String s = doc.getText (0, doc.getLength ());
168
        String s = doc.getText (0, doc.getLength ());
168
        assertEquals ("The document is now the same as at the begining", content, s);
169
        assertEquals ("The document is now the same as at the begining", content, s);
(-)a/openide.text/test/unit/src/org/openide/text/CloneableEditorSupportTest.java (-6 / +7 lines)
Lines 56-61 Link Here
56
import java.util.Date;
56
import java.util.Date;
57
import javax.swing.JEditorPane;
57
import javax.swing.JEditorPane;
58
import javax.swing.SwingUtilities;
58
import javax.swing.SwingUtilities;
59
import javax.swing.text.BadLocationException;
59
import javax.swing.text.Document;
60
import javax.swing.text.Document;
60
import javax.swing.text.EditorKit;
61
import javax.swing.text.EditorKit;
61
import junit.framework.AssertionFailedError;
62
import junit.framework.AssertionFailedError;
Lines 265-276 Link Here
265
        assertFalse ("Nothing to undo", support.getUndoRedo ().canUndo ());
266
        assertFalse ("Nothing to undo", support.getUndoRedo ().canUndo ());
266
        
267
        
267
        // this should not be allowed
268
        // this should not be allowed
268
        doc.insertString (0, "Kuk", null);
269
        try {
269
        
270
            doc.insertString (0, "Kuk", null);
270
        String modifiedForAWhile = doc.getText (0, 3);
271
            fail("Modification should not proceed");
271
        //assertEquals ("For a while the test really starts with Kuk", "Kuk", doc.getText (0, 3));
272
        } catch (BadLocationException ex) {
272
        
273
            // Expected
273
        assertFalse ("The document cannot be modified", support.getUndoRedo ().canUndo ());
274
        }
274
        
275
        
275
        String s = doc.getText (0, doc.getLength ());
276
        String s = doc.getText (0, doc.getLength ());
276
        assertEquals ("The document is now the same as at the begining", content, s);
277
        assertEquals ("The document is now the same as at the begining", content, s);

Return to bug 21237