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: package org.netbeans.modules.vmd.api.model;
042:
043: import org.openide.ErrorManager;
044: import org.openide.util.Mutex;
045:
046: import javax.swing.undo.*;
047: import java.util.Collection;
048:
049: /**
050: * This class manages a transactional access to a document. If you want execute a code which reads or writes to
051: * a document or its components than you have to use this manager and call readAccess or writeAccess. A parameter is
052: * a Runnable which will be executed with read/write access.
053: * <p>
054: * It uses the NetBeans MUTEX class. Therefore it allows multiple reads and just a single write access at the same time.
055: * <p>
056: * When a read or a write access is granted, it automatically waits for a read access on descriptor registry.
057: *
058: * @author David Kaspar
059: */
060: public final class TransactionManager {
061:
062: private final DesignDocument document;
063: private final DescriptorRegistry descriptorRegistry;
064: private final ListenerManager listenerManager;
065: private final Mutex mutex = new Mutex();
066: private boolean notRootLevelWriteAccess = false;
067: private boolean assertEventAllowed = false;
068: private boolean rollback = false;
069: private boolean useUndoManager = false;
070: private boolean discardAllEdits = false;
071: private TransactionEdit transactionEdit;
072:
073: TransactionManager(DesignDocument document,
074: DescriptorRegistry descriptorRegistry,
075: ListenerManager listenerManager) {
076: assert Debug.isFriend(DesignDocument.class, "<init>"); // NOI18N
077: this .document = document;
078: this .descriptorRegistry = descriptorRegistry;
079: this .listenerManager = listenerManager;
080: }
081:
082: /**
083: * Executes a Runnable.run method with read access.
084: * @param runnable the runnable
085: */
086: public void readAccess(final Runnable runnable) {
087: descriptorRegistry.readAccess(new Runnable() {
088: public void run() {
089: mutex.readAccess(runnable);
090: }
091: });
092: }
093:
094: /**
095: * Executes a Runnable.run method with write access.
096: * @param runnable the runnable
097: * @return the event id at the end of the write transaction
098: */
099: public long writeAccess(final Runnable runnable) {
100: final long eventID[] = new long[] { 0 };
101: descriptorRegistry.readAccess(new Runnable() {
102: public void run() {
103: mutex.writeAccess(new Runnable() {
104: public void run() {
105: writeAccessCore(runnable);
106: eventID[0] = listenerManager.getEventID();
107: }
108: });
109: }
110: });
111: return eventID[0];
112: }
113:
114: private void writeAccessCore(Runnable runnable) {
115: boolean rootLevel = !notRootLevelWriteAccess;
116: notRootLevelWriteAccess = true;
117:
118: if (rootLevel)
119: writeAccessRootBegin();
120:
121: try {
122: runnable.run();
123: } finally {
124: if (rootLevel)
125: writeAccessRootEnd();
126: }
127: }
128:
129: private void writeAccessRootBegin() {
130: assertEventAllowed = true;
131: rollback = false;
132: useUndoManager = true;
133: discardAllEdits = false;
134: transactionEdit = null;
135: }
136:
137: private void writeAccessRootEnd() {
138: assertEventAllowed = false;
139: DesignEvent event = null;
140: try {
141: if (rollback)
142: rollbackCore();
143: event = listenerManager.fireEvent();
144: } finally {
145: try {
146: if (useUndoManager) {
147: if (discardAllEdits)
148: document.getDocumentInterface()
149: .discardAllEdits();
150: else if (transactionEdit != null) {
151: transactionEdit.end();
152: document.getDocumentInterface()
153: .undoableEditHappened(transactionEdit);
154: }
155: }
156: } finally {
157: notRootLevelWriteAccess = false;
158: if (event != null && event.isStructureChanged())
159: document.getDocumentInterface().notifyModified();
160: }
161: }
162: }
163:
164: /**
165: * Checks whether the current thread has a read or a write access granted.
166: * @return true if a read or a write access is granted
167: */
168: public boolean isAccess() {
169: return mutex.isReadAccess() || mutex.isWriteAccess();
170: }
171:
172: /**
173: * Checks whether the current thread has a write access granted.
174: * @return true if a write access is granted
175: */
176: public boolean isWriteAccess() {
177: return mutex.isWriteAccess();
178: }
179:
180: /**
181: * Notifies the manager to rollback the transaction at the end of the of write access.
182: * The rollback is not asure to happen when a model was changed the way that cannot be undone.
183: * <p>
184: * Even through the rollback is performed, the created component will be still presented in the document
185: * and the listener manager will fire an event with all changes. If it is rollback-able, then the old and new state in the DesignEvent will be the same.
186: * <p>
187: * Note: Use this method in corner cases only. It is not asured that the rollback happens or happens correctly.
188: * <p>
189: * Note: Supported undoable changes are Component.addComponent, Component.removeComponent, Component.writeProperty and Document.setSelectedComponents only.
190: * <p>
191: * Note: Rejecting operations are: DescriptorRegistry changed, Document.setRootComponent, custom UndoableEdit.
192: */
193: public void rollback() {
194: assert assertEventAllowed;
195: rollback = true;
196: useUndoManager = false;
197: }
198:
199: private void rollbackCore() {
200: if (discardAllEdits) {
201: ErrorManager.getDefault().log(ErrorManager.ERROR,
202: "Cannot rollback operation"); // NOI18N
203: return;
204: }
205: // TODO - implement rollback
206: ErrorManager.getDefault().log(ErrorManager.ERROR,
207: "Rollback is not supported"); // NOI18N
208: }
209:
210: void rootChangeHappened(DesignComponent root) {
211: assert Debug.isFriend(DesignDocument.class, "setRootComponent"); // NOI18N
212: assert assertEventAllowed;
213: listenerManager.addAffectedComponentHierarchy(root);
214: discardAllEdits = true;
215: }
216:
217: void componentDescriptorChangeHappened(DesignComponent component,
218: Collection<? extends Presenter> presentersToRemove,
219: Collection<Presenter> presentersToAdd, boolean useUndo) {
220: assert Debug.isFriend(DesignComponent.class,
221: "setComponentDescriptor"); // NOI18N
222: assert assertEventAllowed;
223: listenerManager.addComponentDescriptorChanged(component,
224: presentersToRemove, presentersToAdd);
225: if (useUndo)
226: discardAllEdits = true;
227: }
228:
229: void parentChangeHappened(DesignComponent previousParent,
230: DesignComponent parent, DesignComponent child) {
231: assert Debug.isFriend(DesignComponent.class, "addComponent")
232: || Debug.isFriend(DesignComponent.class,
233: "removeComponent"); // NOI18N
234: assert assertEventAllowed;
235: listenerManager.addAffectedComponentHierarchy(previousParent);
236: listenerManager.addAffectedComponentHierarchy(parent);
237: listenerManager.addAffectedComponentHierarchy(child);
238: undoableEditHappened(new SetParentEdit(previousParent, parent,
239: child));
240: }
241:
242: void writePropertyHappened(DesignComponent component,
243: String propertyName, PropertyValue oldValue,
244: PropertyValue newValue) {
245: assert Debug.isFriend(DesignComponent.class, "writeProperty"); // NOI18N
246: assert assertEventAllowed;
247: listenerManager.addAffectedDesignComponent(component,
248: propertyName, oldValue);
249: undoableEditHappened(new WritePropertyEdit(component,
250: propertyName, oldValue, newValue));
251: }
252:
253: void selectComponentsHappened(
254: Collection<DesignComponent> oldSelection,
255: Collection<DesignComponent> newSelection) {
256: assert Debug.isFriend(DesignDocument.class,
257: "setSelectedComponents"); // NOI18N
258: assert assertEventAllowed;
259: listenerManager.setSelectionChanged();
260: undoableEditHappened(new SelectionEdit(document, oldSelection,
261: newSelection));
262: }
263:
264: /**
265: * Adds an undoable edit into a undo-redo queue.
266: * <p>
267: * Note: use this to add an additional undoable edit that cannot be produces by the model directly.
268: * <p>
269: * @param edit the edit; for whole edit instance lifecycle, it has to: edit.isSignificant must return false, edit.canUndo and edit.canRedo must return true, edit.undo and edit.redo must not throw any exception.
270: */
271: public void undoableEditHappened(UndoableEdit edit) {
272: assert isWriteAccess();
273: assert !edit.isSignificant();
274: if (transactionEdit == null)
275: transactionEdit = new TransactionEdit();
276: transactionEdit.addEdit(edit);
277: }
278:
279: private class TransactionEdit extends CompoundEdit {
280:
281: @Override
282: public boolean isSignificant() {
283: return true;
284: }
285:
286: @Override
287: public void undo() throws CannotUndoException {
288: final boolean[] error = new boolean[1];
289: writeAccess(new Runnable() {
290: public void run() {
291: useUndoManager = false;
292: try {
293: TransactionEdit.super .undo();
294: } catch (CannotUndoException e) {
295: error[0] = true;
296: ErrorManager.getDefault().notify(
297: ErrorManager.ERROR, e);
298: }
299: }
300: });
301: if (error[0])
302: throw new CannotUndoException();
303: }
304:
305: @Override
306: public void redo() throws CannotRedoException {
307: final boolean[] error = new boolean[1];
308: writeAccess(new Runnable() {
309: public void run() {
310: useUndoManager = false;
311: try {
312: TransactionEdit.super .redo();
313: } catch (CannotRedoException e) {
314: error[0] = true;
315: ErrorManager.getDefault().notify(
316: ErrorManager.ERROR, e);
317: }
318: }
319: });
320: if (error[0])
321: throw new CannotRedoException();
322: }
323:
324: }
325:
326: public class SetParentEdit extends AbstractUndoableEdit {
327:
328: private DesignComponent oldParent;
329: private DesignComponent newParent;
330: private DesignComponent child;
331:
332: public SetParentEdit(DesignComponent oldParent,
333: DesignComponent newParent, DesignComponent child) {
334: this .oldParent = oldParent;
335: this .newParent = newParent;
336: this .child = child;
337: }
338:
339: @Override
340: public boolean isSignificant() {
341: return false;
342: }
343:
344: @Override
345: public void undo() throws CannotUndoException {
346: super .undo();
347: if (newParent != null)
348: newParent.removeComponent(child);
349: if (oldParent != null)
350: oldParent.addComponent(child);
351: }
352:
353: @Override
354: public void redo() throws CannotRedoException {
355: super .redo();
356: if (oldParent != null)
357: oldParent.removeComponent(child);
358: if (newParent != null)
359: newParent.addComponent(child);
360: }
361:
362: }
363:
364: public class WritePropertyEdit extends AbstractUndoableEdit {
365:
366: private DesignComponent component;
367: private String propertyName;
368: private PropertyValue oldValue;
369: private PropertyValue newValue;
370:
371: public WritePropertyEdit(DesignComponent component,
372: String propertyName, PropertyValue oldValue,
373: PropertyValue newValue) {
374: this .component = component;
375: this .propertyName = propertyName;
376: this .oldValue = oldValue;
377: this .newValue = newValue;
378: }
379:
380: @Override
381: public boolean isSignificant() {
382: return false;
383: }
384:
385: @Override
386: public void undo() throws CannotUndoException {
387: super .undo();
388: component.writeProperty(propertyName, oldValue);
389: }
390:
391: @Override
392: public void redo() throws CannotRedoException {
393: super .redo();
394: component.writeProperty(propertyName, newValue);
395: }
396:
397: }
398:
399: public class SelectionEdit extends AbstractUndoableEdit {
400:
401: private DesignDocument document;
402: private Collection<DesignComponent> oldSelection;
403: private Collection<DesignComponent> newSelection;
404:
405: public SelectionEdit(DesignDocument document,
406: Collection<DesignComponent> oldSelection,
407: Collection<DesignComponent> newSelection) {
408: this .document = document;
409: this .oldSelection = oldSelection;
410: this .newSelection = newSelection;
411: }
412:
413: @Override
414: public boolean isSignificant() {
415: return false;
416: }
417:
418: @Override
419: public void undo() throws CannotUndoException {
420: super .undo();
421: document.setSelectedComponents(null, oldSelection);
422: }
423:
424: @Override
425: public void redo() throws CannotRedoException {
426: super.redo();
427: document.setSelectedComponents(null, newSelection);
428: }
429:
430: }
431:
432: }
|