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.xml.text;
042:
043: import java.io.*;
044: import java.awt.event.*;
045: import java.text.*;
046: import java.util.Enumeration;
047: import java.lang.ref.WeakReference;
048: import java.beans.PropertyChangeEvent;
049:
050: import javax.swing.Timer;
051: import javax.swing.event.*;
052: import javax.swing.text.*;
053:
054: import org.netbeans.modules.xml.api.EncodingUtil;
055: import org.openide.*;
056: import org.openide.awt.StatusDisplayer;
057: import org.openide.text.*;
058: import org.openide.util.*;
059: import org.openide.windows.CloneableTopComponent;
060: import org.openide.windows.CloneableOpenSupport;
061: import org.openide.loaders.*;
062: import org.openide.cookies.*;
063: import org.openide.nodes.*;
064: import org.openide.filesystems.FileObject;
065: import org.openide.filesystems.FileLock;
066:
067: import org.netbeans.modules.xml.*;
068: import org.netbeans.modules.xml.lib.*;
069: import org.netbeans.modules.xml.sync.*;
070: import org.netbeans.modules.xml.cookies.*;
071:
072: /**
073: * Text editor support that handles I/O encoding and sync with tree.
074: * There are two timers a long time and a short time. The long time
075: * updates tree even in middle of writing text. The short time is restarted
076: * at every text change..
077: * <p>
078: * Listens for: text document change (edit), timers and document status change (loading).
079: */
080: public class TextEditorSupport extends DataEditorSupport implements
081: EditorCookie.Observable, EditCookie, CloseCookie, PrintCookie {
082: // ToDo:
083: // + extend CloneableEditorSupport instead of DataEditorSupport which is associated with DataObject
084:
085: /**
086: * Swings document property added by this support.
087: */
088: public static final String PROP_DOCUMENT_URL = "doc-url";
089:
090: /** Timer which countdowns the auto-reparsing time. */
091: private Timer timer;
092:
093: /** Used as lock object in close and openCloneableTopComponent. */
094: private static java.awt.Container awtLock;
095:
096: private Representation rep; //it is my representation
097:
098: //
099: // init
100: //
101:
102: /** public jsu for backward compatibility purposes. */
103: protected TextEditorSupport(XMLDataObjectLook xmlDO, Env env,
104: String mime_type) {
105: super ((DataObject) xmlDO, env);
106:
107: setMIMEType(mime_type);
108:
109: initTimer();
110:
111: initListeners();
112:
113: //??? why it is not under text module control?
114: // it must be more lazy, why we must open document
115: // it looks that Document's StreamDescriptionProperty fits
116: // try {
117: // if (xmlDO instanceof DataObject) {
118: // DataObject dobj = (DataObject) xmlDO; // we must cast, we cannot as for cookie in cookie <init> that is produced by factory
119: // FileObject fo = dobj.getPrimaryFile();
120: // URL url = fo.getURL();
121: // String system = url.toExternalForm();
122: // openDocument().putProperty(PROP_DOCUMENT_URL, system); //openit
123: // } else {
124: // new RuntimeException("DO expected").printStackTrace();
125: // }
126: // } catch (Exception ex) {
127: // // just let property undefined
128: // ex.printStackTrace();
129: // }
130: }
131:
132: /** public jsu for backward compatibility purposes. */
133: public TextEditorSupport(XMLDataObjectLook xmlDO, String mime_type) {
134: this (xmlDO, new Env(xmlDO), mime_type);
135: }
136:
137: //
138: // timer
139: //
140:
141: /**
142: * Initialize timers and handle their ticks.
143: */
144: private void initTimer() {
145: timer = new Timer(0, new java.awt.event.ActionListener() {
146: // we are called from the AWT thread so put itno other one
147: public void actionPerformed(java.awt.event.ActionEvent e) {
148: if (Util.THIS.isLoggable()) /* then */
149: Util.THIS
150: .debug("$$ TextEditorSupport::initTimer::actionPerformed: event = "
151: + e);
152:
153: RequestProcessor.postRequest(new Runnable() {
154: public void run() {
155: syncDocument(false);
156: }
157: });
158: }
159: });
160:
161: timer.setInitialDelay(getAutoParsingDelay());
162: timer.setRepeats(false);
163: }
164:
165: /*
166: * Add listeners at Document and document memory status (loading).
167: */
168: private void initListeners() {
169:
170: // create document listener
171:
172: final DocumentListener docListener = new DocumentListener() {
173: public void insertUpdate(DocumentEvent e) {
174: if (Util.THIS.isLoggable()) /* then */
175: Util.THIS
176: .debug("** TextEditorSupport::DocumentListener::insertUpdate: event = "
177: + e);
178:
179: restartTime();
180: }
181:
182: public void changedUpdate(DocumentEvent e) {
183: if (Util.THIS.isLoggable()) /* then */
184: Util.THIS
185: .debug("** TextEditorSupport::DocumentListener::changedUpdate: event = "
186: + e);
187:
188: // not interested in attribute changes
189: }
190:
191: public void removeUpdate(DocumentEvent e) {
192: if (Util.THIS.isLoggable()) /* then */
193: Util.THIS
194: .debug("** TextEditorSupport::DocumentListener::removeUpdate: event = "
195: + e);
196:
197: restartTime();
198: }
199:
200: private void restartTime() {
201: if (Util.THIS.isLoggable()) /* then */
202: Util.THIS
203: .debug("** TextEditorSupport::DocumentListener::restartTime: isInSync = "
204: + getXMLDataObjectLook()
205: .getSyncInterface()
206: .isInSync());
207:
208: if (getXMLDataObjectLook().getSyncInterface()
209: .isInSync()) {
210: return;
211: }
212: restartTimer(false);
213: }
214: };
215:
216: // listen for document loading then register to it the docListener as weak
217:
218: addChangeListener(new ChangeListener() {
219: public void stateChanged(ChangeEvent evt) {
220:
221: if (isDocumentLoaded()) {
222:
223: Document doc = getDocument();
224: // when the document is not yet loaded, do nothing
225: if (doc == null)
226: return;
227: doc.addDocumentListener(WeakListeners.document(
228: docListener, doc));
229:
230: if (rep == null) {
231: XMLDataObjectLook dobj = (XMLDataObjectLook) getDataObject();
232: Synchronizator sync = dobj.getSyncInterface();
233:
234: //!!! What does this hardcoding mean???
235: //[DEPENDENCY] it introduces really ugly core to it's client dependencies!!!
236: if (dobj instanceof org.netbeans.modules.xml.XMLDataObject) {
237: rep = new XMLTextRepresentation(
238: TextEditorSupport.this , sync);
239: } else if (dobj instanceof DTDDataObject) {
240: rep = new DTDTextRepresentation(
241: TextEditorSupport.this , sync);
242: } else if (dobj instanceof EntityDataObject) {
243: rep = new EntityTextRepresentation(
244: TextEditorSupport.this , sync);
245: }
246:
247: if (rep != null) {
248: sync.addRepresentation(rep);
249: }
250: }
251: }
252: }
253: });
254:
255: }
256:
257: /**
258: * It simply calls super.notifyClosed() for all instances except
259: * TextEditorSupport.class == this.getClass().
260: */
261: protected void notifyClosed() {
262: super .notifyClosed();
263:
264: // #15756 following code handles synchronization on text editor closing only!
265: if (this .getClass() != TextEditorSupport.class)
266: return;
267:
268: XMLDataObjectLook dobj = (XMLDataObjectLook) getDataObject();
269: Synchronizator sync = dobj.getSyncInterface();
270: Representation oldRep = rep;
271: rep = null;
272: if (oldRep != null) { // because of remove modified document
273: sync.removeRepresentation(oldRep);
274: }
275:
276: // if ( isModified() ) { // possible way to remove needless closeDocument followed by open
277: // Task reload = reloadDocument();
278: // reload.waitFinished();
279: // }
280: }
281:
282: /**
283: */
284: Env getEnv() {
285: return (Env) env;
286: }
287:
288: /**
289: */
290: protected XMLDataObjectLook getXMLDataObjectLook() {
291: return getEnv().getXMLDataObjectLook();
292: }
293:
294: /*
295: * Update presence of SaveCookie on first keystroke.
296: */
297: protected boolean notifyModified() {
298: if (getEnv().isModified()) {
299: return true;
300: }
301: if (!super .notifyModified()) {
302: return false;
303: }
304:
305: CookieManagerCookie manager = getEnv().getXMLDataObjectLook()
306: .getCookieManager();
307: manager.addCookie(getEnv());
308: XMLDataObjectLook obj = (XMLDataObjectLook) getDataObject();
309: if (obj.getCookie(SaveCookie.class) == null) {
310: obj.getCookieManager().addCookie(new SaveCookie() {
311: public void save() throws java.io.IOException {
312: try {
313: saveDocument();
314: } catch (UserCancelException e) {
315: //just ignore
316: }
317: }
318: });
319: }
320:
321: return true;
322: }
323:
324: /*
325: * Update presence of SaveCookie after save.
326: */
327: protected void notifyUnmodified() {
328: if (Util.THIS.isLoggable()) /* then */
329: Util.THIS.debug("Notifing unmodified"); // NOI18N
330:
331: super .notifyUnmodified();
332: CookieManagerCookie manager = getEnv().getXMLDataObjectLook()
333: .getCookieManager();
334: manager.removeCookie(getEnv());
335: }
336:
337: //~~~~~~~~~~~~~~~~~~~~~~~~~ I/O ENCODING HANDLING ~~~~~~~~~~~~~~~~~~~~~~~~~~~
338:
339: //indicates than document has wrong encoding @see #edit
340: private volatile boolean encodingErr = false;
341:
342: /** Read the file from the stream, detect right encoding.
343: */
344: protected void loadFromStreamToKit(StyledDocument doc,
345: InputStream in, EditorKit kit) throws IOException,
346: BadLocationException {
347: // predetect it to get optimalized XmlReader if utf-8
348: String enc = EncodingUtil.detectEncoding(in);
349: if (enc == null) {
350: enc = "UTF8"; //!!! // NOI18N
351: }
352: try {
353: Reader reader = new InputStreamReader(in, enc);
354: kit.read(reader, doc, 0);
355: } catch (CharConversionException ex) {
356: if (Util.THIS.isLoggable()) /* then */
357: Util.THIS.debug(
358: "\n!!! TextEditorSupport.loadFromStreamToKit: enc = '"
359: + enc + "'", ex);
360:
361: encodingErr = true;
362: } catch (UnsupportedEncodingException ex) {
363: if (Util.THIS.isLoggable()) /* then */
364: Util.THIS.debug(
365: "\n!!! TextEditorSupport.loadFromStreamToKit: enc = '"
366: + enc + "'", ex);
367:
368: encodingErr = true;
369: }
370:
371: }
372:
373: /** Store the document in proper encoding.
374: */
375: protected void saveFromKitToStream(StyledDocument doc,
376: EditorKit kit, OutputStream out) throws IOException,
377: BadLocationException {
378: String enc = EncodingUtil.detectEncoding(doc);
379: if (enc == null) {
380: enc = "UTF8"; //!!! // NOI18N
381: }
382: try {
383: if (Util.THIS.isLoggable()) /* then */
384: Util.THIS.debug("Saving using encoding");//, new RuntimeException (enc)); // NOI18N
385: if (Util.THIS.isLoggable()) /* then */
386: Util.THIS
387: .debug("!!! TextEditorSupport::saveFromKitToStream: enc = "
388: + enc);
389: //test encoding on dummy stream
390: new OutputStreamWriter(new ByteArrayOutputStream(1), enc);
391: if (Util.THIS.isLoggable()) /* then */
392: Util.THIS
393: .debug("!!! ::saveFromKitToStream: after first test -> OK");
394: Writer writer = new OutputStreamWriter(out, enc);
395: if (Util.THIS.isLoggable()) /* then */
396: Util.THIS
397: .debug("!!! ::saveFromKitToStream: writer = "
398: + writer);
399: kit.write(writer, doc, 0, doc.getLength());
400: } catch (UnsupportedEncodingException ex) {
401: //!!! just write nothing //?? save say as UTF-8
402: ErrorManager emgr = ErrorManager.getDefault();
403: IOException ioex = new IOException("Unsupported encoding "
404: + enc); // NOI18N
405: emgr.annotate(ioex, Util.THIS.getString(
406: "MSG_unsupported_encoding", enc));
407: throw ioex;
408: }
409: }
410:
411: /*
412: * Save document using encoding declared in XML prolog if possible otherwise
413: * at UTF-8 (in such case it updates the prolog).
414: */
415: public void saveDocument() throws IOException {
416: if (Util.THIS.isLoggable()) /* then */
417: Util.THIS.debug("saveDocument()..."); // NOI18N
418: final StyledDocument doc = getDocument();
419: String enc = EncodingUtil.detectEncoding(doc);
420: if (Util.THIS.isLoggable()) /* then */
421: Util.THIS
422: .debug("!!! TextEditorSupport::saveDocument: enc = "
423: + enc);
424: if (enc == null) {
425: enc = "UTF8"; //!!! // NOI18N
426: }
427: try {
428: //test encoding on dummy stream
429: new OutputStreamWriter(new ByteArrayOutputStream(1), enc);
430: if (!checkCharsetConversion(EncodingUtil
431: .getJava2IANAMapping(enc))) {
432: if (Util.THIS.isLoggable()) /* then */
433: Util.THIS.debug("Let unsaved."); // NOI18N
434: return;
435: }
436: super .saveDocument();
437: //moved from Env.save()
438: getDataObject().setModified(false);
439: getXMLDataObjectLook().getSyncInterface()
440: .representationChanged(Document.class);
441: } catch (UnsupportedEncodingException ex) {
442: //ask user what next?
443: NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(
444: java.text.MessageFormat.format(Util.THIS
445: .getString("TEXT_SAVE_AS_UTF"),
446: new Object[] { enc }));
447: Object res = DialogDisplayer.getDefault()
448: .notify(descriptor);
449: if (res.equals(NotifyDescriptor.YES_OPTION)) {
450: updateDocumentWithNewEncoding(doc);
451: } else { // NotifyDescriptor != YES_OPTION
452: if (Util.THIS.isLoggable()) /* then */
453: Util.THIS.debug("Let unsaved."); // NOI18N
454: throw new UserCancelException();
455: }
456: } // of catch UnsupportedEncodingException
457: }
458:
459: /**
460: * update prolog to new valid encoding
461: */
462: private void updateDocumentWithNewEncoding(final StyledDocument doc)
463: throws IOException {
464: try {
465: final int MAX_PROLOG = 1000;
466: int maxPrologLen = Math.min(MAX_PROLOG, doc.getLength());
467: final char prolog[] = doc.getText(0, maxPrologLen)
468: .toCharArray();
469: int prologLen = 0; // actual prolog length
470: //parse prolog and get prolog end
471: if (prolog[0] == '<' && prolog[1] == '?'
472: && prolog[2] == 'x') {
473: // look for delimitting ?>
474: for (int i = 3; i < maxPrologLen; i++) {
475: if (prolog[i] == '?' && prolog[i + 1] == '>') {
476: prologLen = i + 1;
477: break;
478: }
479: }
480: }
481: final int passPrologLen = prologLen;
482: Runnable edit = new Runnable() {
483: public void run() {
484: try {
485: doc.remove(0, passPrologLen + 1); // +1 it removes exclusive
486: doc.insertString(0,
487: "<?xml version='1.0' encoding='UTF-8' ?> \n<!-- was: "
488: + new String(prolog, 0,
489: passPrologLen + 1)
490: + " -->", null); // NOI18N
491: } catch (BadLocationException e) {
492: if (System
493: .getProperty("netbeans.debug.exceptions") != null) // NOI18N
494: e.printStackTrace();
495: }
496: }
497: };
498: NbDocument.runAtomic(doc, edit);
499: super .saveDocument();
500: //moved from Env.save()
501: getDataObject().setModified(false);
502: getXMLDataObjectLook().getSyncInterface()
503: .representationChanged(Document.class);
504: if (Util.THIS.isLoggable()) /* then */
505: Util.THIS.debug("Saved."); // NOI18N
506: } catch (BadLocationException lex) {
507: ErrorManager.getDefault().notify(lex);
508: }
509: }
510:
511: private boolean checkCharsetConversion(String encoding) /*throws UnsupportedEncodingException*/{
512: boolean value = true;
513: try {
514: java.nio.charset.CharsetEncoder coder = java.nio.charset.Charset
515: .forName(encoding).newEncoder();
516: if (!coder.canEncode(getDocument().getText(0,
517: getDocument().getLength()))) {
518: NotifyDescriptor nd = new NotifyDescriptor.Confirmation(
519: NbBundle
520: .getMessage(
521: TextEditorSupport.class,
522: "MSG_BadCharConversion", //NOI18N
523: new Object[] {
524: getDataObject()
525: .getPrimaryFile()
526: .getNameExt(),
527: encoding }),
528: NotifyDescriptor.YES_NO_OPTION,
529: NotifyDescriptor.WARNING_MESSAGE);
530: nd.setValue(NotifyDescriptor.NO_OPTION);
531: DialogDisplayer.getDefault().notify(nd);
532: if (nd.getValue() != NotifyDescriptor.YES_OPTION)
533: value = false;
534: }
535: } catch (javax.swing.text.BadLocationException e) {
536: ErrorManager.getDefault().notify(
537: ErrorManager.INFORMATIONAL, e);
538: }
539: /*catch (java.nio.charset.UnsupportedCharsetException e){
540: throw new UnsupportedEncodingException();
541: }*/
542: return value;
543: }
544:
545: //~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SYNC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
546:
547: /**
548: * TEXT changed -> update TREE.
549: */
550: protected void syncDocument(boolean fromFocus) {
551: if (Util.THIS.isLoggable()) /* then */
552: Util.THIS
553: .debug("@@ TextEditorSupport::syncDocument: fromFocus = "
554: + fromFocus);
555: if (Util.THIS.isLoggable()) /* then */
556: Util.THIS
557: .debug("@@ ::syncDocument: timer.isRunning = "
558: + timer.isRunning());
559:
560: if (fromFocus && !timer.isRunning())
561: return;
562: if (timer.isRunning())
563: timer.stop();
564:
565: XMLDataObjectLook sync = getXMLDataObjectLook();
566: if (sync != null) { // && isModified()) {
567: sync.getSyncInterface().representationChanged(
568: Document.class);
569: }
570:
571: }
572:
573: int getAutoParsingDelay() {
574: return 3000;
575: }
576:
577: /** Restart the timer which starts the parser after the specified delay.
578: * @param onlyIfRunning Restarts the timer only if it is already running
579: */
580: void restartTimer(boolean onlyIfRunning) {
581: if (Util.THIS.isLoggable()) /* then */
582: Util.THIS
583: .debug("## TextEditorSupport::restartTimer: onlyIfRunning = "
584: + onlyIfRunning);
585: if (Util.THIS.isLoggable()) /* then */
586: Util.THIS
587: .debug("## ::restartTimer: timer.isRunning = "
588: + timer.isRunning());
589:
590: if (onlyIfRunning && !timer.isRunning())
591: return;
592:
593: int delay = getAutoParsingDelay();
594: if (delay > 0) {
595: timer.setInitialDelay(delay);
596: timer.restart();
597: }
598: }
599:
600: //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
601:
602: /*
603: * An entry point via EditCookie.
604: * Delegate to <code>openDocument()</code>.
605: */
606: public final void edit() {
607:
608: try {
609: openDocument(); //use sync version of call - prepare encodingErr
610: if (encodingErr) {
611: String pattern = Util.THIS
612: .getString("TEXT_WRONG_ENCODING");
613: String msg = MessageFormat
614: .format(
615: pattern,
616: new Object[] { getDataObject()
617: .getPrimaryFile().toString() /*compatibleEntry.getFile().toString()*/});
618: DialogDisplayer.getDefault().notify(
619: new NotifyDescriptor.Message(msg,
620: NotifyDescriptor.ERROR_MESSAGE));
621:
622: } else {
623: Mutex.EVENT.writeAccess(new Runnable() {
624: public void run() {
625: CloneableTopComponent editor = openCloneableEditor();
626: editor.requestActive();
627: }
628: });
629: }
630: } catch (UserQuestionException e) { //this is a hack due to the issue #50701
631: open();
632: if (isDocumentLoaded()) {
633: if (encodingErr) {
634: String pattern = Util.THIS
635: .getString("TEXT_WRONG_ENCODING");
636: String msg = MessageFormat
637: .format(
638: pattern,
639: new Object[] { getDataObject()
640: .getPrimaryFile()
641: .toString() /*compatibleEntry.getFile().toString()*/});
642: DialogDisplayer.getDefault().notify(
643: new NotifyDescriptor.Message(msg,
644: NotifyDescriptor.ERROR_MESSAGE));
645:
646: } else {
647: Mutex.EVENT.writeAccess(new Runnable() {
648: public void run() {
649: CloneableTopComponent editor = openCloneableEditor();
650: editor.requestActive();
651: }
652: });
653: }
654: }
655: } catch (IOException ex) {
656: String pattern = Util.THIS.getString("TEXT_LOADING_ERROR");
657: String msg = MessageFormat
658: .format(
659: pattern,
660: new Object[] { getDataObject()
661: .getPrimaryFile().toString() /*compatibleEntry.getFile().toString()*/});
662: DialogDisplayer.getDefault().notify(
663: new NotifyDescriptor.Message(msg,
664: NotifyDescriptor.ERROR_MESSAGE));
665: }
666:
667: }
668:
669: /*
670: * Simply open for an cloneable editor. It at first tries to locate
671: * existing component in <code>allEditors</code> then if it fails create new one
672: * and registers it with <code>allEditors>/code>.
673: */
674: protected final CloneableEditor openCloneableEditor() {
675:
676: CloneableEditor ret = null;
677:
678: synchronized (getLock()) {
679:
680: String msg = messageOpening();
681: if (msg != null) {
682: StatusDisplayer.getDefault().setStatusText(msg);
683: }
684:
685: Enumeration en = allEditors.getComponents();
686: while (en.hasMoreElements()) {
687: CloneableTopComponent editor = (CloneableTopComponent) en
688: .nextElement();
689: if (editor instanceof CloneableEditor) {
690: editor.open();
691: ret = (CloneableEditor) editor;
692: }
693: }
694:
695: // no opened editor, create a new one
696:
697: if (ret == null) {
698: CloneableEditor editor = (CloneableEditor) createCloneableTopComponent(); // this is important -- see final createCloneableTopComponent
699: editor.setReference(allEditors);
700: editor.open();
701: ret = editor;
702: }
703:
704: msg = messageOpened();
705: if (msg == null) {
706: msg = ""; // NOI18N
707: }
708: StatusDisplayer.getDefault().setStatusText(msg);
709:
710: return ret;
711: }
712: }
713:
714: /**
715: * Creates lock object used in close and openCloneableTopComponent.
716: * @return never null
717: */
718: protected Object getLock() {
719: if (awtLock == null) {
720: awtLock = new java.awt.Container();
721: }
722: return awtLock.getTreeLock();
723: }
724:
725: /*
726: * @return component visualizing this support.
727: */
728: protected CloneableEditor createCloneableEditor() {
729: return new TextEditorComponent(this );
730: }
731:
732: // This must call super createCloneableTopComponent because it prepare document, create cloneable editor and initialize it. See super.
733: protected final CloneableTopComponent createCloneableTopComponent() {
734: return super .createCloneableTopComponent(); // creates CloneableEditor (calling createCloneableEditor)
735: }
736:
737: /**
738: */
739: public static final TextEditorSupportFactory findEditorSupportFactory(
740: XMLDataObjectLook xmlDO, String mime) {
741: return new TextEditorSupportFactory(xmlDO, mime);
742: }
743:
744: //
745: // class Env
746: //
747:
748: /**
749: *
750: */
751: protected static class Env extends DataEditorSupport.Env implements
752: SaveCookie {
753:
754: /** Serial Version UID */
755: private static final long serialVersionUID = -5285524519399090028L;
756:
757: /** */
758: public Env(XMLDataObjectLook obj) {
759: super ((DataObject) obj);
760: }
761:
762: /**
763: */
764: protected XMLDataObjectLook getXMLDataObjectLook() {
765: return (XMLDataObjectLook) getDataObject();
766: }
767:
768: /**
769: */
770: protected FileObject getFile() {
771: return getDataObject().getPrimaryFile();
772: }
773:
774: /**
775: */
776: protected FileLock takeLock() throws IOException {
777: return ((MultiDataObject) getDataObject())
778: .getPrimaryEntry().takeLock();
779: }
780:
781: /**
782: */
783: public synchronized void save() throws IOException {
784: findTextEditorSupport().saveDocument();
785: }
786:
787: /**
788: */
789: public CloneableOpenSupport findCloneableOpenSupport() {
790: return findTextEditorSupport();
791: }
792:
793: /**
794: */
795: public TextEditorSupport findTextEditorSupport() {
796: EditCookie cookie = getDataObject().getCookie(
797: EditCookie.class);
798: if (cookie instanceof TextEditorSupport)
799: return (TextEditorSupport) cookie;
800:
801: return null;
802: }
803:
804: // copy pasted, do not get it
805: public void propertyChange(PropertyChangeEvent ev) {
806: if (DataObject.PROP_PRIMARY_FILE.equals(ev
807: .getPropertyName())) {
808: changeFile();
809: }
810: super .propertyChange(ev);
811: }
812:
813: } // end: class Env
814:
815: //
816: // class TextEditorSupportFactory
817: //
818:
819: /**
820: *
821: */
822: public static class TextEditorSupportFactory implements
823: CookieSet.Factory {
824: /** */
825: private WeakReference editorRef;
826: /** */
827: private final XMLDataObjectLook dataObject; // used while creating the editor
828: /** */
829: private final String mime; // used while creating the editor
830:
831: //
832: // init
833: //
834:
835: /** Create new TextEditorSupportFactory. */
836: public TextEditorSupportFactory(XMLDataObjectLook dobj,
837: String mime) {
838: this .dataObject = dobj;
839: this .mime = mime;
840: }
841:
842: /**
843: */
844: protected Class[] supportedCookies() {
845: return new Class[] { EditorCookie.class,
846: EditorCookie.Observable.class, EditCookie.class,
847: CloseCookie.class, PrintCookie.class, };
848: }
849:
850: /**
851: */
852: public final void registerCookies(CookieSet cookieSet) {
853: Class[] supportedCookies = supportedCookies();
854: for (int i = 0; i < supportedCookies.length; i++) {
855: cookieSet.add(supportedCookies[i], this );
856: }
857: }
858:
859: /** Creates a Node.Cookie of given class. The method
860: * may be called more than once.
861: */
862: public final Node.Cookie createCookie(Class klass) {
863: Class[] supportedCookies = supportedCookies();
864: for (int i = 0; i < supportedCookies.length; i++) {
865: if (supportedCookies[i].isAssignableFrom(klass)) {
866: return createEditor();
867: }
868: }
869: return null;
870: }
871:
872: /**
873: */
874: private final synchronized TextEditorSupport createEditor() { // atomic test and set
875: TextEditorSupport editorSupport = null;
876:
877: if (editorRef != null) {
878: editorSupport = (TextEditorSupport) editorRef.get();
879: }
880: if (editorSupport == null) {
881: editorSupport = prepareEditor();
882: editorRef = new WeakReference(editorSupport);
883: }
884:
885: return editorSupport;
886: }
887:
888: /**
889: */
890: protected TextEditorSupport prepareEditor() {
891: if (Util.THIS.isLoggable()) /* then */
892: Util.THIS.debug("Initializing TextEditorSupport ..."); // NOI18N
893:
894: return new TextEditorSupport(getDataObject(), getMIMEType());
895: }
896:
897: /**
898: */
899: protected final XMLDataObjectLook getDataObject() {
900: return dataObject;
901: }
902:
903: /**
904: */
905: protected final String getMIMEType() {
906: return mime;
907: }
908:
909: } // end of class TextEditorSupportFactory
910:
911: }
|