alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */
package org.netbeans.modules.xml.core.text;

import java.io.*;
import java.net.URL;
import java.awt.event.*;
import java.text.*;
import java.util.Enumeration;
import java.lang.ref.WeakReference;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;

import javax.swing.Timer;
import javax.swing.JEditorPane;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.JViewport;

import org.openide.*;
import org.openide.awt.StatusDisplayer;
import org.openide.text.*;
import org.openide.util.*;
import org.openide.windows.CloneableTopComponent;
import org.openide.windows.CloneableOpenSupport;
import org.openide.windows.Workspace;
import org.openide.windows.Mode;
import org.openide.loaders.*;
import org.openide.cookies.*;
import org.openide.nodes.*;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileLock;

import org.netbeans.modules.xml.core.*;
import org.netbeans.modules.xml.core.lib.*;
import org.netbeans.modules.xml.core.sync.*;
import org.netbeans.modules.xml.core.cookies.*;

import org.netbeans.modules.xml.core.settings.CoreSettings;

/**
 * Text editor support that handles I/O encoding and sync with tree.
 * There are two timers a long time and a short time. The long time
 * updates tree even in middle of writing text. The short time is restarted
 * at every text change..
 * 

* Listens for: text document change (edit), timers and document status change (loading). */ public class TextEditorSupport extends DataEditorSupport implements EditorCookie.Observable, EditCookie, CloseCookie, PrintCookie { // ToDo: // + extend CloneableEditorSupport instead of DataEditorSupport which is associated with DataObject /** * Swings document property added by this support. */ public static final String PROP_DOCUMENT_URL = "doc-url"; /** XML Settings */ static final CoreSettings settings = CoreSettings.getDefault(); /** Timer which countdowns the auto-reparsing time. */ private Timer timer; /** Used as lock object in close and openCloneableTopComponent. */ private static java.awt.Container awtLock; private Representation rep; //it is my representation // // init // /** public jsu for backward compatibility purposes. */ protected TextEditorSupport (XMLDataObjectLook xmlDO, Env env, String mime_type) { super ((DataObject)xmlDO, env); //??? initListeners xmlDO.addPropertyChangeListener (new ModificationListener()); setMIMEType (mime_type); initTimer(); initListeners(); //??? why it is not under text module control? // it must be more lazy, why we must open document // it looks that Document's StreamDescriptionProperty fits // try { // if (xmlDO instanceof DataObject) { // DataObject dobj = (DataObject) xmlDO; // we must cast, we cannot as for cookie in cookie that is produced by factory // FileObject fo = dobj.getPrimaryFile(); // URL url = fo.getURL(); // String system = url.toExternalForm(); // openDocument().putProperty(PROP_DOCUMENT_URL, system); //openit // } else { // new RuntimeException("DO expected").printStackTrace(); // } // } catch (Exception ex) { // // just let property undefined // ex.printStackTrace(); // } } /** public jsu for backward compatibility purposes. */ public TextEditorSupport (XMLDataObjectLook xmlDO, String mime_type) { this (xmlDO, new Env (xmlDO), mime_type); } // // timer // /** * Initialize timers and handle their ticks. */ private void initTimer () { timer = new Timer (0, new java.awt.event.ActionListener() { // we are called from the AWT thread so put itno other one public void actionPerformed (java.awt.event.ActionEvent e) { if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("$$ TextEditorSupport::initTimer::actionPerformed: event = " + e); RequestProcessor.postRequest( new Runnable() { public void run() { syncDocument (false); } }); } }); timer.setInitialDelay (settings.getAutoParsingDelay()); timer.setRepeats (false); } /* * Add listeners at Document and document memory status (loading). */ private void initListeners() { // create document listener final DocumentListener docListener = new DocumentListener () { public void insertUpdate (DocumentEvent e) { if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("** TextEditorSupport::DocumentListener::insertUpdate: event = " + e); restartTime(); } public void changedUpdate (DocumentEvent e) { if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("** TextEditorSupport::DocumentListener::changedUpdate: event = " + e); // not interested in attribute changes } public void removeUpdate (DocumentEvent e) { if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("** TextEditorSupport::DocumentListener::removeUpdate: event = " + e); restartTime(); } private void restartTime() { if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("** TextEditorSupport::DocumentListener::restartTime: isInSync = " + getXMLDataObjectLook().getSyncInterface().isInSync()); if (getXMLDataObjectLook().getSyncInterface().isInSync()) { return; } restartTimer(false); } }; // listen for document loading then register to it the docListener as weak addChangeListener (new ChangeListener() { public void stateChanged (ChangeEvent evt) { if (isDocumentLoaded()) { Document doc = getDocument(); doc.addDocumentListener(WeakListener.document(docListener, doc)); if (rep == null) { XMLDataObjectLook dobj = (XMLDataObjectLook) getDataObject(); Synchronizator sync = dobj.getSyncInterface(); //!!! What does this hardcoding mean??? //[DEPENDENCY] it introduces really ugly core to it's client dependencies!!! if (dobj instanceof org.netbeans.modules.xml.core.XMLDataObject) { rep = new XMLTextRepresentation(TextEditorSupport.this, sync); } else if (dobj instanceof DTDDataObject) { rep = new DTDTextRepresentation(TextEditorSupport.this, sync); } else if (dobj instanceof EntityDataObject) { rep = new EntityTextRepresentation (TextEditorSupport.this, sync); } if (rep != null) { sync.addRepresentation (rep); } } // } else { // moved to notifyClosed // XMLDataObjectLook dobj = (XMLDataObjectLook) getDataObject(); // Synchronizator sync = dobj.getSyncInterface(); // rep = null; // sync.removeRepresentation(rep); } } }); } /** * It simply calls super.notifyClosed() for all instances except * TextEditorSupport.class == this.getClass(). */ protected void notifyClosed () { super.notifyClosed(); // #15756 following code handles synchronization on text editor closing only! if (this.getClass() != TextEditorSupport.class) return; XMLDataObjectLook dobj = (XMLDataObjectLook) getDataObject(); Synchronizator sync = dobj.getSyncInterface(); Representation oldRep = rep; rep = null; if ( oldRep != null ) { // because of remove modified document sync.removeRepresentation (oldRep); } // if ( isModified() ) { // possible way to remove needless closeDocument followed by open // Task reload = reloadDocument(); // reload.waitFinished(); // } } /** */ Env getEnv () { return (Env) env; } /** */ protected XMLDataObjectLook getXMLDataObjectLook () { return getEnv().getXMLDataObjectLook(); } /* * Update presence of SaveCookie on first keystroke. */ protected boolean notifyModified () { if (getEnv().isModified()) { return true; } if (!!! super.notifyModified ()) { return false; } else { CookieManagerCookie manager = getEnv().getXMLDataObjectLook().getCookieManager(); manager.addCookie (getEnv()); return true; } } /* * Update presence of SaveCookie after save. */ protected void notifyUnmodified () { if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("Notifing unmodified"); // NOI18N super.notifyUnmodified (); CookieManagerCookie manager = getEnv().getXMLDataObjectLook().getCookieManager(); manager.removeCookie (getEnv()); } //~~~~~~~~~~~~~~~~~~~~~~~~~ I/O ENCODING HANDLING ~~~~~~~~~~~~~~~~~~~~~~~~~~~ //indicates than document has wrong encoding @see #edit private volatile boolean encodingErr = false; /** Read the file from the stream, detect right encoding. */ protected void loadFromStreamToKit (StyledDocument doc, InputStream in, EditorKit kit) throws IOException, BadLocationException { // predetect it to get optimalized XmlReader if utf-8 String enc = EncodingHelper.detectEncoding(in); if (enc == null) { enc = "UTF8"; //!!! // NOI18N } try { Reader reader = new InputStreamReader(in, enc); kit.read(reader, doc, 0); } catch (CharConversionException ex) { if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("\n!!! TextEditorSupport.loadFromStreamToKit: enc = '" + enc + "'", ex); encodingErr = true; } catch (UnsupportedEncodingException ex) { if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("\n!!! TextEditorSupport.loadFromStreamToKit: enc = '" + enc + "'", ex); encodingErr = true; } } /** Store the document in proper encoding. */ protected void saveFromKitToStream(StyledDocument doc, EditorKit kit, OutputStream out) throws IOException, BadLocationException { String enc = EncodingHelper.detectEncoding(doc); if (enc == null) { enc = "UTF8"; //!!! // NOI18N } try { if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("Saving using encoding");//, new RuntimeException (enc)); // NOI18N if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("!!! TextEditorSupport::saveFromKitToStream: enc = " + enc); //test encoding on dummy stream new OutputStreamWriter(new ByteArrayOutputStream(1), enc); if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("!!! ::saveFromKitToStream: after first test -> OK"); Writer writer = new OutputStreamWriter(out, enc); if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("!!! ::saveFromKitToStream: writer = " + writer); kit.write(writer, doc, 0, doc.getLength()); } catch (UnsupportedEncodingException ex) { //!!! just write nothing //?? save say as UTF-8 ErrorManager emgr = ErrorManager.getDefault(); IOException ioex = new IOException("Unsupported encoding " + enc); // NOI18N emgr.annotate(ioex, Util.THIS.getString ("MSG_unsupported_encoding", enc)); throw ioex; } } /* * Save document using encoding declared in XML prolog if possible otherwise * at UTF-8 (in such case it updates the prolog). */ public void saveDocument () throws IOException { if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("saveDocument()..."); // NOI18N final StyledDocument doc = getDocument(); String enc = EncodingHelper.detectEncoding(doc); if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("!!! TextEditorSupport::saveDocument: enc = " + enc); if (enc == null) { enc = "UTF8"; //!!! // NOI18N } try { //test encoding on dummy stream new OutputStreamWriter(new ByteArrayOutputStream(1), enc); super.saveDocument(); //moved from Env.save() getDataObject().setModified (false); getXMLDataObjectLook().getSyncInterface().representationChanged(Document.class); } catch (UnsupportedEncodingException ex) { // ask user what next? NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(java.text.MessageFormat.format(Util.THIS.getString("TEXT_SAVE_AS_UTF"), new Object[] {enc})); Object res = DialogDisplayer.getDefault().notify(descriptor); if (res.equals(NotifyDescriptor.YES_OPTION)) { // update prolog to new valid encoding try { final int MAX_PROLOG = 1000; int maxPrologLen = Math.min(MAX_PROLOG, doc.getLength()); final char prolog[] = doc.getText(0, maxPrologLen).toCharArray(); int prologLen = 0; // actual prolog length //parse prolog and get prolog end if (prolog[0] == '<' && prolog[1] == '?' && prolog[2] == 'x') { // look for delimitting ?> for (int i = 3; i') { prologLen = i + 1; break; } } } final int passPrologLen = prologLen; Runnable edit = new Runnable() { public void run() { try { doc.remove(0, passPrologLen + 1); // +1 it removes exclusive doc.insertString(0, " \n", null); // NOI18N } catch (BadLocationException e) { if (System.getProperty("netbeans.debug.exceptions") != null) // NOI18N e.printStackTrace(); } } }; NbDocument.runAtomic(doc, edit); super.saveDocument(); //moved from Env.save() getDataObject().setModified (false); getXMLDataObjectLook().getSyncInterface().representationChanged(Document.class); if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("Saved."); // NOI18N } catch (BadLocationException lex) { ErrorManager.getDefault().notify(lex); } } else { // NotifyDescriptor != YES_OPTION if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("Let unsaved."); // NOI18N return; } } // of catch UnsupportedEncodingException } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SYNC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** * TEXT changed -> update TREE. */ protected void syncDocument (boolean fromFocus) { if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("@@ TextEditorSupport::syncDocument: fromFocus = " + fromFocus); if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("@@ ::syncDocument: timer.isRunning = " + timer.isRunning()); if (fromFocus && !timer.isRunning()) return; if (timer.isRunning()) timer.stop(); XMLDataObjectLook sync = getXMLDataObjectLook(); if (sync != null) { // && isModified()) { sync.getSyncInterface().representationChanged(Document.class); } } /** Restart the timer which starts the parser after the specified delay. * @param onlyIfRunning Restarts the timer only if it is already running */ void restartTimer (boolean onlyIfRunning) { if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("## TextEditorSupport::restartTimer: onlyIfRunning = " + onlyIfRunning); if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("## ::restartTimer: timer.isRunning = " + timer.isRunning()); if (onlyIfRunning && !timer.isRunning()) return; int delay = settings.getAutoParsingDelay(); if (delay > 0) { timer.setInitialDelay (delay); timer.restart(); } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /* * An entry point via EditCookie. * Delegate to openDocument(). */ public final void edit () { try { openDocument(); //use sync version of call - prepare encodingErr if (encodingErr) { String pattern = Util.THIS.getString("TEXT_WRONG_ENCODING"); String msg = MessageFormat.format(pattern, new Object[] { getDataObject().getPrimaryFile().toString() /*compatibleEntry.getFile().toString()*/}); DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(msg, NotifyDescriptor.ERROR_MESSAGE)); } else { Mutex.EVENT.writeAccess (new Runnable () { public void run () { CloneableTopComponent editor = openCloneableEditor(); editor.requestActive(); } }); } } catch (IOException ex) { String pattern = Util.THIS.getString("TEXT_LOADING_ERROR"); String msg = MessageFormat.format(pattern, new Object[] { getDataObject().getPrimaryFile().toString() /*compatibleEntry.getFile().toString()*/}); DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(msg, NotifyDescriptor.ERROR_MESSAGE)); } } /* * Simply open for an cloneable editor. It at first tries to locate * existing component in allEditors then if it fails create new one * and registers it with allEditors>/code>. */ protected final CloneableEditor openCloneableEditor () { CloneableEditor ret = null; synchronized (getLock()) { String msg = messageOpening(); if (msg != null) { StatusDisplayer.getDefault().setStatusText (msg); } Enumeration en = allEditors.getComponents(); while ( en.hasMoreElements() ) { CloneableTopComponent editor = (CloneableTopComponent)en.nextElement(); if ( editor instanceof CloneableEditor ) { editor.open(); ret = (CloneableEditor) editor; } } // no opened editor, create a new one if (ret == null) { CloneableEditor editor = (CloneableEditor)createCloneableTopComponent(); // this is important -- see final createCloneableTopComponent editor.setReference (allEditors); editor.open(); ret = editor; } msg = messageOpened(); if (msg == null) { msg = ""; // NOI18N } StatusDisplayer.getDefault ().setStatusText (msg); return ret; } } /** * Creates lock object used in close and openCloneableTopComponent. * @return never null */ protected Object getLock() { if (awtLock == null) { awtLock = new java.awt.Container(); } return awtLock.getTreeLock(); } /* * @return component visualizing this support. */ protected CloneableEditor createCloneableEditor () { return new TextEditorComponent (this); } // This must call super createCloneableTopComponent because it prepare document, create cloneable editor and initialize it. See super. protected final CloneableTopComponent createCloneableTopComponent () { return super.createCloneableTopComponent(); // creates CloneableEditor (calling createCloneableEditor) } /** * Listening for peer DataObject modification and translating it in * notifyModified() or notifyUnmodified() respectively. The then manipulate * SaveCookie presence. */ private class ModificationListener implements PropertyChangeListener { public void propertyChange (PropertyChangeEvent ev) { if (ev.getPropertyName().equals (DataObject.PROP_MODIFIED)) { boolean isModif = getXMLDataObjectLook().isModified(); boolean hasSaveCook = getXMLDataObjectLook().getCookie (SaveCookie.class) != null; if (isModif == hasSaveCook) return; if (isModif) { TextEditorSupport.this.notifyModified(); } else { TextEditorSupport.this.notifyUnmodified(); } } } } /** */ public static final TextEditorSupportFactory findEditorSupportFactory (XMLDataObjectLook xmlDO, String mime) { EditorSupportFactoryCreater editorSupportFactoryCreater = (EditorSupportFactoryCreater) Lookup.getDefault().lookup (EditorSupportFactoryCreater.class); if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("TextEditorSupport.findEditorSupportFactory: editorSupportFactoryCreater = " + editorSupportFactoryCreater); TextEditorSupportFactory editorSupportFactory = null; if ( editorSupportFactoryCreater != null ) { editorSupportFactory = editorSupportFactoryCreater.createEditorSupportFactory (xmlDO, mime); } else { editorSupportFactory = new TextEditorSupportFactory (xmlDO, mime); } if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("--> editorSupportFactory = " + editorSupportFactory); return editorSupportFactory; } // // class Env // /** * */ protected static class Env extends DataEditorSupport.Env implements SaveCookie { /** Serial Version UID */ private static final long serialVersionUID=-5285524519399090028L; /** */ public Env (XMLDataObjectLook obj) { super ((DataObject)obj); } /** */ protected XMLDataObjectLook getXMLDataObjectLook () { return (XMLDataObjectLook) getDataObject (); } /** */ protected FileObject getFile () { return getDataObject ().getPrimaryFile (); } /** */ protected FileLock takeLock () throws IOException { return ((MultiDataObject)getDataObject()).getPrimaryEntry().takeLock(); } /** */ public synchronized void save () throws IOException { findTextEditorSupport().saveDocument(); } /** */ public CloneableOpenSupport findCloneableOpenSupport () { return findTextEditorSupport(); } /** */ public TextEditorSupport findTextEditorSupport () { return (TextEditorSupport) getDataObject().getCookie (EditCookie.class); } // copy pasted, do not get it public void propertyChange(PropertyChangeEvent ev) { if (DataObject.PROP_PRIMARY_FILE.equals(ev.getPropertyName())) { changeFile (); } super.propertyChange(ev); } } // end: class Env // // class TextEditorSupportFactory // /** * */ public static class TextEditorSupportFactory implements CookieSet.Factory { /** */ private WeakReference editorRef; /** */ private final XMLDataObjectLook dataObject; // used while creating the editor /** */ private final String mime; // used while creating the editor // // init // /** Create new TextEditorSupportFactory. */ public TextEditorSupportFactory (XMLDataObjectLook dobj, String mime) { this.dataObject = dobj; this.mime = mime; } /** */ protected Class[] supportedCookies () { return new Class[] { EditorCookie.class, EditorCookie.Observable.class, EditCookie.class, CloseCookie.class, PrintCookie.class, }; } /** */ public final void registerCookies (CookieSet cookieSet) { Class[] supportedCookies = supportedCookies(); for (int i = 0; i < supportedCookies.length; i++) { cookieSet.add (supportedCookies[i], this); } } /** Creates a Node.Cookie of given class. The method * may be called more than once. */ public final Node.Cookie createCookie (Class klass) { Class[] supportedCookies = supportedCookies(); for (int i = 0; i < supportedCookies.length; i++) { if ( supportedCookies[i].isAssignableFrom (klass) ) { return createEditor(); } } return null; } /** */ private final synchronized TextEditorSupport createEditor () { // atomic test and set TextEditorSupport editorSupport = null; if ( editorRef != null ) { editorSupport = (TextEditorSupport) editorRef.get(); } if ( editorSupport == null ) { editorSupport = prepareEditor (); editorRef = new WeakReference (editorSupport); } return editorSupport; } /** */ protected TextEditorSupport prepareEditor () { if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("Initializing TextEditorSupport ..."); // NOI18N return new TextEditorSupport (getDataObject(), getMIMEType()); } /** */ protected final XMLDataObjectLook getDataObject () { return dataObject; } /** */ protected final String getMIMEType () { return mime; } } // end of class TextEditorSupportFactory // // interface EditorSupportFactoryCreater // /** * */ public static interface EditorSupportFactoryCreater { /** */ public TextEditorSupportFactory createEditorSupportFactory (XMLDataObjectLook xmlDO, String mime); } // end of interface EditorSupportFactoryCreater }

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.