001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.editor;
043:
044: import java.util.Hashtable;
045: import java.util.Enumeration;
046: import java.util.List;
047: import javax.swing.text.AbstractDocument;
048: import javax.swing.text.BadLocationException;
049: import javax.swing.text.Element;
050: import javax.swing.event.DocumentEvent;
051: import javax.swing.text.AttributeSet;
052: import javax.swing.undo.UndoableEdit;
053: import javax.swing.undo.AbstractUndoableEdit;
054: import javax.swing.undo.CannotUndoException;
055: import javax.swing.undo.CannotRedoException;
056:
057: /**
058: * Document implementation
059: *
060: * @author Miloslav Metelka
061: * @version 1.00
062: */
063:
064: public class BaseDocumentEvent extends
065: AbstractDocument.DefaultDocumentEvent {
066:
067: private static final boolean debugUndo = Boolean
068: .getBoolean("netbeans.debug.editor.document.undo");
069:
070: private DocumentContent.Edit modifyUndoEdit;
071:
072: private FixLineSyntaxState fixLineSyntaxState;
073:
074: /** Previous event in the chain of the events that were
075: * connected together to be undone/redone at once.
076: */
077: private UndoableEdit previous;
078:
079: private boolean inUndo;
080:
081: private boolean inRedo;
082:
083: /** Unfortunately the undo() and redo() methods
084: * call writeLock() which the protected final method
085: * in AbstractDocument. If somebody calls runAtomic()
086: * or runAtomicAsUser() and the exception is thrown
087: * in the body of the executed runnables, the document
088: * automatically undoes the changes. Unfortunately
089: * the undo() in AbstractDocument has the writeLock()
090: * call hardcoded which throws IllegalStateException()
091: * in this situation.
092: * Therefore the BaseDocumentEvent cannot call
093: * the super.undo() and has to reimplement the functionality
094: * of all the parents. The extWriteLock() and extWriteUnlock()
095: * are used instead.
096: */
097:
098: private boolean hasBeenDone2;
099:
100: private boolean alive2;
101:
102: private boolean inProgress2;
103:
104: private Hashtable changeLookup2;
105:
106: private int lfCount = -1;
107:
108: private AttributeSet attribs = null;
109:
110: static final long serialVersionUID = -7624299835780414963L;
111:
112: /** Construct document event instance.
113: * @param offset position in the document where the insert/remove/change
114: * occured
115: * @param length number of the characters affected by the event
116: * @param type type of the event - INSERT/REMOVE/CHANGE
117: */
118: public BaseDocumentEvent(BaseDocument doc, int offset, int length,
119: DocumentEvent.EventType type) {
120: ((AbstractDocument) doc).super (offset, length, type);
121:
122: hasBeenDone2 = true;
123: alive2 = true;
124: inProgress2 = true;
125: }
126:
127: /* package */void attachChangeAttribs(AttributeSet attribs) {
128: this .attribs = attribs;
129: }
130:
131: /**
132: * Gets the attributes associated with the change that caused this event.
133: * If no attributes were associated with the document change, this method
134: * may return <code>null</code>;
135: *
136: * @return The <code>AttributeSet</code> associated with the document
137: * change or <code>null</code>.
138: * @since 1.17
139: */
140: public final AttributeSet getChangeAttributes() {
141: return attribs;
142: }
143:
144: protected UndoableEdit findEdit(Class editClass) {
145: for (int i = edits.size() - 1; i >= 0; i--) {
146: Object edit = edits.get(i);
147: if (editClass.isInstance(edit)) {
148: return (UndoableEdit) edit;
149: }
150: }
151: return null;
152: }
153:
154: private DocumentContent.Edit getModifyUndoEdit() {
155: if (getType() == DocumentEvent.EventType.CHANGE) {
156: throw new IllegalStateException(
157: "Cannot be called for CHANGE events."); // NOI18N
158: }
159:
160: if (modifyUndoEdit == null) {
161: modifyUndoEdit = (DocumentContent.Edit) findEdit(DocumentContent.Edit.class);
162: }
163: return modifyUndoEdit;
164: }
165:
166: private FixLineSyntaxState getFixLineSyntaxState() {
167: if (getType() == DocumentEvent.EventType.CHANGE) {
168: throw new IllegalStateException(
169: "Cannot be called for CHANGE events."); // NOI18N
170: }
171:
172: if (fixLineSyntaxState == null) {
173: fixLineSyntaxState = ((FixLineSyntaxState.BeforeLineUndo) findEdit(FixLineSyntaxState.BeforeLineUndo.class))
174: .getMaster();
175: }
176: return fixLineSyntaxState;
177: }
178:
179: /** Gets the characters that were inserted/removed or null
180: * for change event.
181: * Characters must be used only in readonly mode as the
182: * character array is shared by all listeners and also by
183: * modification event itself.
184: * @deprecated
185: */
186: public char[] getChars() {
187: String text = getText();
188: return (text != null) ? text.toCharArray() : null;
189: }
190:
191: /** Get the text that was inserted/removed or null
192: * for change event.
193: */
194: public String getText() {
195: return (getModifyUndoEdit() != null) ? getModifyUndoEdit()
196: .getUndoRedoText() : null;
197: }
198:
199: /**
200: * Get the line at which the insert/remove occured.
201: * @deprecated
202: */
203: public int getLine() {
204: Element lineRoot = ((BaseDocument) getDocument())
205: .getParagraphElement(0).getParentElement();
206: int lineIndex = lineRoot.getElementIndex(getOffset());
207: return lineIndex;
208: // return (getModifyUndoEdit() != null) ? getModifyUndoEdit().getLine() : 0;
209: }
210:
211: /** Get the count of '\n' (line-feeds) contained in the inserted/removed text. */
212: public int getLFCount() {
213: if (getType() == DocumentEvent.EventType.CHANGE) {
214: throw new IllegalStateException(
215: "Not available for CHANGE events"); // NOI18N
216: }
217:
218: if (lfCount == -1) {
219: String text = getText();
220: int lfCnt = 0;
221: for (int i = text.length() - 1; i >= 0; i--) {
222: if (text.charAt(i) == '\n') {
223: lfCnt++;
224: }
225: }
226: lfCount = lfCnt;
227: }
228:
229: return lfCount;
230: }
231:
232: /** Get the offset at which the updating of the syntax stopped so there
233: * are no more changes in the tokens after this point.
234: */
235: public int getSyntaxUpdateOffset() {
236: if (getType() == DocumentEvent.EventType.CHANGE) {
237: throw new IllegalStateException(
238: "Not available for CHANGE events"); // NOI18N
239: }
240:
241: return getFixLineSyntaxState().getSyntaxUpdateOffset();
242: }
243:
244: List getSyntaxUpdateTokenList() {
245: return getFixLineSyntaxState().getSyntaxUpdateTokenList();
246: }
247:
248: public String getDrawLayerName() {
249: if (getType() != DocumentEvent.EventType.CHANGE) {
250: throw new IllegalStateException(
251: "Can be called for CHANGE events only."); // NOI18N
252: }
253:
254: DrawLayerChange dlc = (DrawLayerChange) findEdit(DrawLayerChange.class);
255:
256: return (dlc != null) ? dlc.getDrawLayerName() : null;
257: }
258:
259: public int getDrawLayerVisibility() {
260: if (getType() != DocumentEvent.EventType.CHANGE) {
261: throw new IllegalStateException(
262: "Can be called for CHANGE events only."); // NOI18N
263: }
264:
265: DrawLayerChange dlc = (DrawLayerChange) findEdit(DrawLayerChange.class);
266:
267: return (dlc != null) ? dlc.getDrawLayerVisibility() : -1;
268: }
269:
270: /** Whether this event is being fired because it's being undone. */
271: public boolean isInUndo() {
272: return inUndo;
273: }
274:
275: /** Whether this event is being fired because it's being redone. */
276: public boolean isInRedo() {
277: return inRedo;
278: }
279:
280: public void undo() throws CannotUndoException {
281: BaseDocument doc = (BaseDocument) getDocument();
282: inUndo = true;
283:
284: boolean notifyMod;
285: try {
286: notifyMod = doc.notifyModifyCheckStart(0, "undo() vetoed"); // NOI18N
287: } catch (BadLocationException ex) {
288: throw new CannotUndoException();
289: }
290: boolean modFinished = false;
291:
292: // Super of undo()
293: doc.extWriteLock(); // call this extWriteLock() instead of writeLock()
294: try {
295: if (!canUndo()) {
296: throw new CannotUndoException();
297: }
298: hasBeenDone2 = false;
299:
300: doc.lastModifyUndoEdit = null; // #8692 check last modify undo edit
301:
302: if (debugUndo) {
303: /*DEBUG*/System.err.println("UNDO in doc=" + doc);
304: }
305:
306: int i = edits.size();
307: if (i > 0) {
308: doc.markModsUndoneOrRedone();
309: }
310: while (i-- > 0) {
311: UndoableEdit e = (UndoableEdit) edits.elementAt(i);
312: e.undo();
313: }
314:
315: // fire a DocumentEvent to notify the view(s)
316: if (getType() == DocumentEvent.EventType.REMOVE) {
317: doc.fireInsertUpdate(this );
318: } else if (getType() == DocumentEvent.EventType.INSERT) {
319: doc.fireRemoveUpdate(this );
320: } else {
321: doc.fireChangedUpdate(this );
322: }
323: } finally {
324: doc.extWriteUnlock(); // call this extWriteUnlock() instead of writeUnlock()
325: if (notifyMod) {
326: doc.notifyModifyCheckEnd(modFinished);
327: }
328: }
329: // End super of undo()
330:
331: if (previous != null) {
332: previous.undo();
333: }
334:
335: inUndo = false;
336: }
337:
338: public void redo() throws CannotRedoException {
339: BaseDocument doc = (BaseDocument) getDocument();
340: boolean notifyMod;
341: try {
342: notifyMod = doc.notifyModifyCheckStart(0, "redo() vetoed"); // NOI18N
343: } catch (BadLocationException ex) {
344: throw new CannotRedoException();
345: }
346:
347: inRedo = true;
348: if (previous != null) {
349: previous.redo();
350: }
351:
352: boolean modFinished = false; // Whether modification succeeded
353:
354: // Super of redo()
355: doc.extWriteLock(); // call this extWriteLock() instead of writeLock()
356: try {
357:
358: if (!canRedo()) {
359: throw new CannotRedoException();
360: }
361: hasBeenDone2 = true;
362:
363: if (debugUndo) {
364: /*DEBUG*/System.err.println("REDO in doc=" + doc);
365: }
366:
367: Enumeration cursor = edits.elements();
368: if (cursor.hasMoreElements()) {
369: doc.markModsUndoneOrRedone();
370: }
371: while (cursor.hasMoreElements()) {
372: ((UndoableEdit) cursor.nextElement()).redo();
373: }
374:
375: // fire a DocumentEvent to notify the view(s)
376: if (getType() == DocumentEvent.EventType.INSERT) {
377: doc.fireInsertUpdate(this );
378: } else if (getType() == DocumentEvent.EventType.REMOVE) {
379: doc.fireRemoveUpdate(this );
380: } else {
381: doc.fireChangedUpdate(this );
382: }
383: } finally {
384: doc.extWriteUnlock(); // call this extWriteUnlock() instead of writeUnlock()
385: if (notifyMod) {
386: doc.notifyModifyCheckEnd(modFinished);
387: }
388: }
389: // End super of redo()
390:
391: inRedo = false;
392: }
393:
394: public boolean addEdit(UndoableEdit anEdit) {
395: // Super of addEdit()
396:
397: // if the number of changes gets too great, start using
398: // a hashtable for to locate the change for a given element.
399: if ((changeLookup2 == null) && (edits.size() > 10)) {
400: changeLookup2 = new Hashtable();
401: int n = edits.size();
402: for (int i = 0; i < n; i++) {
403: Object o = edits.elementAt(i);
404: if (o instanceof DocumentEvent.ElementChange) {
405: DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) o;
406: changeLookup2.put(ec.getElement(), ec);
407: }
408: }
409: }
410:
411: // if we have a hashtable... add the entry if it's
412: // an ElementChange.
413: if ((changeLookup2 != null)
414: && (anEdit instanceof DocumentEvent.ElementChange)) {
415: DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) anEdit;
416: changeLookup2.put(ec.getElement(), ec);
417: }
418:
419: if (!inProgress2) {
420: return false;
421:
422: } else {
423: UndoableEdit last = lastEdit();
424:
425: // If this is the first subedit received, just add it.
426: // Otherwise, give the last one a chance to absorb the new
427: // one. If it won't, give the new one a chance to absorb
428: // the last one.
429:
430: if (last == null) {
431: edits.addElement(anEdit);
432: } else if (!last.addEdit(anEdit)) {
433: if (anEdit.replaceEdit(last)) {
434: edits.removeElementAt(edits.size() - 1);
435: }
436: edits.addElement(anEdit);
437: }
438:
439: return true;
440: }
441: // End super of addEdit()
442: }
443:
444: private boolean isLastModifyUndoEdit() {
445: if (true)
446: return true; // #83740 - make this method always return true
447: if (getType() == DocumentEvent.EventType.CHANGE) {
448: return true; // OK in this case
449: }
450:
451: BaseDocument doc = (BaseDocument) getDocument();
452: doc.extWriteLock(); // lock to sync if ongoing doc change
453: try {
454: // #8692 check last modify undo edit
455: if (doc.lastModifyUndoEdit == null) {
456: return true; // OK in this case
457: }
458:
459: DocumentContent.Edit undoEdit = getModifyUndoEdit();
460: return (undoEdit == doc.lastModifyUndoEdit);
461: } finally {
462: doc.extWriteUnlock();
463: }
464: }
465:
466: public boolean canUndo() {
467: // Super of canUndo
468: return !inProgress2 && alive2 && hasBeenDone2
469: // End super of canUndo
470: && isLastModifyUndoEdit();
471: }
472:
473: /**
474: * Returns false if isInProgress or if super does.
475: *
476: * @see #isInProgress
477: */
478: public boolean canRedo() {
479: // Super of canRedo
480: return !inProgress2 && alive2 && !hasBeenDone2;
481: // End super of canRedo
482: }
483:
484: public boolean isInProgress() {
485: // Super of isInProgress()
486: return inProgress2;
487: // End super of isInProgress()
488: }
489:
490: public String getUndoPresentationName() {
491: return "";
492: }
493:
494: public String getRedoPresentationName() {
495: return "";
496: }
497:
498: /** Returns true if this event can be merged by the previous
499: * one (given as parameter) in the undo-manager queue.
500: */
501: public boolean canMerge(BaseDocumentEvent evt) {
502: if (getType() == DocumentEvent.EventType.INSERT) { // last was insert
503: if (evt.getType() == DocumentEvent.EventType.INSERT) { // adding insert to insert
504: String text = getText();
505: String evtText = evt.getText();
506: if ((getLength() == 1 || (getLength() > 1 && Analyzer
507: .isSpace(text)))
508: && (evt.getLength() == 1 || (evt.getLength() > 1 && Analyzer
509: .isSpace(evtText)))
510: && (evt.getOffset() + evt.getLength() == getOffset()) // this follows the previous
511: ) {
512: BaseDocument doc = (BaseDocument) getDocument();
513: boolean this Word = doc.isIdentifierPart(text
514: .charAt(0));
515: boolean lastWord = doc.isIdentifierPart(evtText
516: .charAt(0));
517: if (this Word && lastWord) { // add word char to word char(s)
518: return true;
519: }
520: boolean this White = doc
521: .isWhitespace(text.charAt(0));
522: boolean lastWhite = doc.isWhitespace(evtText
523: .charAt(0));
524: if ((lastWhite && this White)
525: || (!lastWhite && !lastWord && !this White && !this Word)) {
526: return true;
527: }
528: }
529: } else { // adding remove to insert
530: }
531: } else { // last was remove
532: if (evt.getType() == DocumentEvent.EventType.INSERT) { // adding insert to remove
533: } else { // adding remove to remove
534: }
535: }
536: return false;
537: }
538:
539: /** Try to determine whether this event can replace the old one.
540: * This is used to batch the one-letter modifications into larger
541: * parts (words) and undoing/redoing them at once.
542: * This method returns true whether
543: */
544: public boolean replaceEdit(UndoableEdit anEdit) {
545: BaseDocument doc = (BaseDocument) getDocument();
546: if (anEdit instanceof BaseDocument.AtomicCompoundEdit) {
547: BaseDocument.AtomicCompoundEdit compEdit = (BaseDocument.AtomicCompoundEdit) anEdit;
548:
549: if (!doc.undoMergeReset && compEdit.getEdits().size() == 1) {
550: UndoableEdit edit = (UndoableEdit) compEdit.getEdits()
551: .get(0);
552: if (edit instanceof BaseDocumentEvent
553: && canMerge((BaseDocumentEvent) edit)) {
554: previous = anEdit;
555: return true;
556: }
557: }
558: } else if (anEdit instanceof BaseDocumentEvent) {
559: BaseDocumentEvent evt = (BaseDocumentEvent) anEdit;
560:
561: if (!doc.undoMergeReset && canMerge(evt)) {
562: previous = anEdit;
563: return true;
564: }
565: }
566: doc.undoMergeReset = false;
567: return false;
568: }
569:
570: public void die() {
571: // Super of die()
572: int size = edits.size();
573: for (int i = size - 1; i >= 0; i--) {
574: UndoableEdit e = (UndoableEdit) edits.elementAt(i);
575: e.die();
576: }
577:
578: alive2 = false;
579: // End super of die()
580:
581: if (previous != null) {
582: previous.die();
583: previous = null;
584: }
585: }
586:
587: public void end() {
588: // Super of end()
589: inProgress2 = false;
590: // End super of end()
591: }
592:
593: public DocumentEvent.ElementChange getChange(Element elem) {
594: // Super of getChange()
595: if (changeLookup2 != null) {
596: return (DocumentEvent.ElementChange) changeLookup2
597: .get(elem);
598: }
599: int n = edits.size();
600: for (int i = 0; i < n; i++) {
601: Object o = edits.elementAt(i);
602: if (o instanceof DocumentEvent.ElementChange) {
603: DocumentEvent.ElementChange c = (DocumentEvent.ElementChange) o;
604: if (c.getElement() == elem) {
605: return c;
606: }
607: }
608: }
609: return null;
610: // End super of getChange()
611: }
612:
613: public String toString() {
614: return System.identityHashCode(this ) + " "
615: + super .toString() // NOI18N
616: + ", type="
617: + getType() // NOI18N
618: + ((getType() != DocumentEvent.EventType.CHANGE) ? ("text='"
619: + getText() + "'")
620: : ""); // NOI18N
621: }
622:
623: /** Edit describing the change of the document draw-layers */
624: static class DrawLayerChange extends AbstractUndoableEdit {
625:
626: String drawLayerName;
627:
628: int drawLayerVisibility;
629:
630: DrawLayerChange(String drawLayerName, int drawLayerVisibility) {
631: this .drawLayerName = drawLayerName;
632: this .drawLayerVisibility = drawLayerVisibility;
633: }
634:
635: public String getDrawLayerName() {
636: return drawLayerName;
637: }
638:
639: public int getDrawLayerVisibility() {
640: return drawLayerVisibility;
641: }
642:
643: }
644:
645: }
|