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