001: /*
002: * The contents of this file are subject to the terms of the Common Development
003: * and Distribution License (the License). You may not use this file except in
004: * compliance with the License.
005: *
006: * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
007: * or http://www.netbeans.org/cddl.txt.
008: *
009: * When distributing Covered Code, include this CDDL Header Notice in each file
010: * and include the License file at http://www.netbeans.org/cddl.txt.
011: * If applicable, add the following below the CDDL Header, with the fields
012: * enclosed by brackets [] replaced by your own identifying information:
013: * "Portions Copyrighted [year] [name of copyright owner]"
014: *
015: * The Original Software is NetBeans. The Initial Developer of the Original
016: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
017: * Microsystems, Inc. All Rights Reserved.
018: */
019: package org.netbeans.modules.xslt.tmap;
020:
021: import java.awt.EventQueue;
022: import java.io.IOException;
023: import java.io.Serializable;
024: import java.util.ArrayList;
025: import java.util.List;
026: import java.util.Set;
027: import javax.swing.text.AbstractDocument;
028: import javax.swing.text.StyledDocument;
029: import org.netbeans.api.xml.cookies.CookieObserver;
030: import org.netbeans.core.api.multiview.MultiViewHandler;
031: import org.netbeans.core.api.multiview.MultiViews;
032: import org.netbeans.core.spi.multiview.CloseOperationHandler;
033: import org.netbeans.core.spi.multiview.CloseOperationState;
034: import org.netbeans.modules.xml.retriever.catalog.Utilities;
035: import org.netbeans.modules.xml.validation.ShowCookie;
036: import org.netbeans.modules.xml.validation.ui.ValidationAnnotation;
037: import org.netbeans.modules.xml.xam.AbstractModel;
038: import org.netbeans.modules.xml.xam.Component;
039: import org.netbeans.modules.xml.xam.Model.State;
040: import org.netbeans.modules.xml.xam.ModelSource;
041: import org.netbeans.modules.xml.xam.spi.Validator.ResultItem;
042: import org.netbeans.modules.xml.xam.ui.undo.QuietUndoManager;
043: import org.netbeans.modules.xslt.tmap.model.api.TMapModel;
044: import org.netbeans.modules.xslt.tmap.model.spi.TMapModelFactory;
045: import org.netbeans.modules.xslt.tmap.multiview.TMapMultiViewSupport;
046: import org.netbeans.modules.xslt.tmap.multiview.source.TMapSourceMultiViewElementDesc;
047: import org.openide.ErrorManager;
048: import org.openide.awt.UndoRedo;
049: import org.openide.cookies.EditCookie;
050: import org.openide.cookies.EditorCookie;
051: import org.openide.cookies.LineCookie;
052: import org.openide.cookies.OpenCookie;
053: import org.openide.filesystems.FileLock;
054: import org.openide.filesystems.FileObject;
055: import org.openide.loaders.DataObject;
056: import org.openide.loaders.MultiDataObject;
057: import org.openide.text.CloneableEditor;
058: import org.openide.text.CloneableEditorSupport;
059: import org.openide.text.CloneableEditorSupport.Pane;
060: import org.openide.text.DataEditorSupport;
061: import org.openide.util.Lookup;
062: import org.openide.util.Task;
063: import org.openide.util.TaskListener;
064: import org.openide.windows.Mode;
065: import org.openide.windows.TopComponent;
066: import org.openide.windows.WindowManager;
067: import org.netbeans.modules.soa.ui.UndoRedoManagerProvider;
068:
069: /**
070: * @author Vitaly Bychkov
071: */
072: public class TMapDataEditorSupport extends DataEditorSupport implements
073: OpenCookie, EditCookie, EditorCookie.Observable, ShowCookie,
074: UndoRedoManagerProvider {
075: public TMapDataEditorSupport(TMapDataObject dObj) {
076: super (dObj, new TMapEnv(dObj));
077: setMIMEType(TMapDataLoader.MIME_TYPE);
078: }
079:
080: // vlv
081: public UndoRedo.Manager getUndoRedoManager() {
082: return getUndoManager();
083: }
084:
085: /** {@inheritDoc} */
086: @Override
087: public void saveDocument() throws IOException {
088: super .saveDocument();
089: syncModel();
090: getDataObject().setModified(false);
091: }
092:
093: /**
094: * Sync Transform Map model with source.
095: */
096: public void syncModel() {
097: try {
098: TMapModel model = getTMapModel();
099: if (model != null) {
100: model.sync();
101: }
102: } catch (IOException e) {
103: ErrorManager.getDefault().notify(
104: ErrorManager.INFORMATIONAL, e);
105: // assert false;
106: }
107: }
108:
109: public QuietUndoManager getUndoManager() {
110: return (QuietUndoManager) getUndoRedo();
111: }
112:
113: /**
114: * @return TransformMap Model for this editor.
115: */
116: public TMapModel getTMapModel() {
117: TMapDataObject dataObject = getEnv().getTMapDataObject();
118: ModelSource modelSource = Utilities.getModelSource(dataObject
119: .getPrimaryFile(), true);
120: return getModelFactory().getModel(modelSource);
121: }
122:
123: /**
124: * Implements ShowCookie interface used to open editor for the object containing xam Component
125: *
126: * Opens the editor for the file pointed by this resultItem.
127: * @param resultItem Contains the error/warning source, message.
128: */
129: public void show(final ResultItem resultItem) {
130: if (!(resultItem.getModel() instanceof AbstractModel)) {
131: return;
132: }
133:
134: final Component componentEntity = resultItem.getComponents();
135:
136: // Get the edit and line cookies.
137: DataObject d = getDataObject();
138: final LineCookie lc = (LineCookie) d
139: .getCookie(LineCookie.class);
140: final EditCookie ec = (EditCookie) d
141: .getCookie(EditCookie.class);
142: if (lc == null || ec == null) {
143: return;
144: }
145:
146: javax.swing.SwingUtilities.invokeLater(new Runnable() {
147: public void run() {
148: // Opens the editor or brings it into focus
149: // and makes it the activated topcomponent.
150: ec.edit();
151:
152: TopComponent tc = WindowManager.getDefault()
153: .getRegistry().getActivated();
154: MultiViewHandler mvh = MultiViews
155: .findMultiViewHandler(tc);
156:
157: if (mvh == null) {
158: return;
159: }
160:
161: // If model is broken
162: // OR if the resultItem.getComponents() is null which
163: // means the resultItem was generated when the model was broken.
164: // In the above cases switch to the source multiview.
165: if (resultItem.getModel().getState().equals(
166: State.NOT_WELL_FORMED)
167: || resultItem.getComponents() == null) {
168: for (int index1 = 0; index1 < mvh.getPerspectives().length; index1++) {
169: if (mvh.getPerspectives()[index1]
170: .preferredID()
171: .equals(
172: TMapSourceMultiViewElementDesc.PREFERED_ID))
173: mvh
174: .requestActive(mvh
175: .getPerspectives()[index1]);
176: }
177: }
178:
179: //TODO a
180: // Set annotation or select element in the multiview.
181: // MultiViewPerspective mvp = mvh.getSelectedPerspective();
182: // if (mvp.preferredID().equals("tmap-designer")) {
183: // List<TopComponent> list = getAssociatedTopComponents();
184: // for (TopComponent topComponent : list) {
185: // // Make sure this is a multiview window, and not just
186: // // some
187: // // window that has our DataObject (e.g. Projects,Files).
188: // MultiViewHandler handler = MultiViews
189: // .findMultiViewHandler(topComponent);
190: // if (handler != null && topComponent != null) {
191: // SelectXsltElement selectElement =
192: // (SelectXsltElement) topComponent.getLookup()
193: // .lookup(SelectXsltElement.class);
194: // if (selectElement == null)
195: // return;
196: // selectElement.select(XSLTComponent);
197: // }
198: // }
199: // } else if (mvp.preferredID().equals("xslt-mapper")) {
200: // List<TopComponent> list = getAssociatedTopComponents();
201: // for (TopComponent topComponent : list) {
202: // // Make sure this is a multiview window, and not just
203: // // some
204: // // window that has our DataObject (e.g. Projects,Files).
205: // MultiViewHandler handler = MultiViews
206: // .findMultiViewHandler(topComponent);
207: // if (handler != null && topComponent != null) {
208: // SelectXsltElement selectElement =
209: // (SelectXsltElement) topComponent.getLookup()
210: // .lookup(SelectXsltElement.class);
211: // if (selectElement == null)
212: // return;
213: // selectElement.select(XSLTComponent);
214: // }
215: // }
216: // } else if (mvp.preferredID().equals(
217: // XSLTSourceMultiViewElementDesc.PREFERED_ID)) {
218: //
219: // // Get the line number.
220: // int lineNum;
221: // if(resultItem.getComponents() != null) {
222: // lineNum = getLineNumber((XSLTComponent)resultItem.getComponents());
223: // } else {
224: // lineNum = resultItem.getLineNumber() - 1;
225: // }
226: // if (lineNum < 1) {
227: // return;
228: // }
229: // Line l = lc.getLineSet().getCurrent(lineNum);
230: // l.show(Line.SHOW_GOTO);
231: // annotation.show(l, resultItem.getDescription());
232: //
233: // }
234: }
235: });
236:
237: }
238:
239: private List<TopComponent> getAssociatedTopComponents() {
240: // Create a list of TopComponents associated with the
241: // editor's schema data object, starting with the the
242: // active TopComponent. Add all open TopComponents in
243: // any mode that are associated with the DataObject.
244: // [Note that EDITOR_MODE does not contain editors in
245: // split mode.]
246: List<TopComponent> associatedTCs = new ArrayList<TopComponent>();
247: DataObject targetDO = getDataObject();
248: TopComponent activeTC = TopComponent.getRegistry()
249: .getActivated();
250: if (activeTC != null
251: && targetDO == (DataObject) activeTC.getLookup()
252: .lookup(DataObject.class)) {
253: associatedTCs.add(activeTC);
254: }
255: Set openTCs = TopComponent.getRegistry().getOpened();
256: for (Object tc : openTCs) {
257: TopComponent tcc = (TopComponent) tc;
258: if (targetDO == (DataObject) tcc.getLookup().lookup(
259: DataObject.class)) {
260: associatedTCs.add(tcc);
261: }
262: }
263: return associatedTCs;
264: }
265:
266: public boolean validateXML(CookieObserver observer) {
267: // TODO a
268: return true;
269: }
270:
271: @Override
272: protected CloneableEditorSupport.Pane createPane() {
273: TopComponent multiview = TMapMultiViewSupport
274: .createMultiView((TMapDataObject) getDataObject());
275:
276: Mode editorMode = WindowManager.getDefault().findMode(
277: EDITOR_MODE);
278: if (editorMode != null) {
279: editorMode.dockInto(multiview);
280: }
281:
282: return (Pane) multiview;
283: }
284:
285: @Override
286: protected void notifyClosed() {
287: QuietUndoManager undo = getUndoManager();
288: StyledDocument doc = getDocument();
289: synchronized (undo) {
290: // May be null when closing the editor.
291: if (doc != null) {
292: doc.removeUndoableEditListener(undo);
293: undo.endCompound();
294: undo.setDocument(null);
295: }
296:
297: TMapModel model = getTMapModel();
298: if (model != null) {
299: model.removeUndoableEditListener(undo);
300: }
301: // Must unset the model when no longer listening to it.
302: undo.setModel(null);
303:
304: }
305: super .notifyClosed();
306: getUndoManager().discardAllEdits();
307:
308: // all editors are closed so we don't need to keep this task.
309: prepareTask = null;
310:
311: // getValidationController().detach();
312:
313: }
314:
315: /*
316: * This method is redefined for marking big TopCompenent as modified (
317: * asterik (*) needs to be appended to name of bpel file ). Without this
318: * overriding file will be marked as modified only when source multiview is
319: * edited. Modification in design view will not lead to marking TopComponent
320: * as modified. see bug description for #6421669. (non-Javadoc)
321: *
322: * @see org.openide.text.CloneableEditorSupport#updateTitles()
323: */
324: @Override
325: protected void updateTitles() {
326: /* This method is invoked by DataEditorSupport.DataNodeListener
327: * whenever the DataNode displayName property is changed. It is
328: * also called when the CloneableEditorSupport is (un)modified.
329: */
330:
331: // Let the superclass handle the CloneableEditor instances.
332: super .updateTitles();
333: // We need to get the title updated on the MultiViewTopComponent.
334: EventQueue.invokeLater(new Runnable() {
335:
336: public void run() {
337: List<TopComponent> list = getAssociatedTopComponents();
338: for (TopComponent topComponent : list) {
339: // Make sure this is a multiview window, and not just some
340: // window that has our DataObject (e.g. Projects, Files).
341: MultiViewHandler handler = MultiViews
342: .findMultiViewHandler(topComponent);
343: if (handler != null && topComponent != null) {
344: topComponent
345: .setHtmlDisplayName(messageHtmlName());
346: String name = messageName();
347: topComponent.setDisplayName(name);
348: topComponent.setName(name);
349: topComponent.setToolTipText(messageToolTip());
350: }
351: }
352: }
353: });
354: }
355:
356: protected TMapEnv getEnv() {
357: return (TMapEnv) env;
358: }
359:
360: @Override
361: protected UndoRedo.Manager createUndoRedoManager() {
362: // Override so the superclass will use our proxy undo manager
363: // instead of the default, then we can intercept edits.
364: return new QuietUndoManager(super .createUndoRedoManager());
365: // Note we cannot set the document on the undo manager right
366: // now, as CES is probably trying to open the document.
367: }
368:
369: /**
370: * Environment that connects the dataobject and ClonneableEditorSupport
371: */
372: private static class TMapEnv extends DataEditorSupport.Env {
373:
374: private static final long serialVersionUID = 835762240381934851L;
375:
376: public TMapEnv(TMapDataObject dObj) {
377: super (dObj);
378: }
379:
380: public TMapDataObject getTMapDataObject() {
381: return (TMapDataObject) getDataObject();
382: }
383:
384: protected FileObject getFile() {
385: return getDataObject().getPrimaryFile();
386: }
387:
388: protected FileLock takeLock() throws IOException {
389: return ((MultiDataObject) getDataObject())
390: .getPrimaryEntry().takeLock();
391: }
392: }
393:
394: @Override
395: public void initializeCloneableEditor(CloneableEditor editor) {
396: super .initializeCloneableEditor(editor);
397: // Force the title to update so the * left over from when the
398: // modified data object was discarded is removed from the title.
399: if (!getEnv().getTMapDataObject().isModified()) {
400: // Update later to avoid an infinite loop.
401: EventQueue.invokeLater(new Runnable() {
402: public void run() {
403: updateTitles();
404: }
405: });
406: }
407:
408: // TODO a
409: // /*
410: // * I put this code here because it is called each time when
411: // * editor is opened. This can happened omn first open,
412: // * on reopen, on deserialization.
413: // * CTOR of BPELDataEditorSupport is called only once due lifecycle
414: // * data object, so it cannot be used on attach after reopening.
415: // * Method "open" doesn't called after deser-ion.
416: // * But this method is called always on editor opening.
417: // */
418: // getValidationController().attach();
419: }
420:
421: @Override
422: public Task prepareDocument() {
423: Task task = super .prepareDocument();
424: // Avoid listening to the same task more than once.
425: if (task == prepareTask) {
426: return task;
427: }
428: task.addTaskListener(new TaskListener() {
429:
430: public void taskFinished(Task task) {
431: /* The superclass prepareDocument() adds the undo/redo
432: * manager as a listener -- we need to remove it since
433: * the views will add and remove it as needed.
434: */
435: QuietUndoManager undo = (QuietUndoManager) getUndoRedo();
436: StyledDocument doc = getDocument();
437: synchronized (undo) {
438: // Now that the document is ready, pass it to the manager.
439: undo.setDocument((AbstractDocument) doc);
440: if (!undo.isCompound()) {
441: /* The superclass prepareDocument() adds the undo/redo
442: * manager as a listener -- we need to remove it since
443: * we will initially listen to the model instead.
444: */
445: doc.removeUndoableEditListener(undo);
446: // If not listening to document, then listen to model.
447: addUndoManagerToModel(undo);
448: }
449: }
450: }
451: });
452: return task;
453: }
454:
455: @Override
456: public Task reloadDocument() {
457: Task task = super .reloadDocument();
458: task.addTaskListener(new TaskListener() {
459:
460: public void taskFinished(Task task) {
461: EventQueue.invokeLater(new Runnable() {
462:
463: public void run() {
464: QuietUndoManager undo = getUndoManager();
465: StyledDocument doc = getDocument();
466: /* The superclass reloadDocument() adds the undo
467: * manager as an undoable edit listener.
468: */
469: synchronized (undo) {
470: if (!undo.isCompound() && doc != null) {
471: doc.removeUndoableEditListener(undo);
472: }
473: }
474: }
475: });
476: }
477: });
478: return task;
479: }
480:
481: /**
482: * Adds the undo/redo manager to the document as an undoable edit listener,
483: * so it receives the edits onto the queue. The manager will be removed from
484: * the model as an undoable edit listener.
485: * <p>
486: * This method may be called repeatedly.
487: * </p>
488: */
489: public void addUndoManagerToDocument() {
490: /*
491: * This method may be called repeatedly.
492: * Stop the undo manager from listening to the model, as it will
493: * be listening to the document now.
494: */
495: QuietUndoManager undo = getUndoManager();
496: StyledDocument doc = getDocument();
497: synchronized (undo) {
498:
499: removeUndoManagerFromModel();
500:
501: /*
502: * Document may be null if the cloned views are not behaving
503: * correctly.
504: */
505: if (doc != null) {
506: // Ensure the listener is not added twice.
507: doc.removeUndoableEditListener(undo);
508: doc.addUndoableEditListener(undo);
509: /*
510: * Start the compound mode of the undo manager, such that when
511: * we are hidden, we will treat all of the edits as a single
512: * compound edit. This avoids having the user invoke undo
513: * numerous times when in the model view.
514: */
515: undo.beginCompound();
516: }
517: }
518: }
519:
520: /**
521: * Add the undo/redo manager undoable edit listener to the model.
522: * <p>
523: * Caller should synchronize on the undo manager prior to calling this
524: * method, to avoid thread concurrency issues.
525: * </p>
526: *
527: * @param undo
528: * the undo manager.
529: */
530: public void addUndoManagerToModel(QuietUndoManager undo) {
531: TMapModel model = getTMapModel();
532: if (model != null) {
533: // Ensure the listener is not added twice.
534: removeUndoManagerFromModel();
535: model.addUndoableEditListener(undo);
536: /* Ensure the model is sync'd when undo/redo is invoked,
537: * otherwise the edits are added to the queue and eventually
538: * cause exceptions.
539: */
540: undo.setModel(model);
541:
542: }
543: }
544:
545: /**
546: * Removes the undo/redo manager undoable edit listener from the document,
547: * to stop receiving undoable edits. The manager will be added to the model
548: * as an undoable edit listener.
549: * <p>
550: * This method may be called repeatedly.
551: * </p>
552: */
553: public void removeUndoManagerFromDocument() {
554: // This method may be called repeatedly.
555: QuietUndoManager undo = getUndoManager();
556: StyledDocument doc = getDocument();
557: synchronized (undo) {
558: // May be null when closing the editor.
559: if (doc != null) {
560: doc.removeUndoableEditListener(undo);
561: undo.endCompound();
562: }
563: // Have the undo manager listen to the model when it is not
564: // listening to the document.
565: addUndoManagerToModel(undo);
566: }
567: }
568:
569: /**
570: * Removes the undo/redo manager undoable edit listener from the bpel model,
571: * to stop receiving undoable edits.
572: */
573: private void removeUndoManagerFromModel() {
574: TMapModel model = getTMapModel();
575: if (model != null) {
576: QuietUndoManager undo = getUndoManager();
577: model.removeUndoableEditListener(undo);
578: // Must unset the model when leaving model view.
579: undo.setModel(null);
580: }
581: }
582:
583: /**
584: * Remove the undo manager from both the model and document, such that
585: * any changes made to either will not be added to the undo queue. The
586: * caller should invoke <code>resumeUndoRedo()</code> once the changes
587: * are completed.
588: *
589: * @return a value that must be passed to <code>resumeUndoRedo()</code>.
590: */
591: public boolean suspendUndoRedo() {
592: QuietUndoManager undo = getUndoManager();
593: boolean compound;
594: synchronized (undo) {
595: compound = undo.isCompound();
596: if (compound) {
597: removeUndoManagerFromDocument();
598: }
599: removeUndoManagerFromModel();
600: }
601: return compound;
602: }
603:
604: /**
605: * Add the undo manager as an undoable edit listener to either the
606: * Swing document or the XAM model, and set up the compound mode if
607: * that was in place previously.
608: *
609: * @param value value returned from <code>suspendUndoRedo()</code>
610: */
611: public void resumeUndoRedo(boolean value) {
612: if (value) {
613: addUndoManagerToDocument();
614: } else {
615: QuietUndoManager undo = getUndoManager();
616: synchronized (undo) {
617: addUndoManagerToModel(undo);
618: }
619: }
620: }
621:
622: private TMapModelFactory getModelFactory() {
623: TMapModelFactory factory = (TMapModelFactory) Lookup
624: .getDefault().lookup(TMapModelFactory.class);
625: return factory;
626: }
627:
628: /**
629: * This method allows the close behavior of CloneableEditorSupport to be
630: * invoked from the SourceMultiViewElement. The close method of
631: * CloneableEditorSupport at least clears the undo queue and releases the
632: * swing document.
633: */
634: public boolean silentClose() {
635: return super .close(false);
636: }
637:
638: /**
639: * Handles closing of the MultiView component globally. Each opened {@link org.netbeans.core.spi.multiview.MultiViewElement}
640: * creates a {@link org.netbeans.core.spi.multiview.CloseOperationState} instance to notify the environment of it's internal state.
641: *
642: */
643: public static class CloseHandler implements CloseOperationHandler,
644: Serializable {
645:
646: private static final long serialVersionUID = -4621077799099893176L;
647:
648: private CloseHandler() {
649: // CTOR for deser
650: }
651:
652: public CloseHandler(TMapDataObject obj) {
653: myDataObject = obj;
654: }
655:
656: public boolean resolveCloseOperation(
657: CloseOperationState[] elements) {
658: TMapDataEditorSupport support = myDataObject == null ? null
659: : (TMapDataEditorSupport) myDataObject
660: .getCookie(TMapDataEditorSupport.class);
661: if (support == null) {
662: return true;
663: }
664: boolean close = support.canClose();
665: if (close) {
666: if (myDataObject.isValid()) {
667: // In odrer to clear the undo queue of orphaned edits, let's always
668: // reload the document, which discards the edits on the undo queue.
669: // The critical part is that BeforeSaveEdit gets added to the queue.
670: // // In case user discarded edits, need to reload.
671: // if (dataObject.isModified()) {
672: support.reloadDocument().waitFinished();
673: // }
674: }
675:
676: myDataObject.setModified(false); // Issue 85629
677: }
678: return close;
679: }
680:
681: private TMapDataObject myDataObject;
682: }
683:
684: /** Used for managing the prepareTask listener. */
685: private transient Task prepareTask;
686:
687: private ValidationAnnotation myAnnotation = new ValidationAnnotation();
688:
689: }
|