PageRenderTime 113ms CodeModel.GetById 84ms app.highlight 22ms RepoModel.GetById 2ms app.codeStats 0ms

/bundles/plugins-trunk/BufferLocal/src/ise/plugin/bmp/BufferLocalPlugin.java

#
Java | 642 lines | 443 code | 49 blank | 150 comment | 109 complexity | 8619de731b5644f97552a33fbfa03850 MD5 | raw file
  1package ise.plugin.bmp;
  2
  3import java.awt.event.WindowEvent;
  4import java.awt.event.WindowListener;
  5import java.io.*;
  6import java.util.*;
  7
  8import org.gjt.sp.jedit.jEdit;
  9import org.gjt.sp.jedit.Buffer;
 10import org.gjt.sp.jedit.EBMessage;
 11import org.gjt.sp.jedit.EBPlugin;
 12import org.gjt.sp.jedit.EditPane;
 13import org.gjt.sp.jedit.View;
 14
 15import org.gjt.sp.jedit.buffer.FoldHandler;
 16
 17import org.gjt.sp.jedit.msg.BufferUpdate;
 18import org.gjt.sp.jedit.msg.EditPaneUpdate;
 19import org.gjt.sp.jedit.msg.EditorExitRequested;
 20import org.gjt.sp.jedit.msg.PropertiesChanged;
 21import org.gjt.sp.jedit.msg.ViewUpdate;
 22
 23//import org.gjt.sp.util.Log;
 24
 25/**
 26 * This plugin stores buffer-local properties in a file and restores those
 27 * setting when the file is next opened. The settings are stored as a pipe
 28 * separated string:
 29 * <ul>
 30 * <li>getStringProperty("lineSeparator")        Line separator string, values n, r, rn
 31 * <li>buffer.getStringProperty(Buffer.ENCODING) Character encoding string
 32 * <li>buffer.getBooleanProperty(Buffer.GZIPPED) gzip on disk boolean, values t, f
 33 * <li>buffer.getMode().getName()                edit mode string
 34 * <li>buffer.getFoldHandler().getName()         fold mode string
 35 * <li>buffer.getStringProperty("wrap");         word wrap string
 36 * <li>buffer.getIntProperty("maxLineLength");   wrap width int
 37 * <li>buffer.getIntProperty("tabSize")          tab width int
 38 * <li>buffer.getIntProperty("indentSize")       indent width int
 39 * <li>buffer.getBooleanProperty("noTabs")       soft tabs boolean, t = soft tabs, f = hard tabs
 40 * </ul>
 41 * <p>
 42 * example:n|ISO-8859-1|f|java|indent|none|76|3|3|t
 43 *         n|ISO-8859-1|f|    |      |none|0|4|4|t
 44 * <p>
 45 * TODO: need to check how this works with files loaded with the ftp plugin
 46 * <br>
 47 * DID: seems to work okay with ftp, need to test some more
 48 * <p>
 49 * Jan 5, 2004, per request from Slava: removed
 50 * persistence of line separator and encoding. Kept the string format as above,
 51 * but implementation now does not actually use line separator and encoding
 52 * settings.
 53 *
 54 * This class implements WindowListener, then is attached to each view so that
 55 * the auto-close timer only applies when the window is actually active.  If
 56 * the window is deactivated or iconified, the timer is stopped.
 57 *
 58 * @author    Dale Anson, danson@germane-software.com
 59 * @version   $Revision: 18720 $
 60 * @since     Oct 1, 2003
 61 */
 62public class BufferLocalPlugin extends EBPlugin implements WindowListener {
 63
 64    // runs once every 10 minutes at a low priority to clean up the map
 65    Thread janitor = createJanitorThread();
 66
 67    private Thread createJanitorThread() {
 68        return new Thread() {
 69                   public void run() {
 70                       setPriority( Thread.MIN_PRIORITY );
 71                       while ( true ) {
 72                           if ( canClean && map.size() > 0 ) {
 73                               synchronized ( map ) {
 74                                   try {
 75                                       // do 2 loops to avoid ConcurrentModificationExceptions
 76                                       List to_remove = new ArrayList();
 77                                       Iterator it = map.keySet().iterator();
 78                                       while ( it.hasNext() ) {
 79                                           String filename = ( String ) it.next();
 80                                           File f = new File( filename );
 81                                           if ( !f.exists() ) {
 82                                               to_remove.add( filename );
 83                                           }
 84                                       }
 85                                       it = to_remove.iterator();
 86                                       while ( it.hasNext() ) {
 87                                           map.remove( it.next() );
 88                                       }
 89                                   }
 90                                   catch ( Exception e ) {     // NOPMD
 91                                       // ignored
 92                                   }
 93                               }
 94                           }
 95                           try {
 96                               sleep( ( long ) TEN_MINUTES );
 97                           }
 98                           catch ( InterruptedException e ) {
 99                               // ignored
100                           }
101                       }
102                   }
103               };
104    }
105
106    // runs once every staleTime minutes at a low priority to auto-close stale buffers
107    Thread bufferCleaner = createBufferCleanerThread();
108
109    private Thread createBufferCleanerThread() {
110        return new Thread() {
111                   public void run() {
112                       setPriority( Thread.MIN_PRIORITY );
113                       while ( removeStale ) {
114                           try {
115                               if ( canClose ) {
116                                   closeFiles();
117                               }
118                               sleep( ( long ) staleTime );
119                           }
120                           catch ( InterruptedException e ) {
121                               // ignored
122                           }
123                       }
124                   }
125               };
126    }
127
128    private int ONE_MINUTE = 1000 * 60;
129    private int TEN_MINUTES = ONE_MINUTE * 10;
130
131    private int staleTime = 30 * ONE_MINUTE;
132    private boolean removeStale = false;
133
134    // storage for the properties, key is filename as a String,
135    // value is the property settings String, see above
136    private Properties map = new Properties();
137
138    // temporary storage for properties. Properties are stored here, then moved
139    // to permanent storage when they actually change.
140    private Properties tempMap = new Properties();
141
142    // storage for open buffers, key is filename as a String,
143    // value is a BufferReference object
144    private HashMap<String, BufferReference> openBuffers = new HashMap<String, BufferReference>();
145
146    // control for janitor thread.
147    private boolean canClean;
148
149    // control for buffer cleaner thread
150    private boolean canClose = true;
151
152    private Date pausedAt = null;
153
154    public static String NAME = "bufferlocal";
155
156    private File configFile = null;
157
158    /**
159     * Load the stored buffer local properties. The properties are stored in a
160     * file named .bufferlocalplugin.cfg in either the jEdit settings directory
161     * (if writable) otherwise, in $user.home.
162     */
163    public void start() {
164        // load settings from jEdit properties
165        loadProperties();
166
167        // load configuration setings
168        // Previously, this file was stored either in the jEdit settings directory
169        // or user.home.  Now it is stored in plugin home.  For backward compatibility,
170        // first check plugin home.  If the config file is there, assume it has already
171        // been migrated.  If not, check settings directory and user home and copy it
172        // to plugin home, then use it.  Once done, delete the old file.
173        try {
174            File homeDir = jEdit.getPlugin( "ise.plugin.bmp.BufferLocalPlugin" ).getPluginHome();
175            homeDir.mkdir();
176            configFile = new File( homeDir, ".bufferlocalplugin.cfg" );
177            if ( configFile.exists() ) {
178                BufferedInputStream in = new BufferedInputStream( new FileInputStream( configFile ) );
179                map.load( in );
180                in.close();
181            }
182            else {
183                String oldDir = jEdit.getSettingsDirectory();
184                if ( oldDir == null ) {
185                    oldDir = System.getProperty( "user.home" );
186                }
187                configFile = new File( oldDir, ".bufferlocalplugin.cfg" );
188                if ( configFile.exists() ) {
189                    BufferedInputStream in = new BufferedInputStream( new FileInputStream( configFile ) );
190                    map.load( in );
191                    in.close();
192                    // delete the old file and write out the new file
193                    configFile.delete();
194                    configFile = new File( homeDir, ".bufferlocalplugin.cfg" );
195                    synchronized ( map ) {
196                        BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream( configFile ) );
197                        map.store( out, "Machine generated for BufferLocalPlugin, DO NOT EDIT!" );
198                        out.flush();
199                        out.close();
200                    }
201                }
202            }
203        }
204        catch (Exception e) {       // NOPMD
205            // ignored
206        }
207
208        // TODO: attach to open Views
209        for ( View view : jEdit.getViews() ) {
210            view.addWindowListener( this );
211        }
212
213        // prep for currently open buffers
214        initOpenBuffers();
215
216        // start janitor and cleaner threads
217        canClean = true;
218        restartThreads();
219    }
220
221    private void restartThreads() {
222        if ( janitor.isAlive() ) {
223            janitor.interrupt();
224        }
225        janitor = createJanitorThread();
226        janitor.start();
227        if ( bufferCleaner.isAlive() ) {
228            bufferCleaner.interrupt();
229        }
230        bufferCleaner = createBufferCleanerThread();
231        bufferCleaner.start();
232    }
233
234    /**
235     * Save the buffer local properties to disk.
236     *
237     * @see   start
238     */
239    public void stop() {
240        canClean = false;
241        janitor.interrupt();
242        bufferCleaner.interrupt();
243        if ( configFile == null || !configFile.exists() ) {
244            File homeDir = jEdit.getPlugin( "ise.plugin.bmp.BufferLocalPlugin" ).getPluginHome();
245            homeDir.mkdir();
246            configFile = new File( homeDir, ".bufferlocalplugin.cfg" );
247        }
248        try {
249            synchronized ( map ) {
250                BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream( configFile ) );
251                map.store( out, "Machine generated for BufferLocalPlugin, DO NOT EDIT!" );
252                out.flush();
253                out.close();
254            }
255        }
256        catch ( Exception e ) {     // NOPMD
257            // ignored
258        }
259
260        // detach from open views
261        for ( View view : jEdit.getViews() ) {
262            view.removeWindowListener( this );
263        }
264    }
265
266    /**
267     * Check for BufferUpdate messages. Save properties on CLOSED, restore
268     * properties on LOADED.
269     *
270     * @param message
271     */
272    public void handleMessage( EBMessage message ) {
273        if ( message instanceof BufferUpdate ) {
274            BufferUpdate bu = ( BufferUpdate ) message;
275            Object what = bu.getWhat();
276            Buffer buffer = bu.getBuffer();
277            if ( buffer == null ) {
278                return ;
279            }
280            String file = buffer.getPath();
281            if ( BufferUpdate.LOADED.equals( what ) || BufferUpdate.SAVED.equals( what ) ) {
282                String props = map.getProperty( file );
283                if ( props != null ) {
284                    // parse the stored properties
285                    String[] tokens = props.split( "[\\|]" );
286                    String ls = tokens[ 0 ];
287                    String enc = tokens[ 1 ];
288                    String gz = tokens[ 2 ];
289                    String em = tokens[ 3 ];
290                    String fm = tokens[ 4 ];
291                    String wm = tokens[ 5 ];
292                    String ll = tokens[ 6 ];
293                    String tw = tokens[ 7 ];
294                    String iw = tokens[ 8 ];
295                    String tabs = tokens[ 9 ];
296
297                    // apply the stored properties to the buffer
298                    /// see comments above, don't need this right now
299                    /// 13 Dec 2005, more comments, looks like there is a use case for this
300                    /// stuff after all
301                    if ( "n".equals( ls ) ) {
302                        ls = "\n";
303                    }
304                    else if ( "r".equals( ls ) ) {
305                        ls = "\r";
306                    }
307                    else {
308                        ls = "\r\n";
309                    }
310                    buffer.setStringProperty( "lineSeparator", ls );
311                    buffer.setStringProperty( Buffer.ENCODING, enc );
312                    ///
313
314                    if ( gz != null && gz.length() > 0 )
315                        buffer.setBooleanProperty( Buffer.GZIPPED, "t".equals( gz ) ? true : false );
316                    if ( fm != null && fm.length() > 0 && FoldHandler.getFoldHandler( fm ) != null )
317                        buffer.setFoldHandler( FoldHandler.getFoldHandler( fm ) );
318                    if ( wm != null && wm.length() > 0 )
319                        buffer.setStringProperty( "wrap", wm );
320                    if ( ll != null && ll.length() > 0 )
321                        buffer.setIntegerProperty( "maxLineLength", Integer.parseInt( ll ) );
322                    if ( tw != null && tw.length() > 0 )
323                        buffer.setIntegerProperty( "tabSize", Integer.parseInt( tw ) );
324                    if ( iw != null && iw.length() > 0 )
325                        buffer.setIntegerProperty( "indentSize", Integer.parseInt( iw ) );
326                    if ( tabs != null && tabs.length() > 0 )
327                        buffer.setBooleanProperty( "noTabs", "t".equals( tabs ) ? true : false );
328                    if ( em != null && em.length() > 0 )
329                        buffer.setMode( em );
330                }
331                else {
332                    // on load, if we don't already have properties stored for this file,
333                    // stash the string to check against when the file is closed.
334                    tempMap.setProperty( file, getBufferLocalString( buffer ) );
335                }
336                View view = bu.getView();
337                if ( view == null ) {
338                    view = jEdit.getActiveView();
339                }
340                openBuffers.put( buffer.getPath(), new BufferReference( view, buffer ) );
341            }
342            else if ( BufferUpdate.CLOSED.equals( what ) || BufferUpdate.PROPERTIES_CHANGED.equals( what ) ) {
343                String bufferLocalString = getBufferLocalString(buffer);
344                map.setProperty( file, bufferLocalString );
345                if ( BufferUpdate.CLOSED.equals( what ) ) {
346                    openBuffers.remove( buffer.getPath() );
347                }
348            }
349        }
350        else if ( message instanceof EditorExitRequested ) {
351            // jEdit may be shutting down, so update the map for any buffers still open.
352            // Oddly enough, jEdit doesn't send CLOSED messages as it closes buffers
353            // during shutdown. Or maybe it does, but this plugin is unloaded before
354            // getting those messages.
355            Buffer[] buffers = jEdit.getBuffers();
356            String file;
357            String props;
358            String tempProps;
359            for ( int i = 0; i < buffers.length; i++ ) {
360                file = buffers[ i ].getPath();
361                // only save if changed, no need to save if not. Doing it this way
362                // rather than on PROPERTY_CHANGED as jEdit sends lots of
363                // PROPERTY_CHANGED messages even though the properties really
364                // haven't changed
365                props = getBufferLocalString( buffers[ i ] );
366                tempProps = tempMap.getProperty( file );
367                if ( tempProps == null ) {
368                    continue;
369                }
370                if ( !props.equals( tempProps ) ) {
371                    map.setProperty( file, props );
372                }
373            }
374        }
375        else if ( message instanceof EditPaneUpdate ) {
376            // populate the openBuffers list
377            EditPaneUpdate epu = ( EditPaneUpdate ) message;
378            Object what = epu.getWhat();
379            if ( EditPaneUpdate.BUFFER_CHANGED.equals( what ) ) {
380                View view = epu.getEditPane().getView();
381                Buffer buffer = epu.getEditPane().getBuffer();
382                openBuffers.put( buffer.getPath(), new BufferReference( view, buffer ) );
383            }
384        }
385        else if ( message instanceof ViewUpdate ) {
386            ViewUpdate vu = ( ViewUpdate ) message;
387            if ( ViewUpdate.CREATED.equals( vu.getWhat() ) ) {
388                initView( vu.getView() );
389                vu.getView().addWindowListener( this );
390            }
391            else if ( ViewUpdate.CLOSED.equals( vu.getWhat() ) ) {
392                vu.getView().removeWindowListener( this );
393            }
394        }
395        else if ( message instanceof PropertiesChanged ) {
396            loadProperties();
397        }
398    }
399
400    /** Closes stale files right now.  */
401    public void closeFiles() {
402        if ( openBuffers.size() > 0 ) {
403            synchronized ( openBuffers ) {
404                try {
405                    // do 2 loops to avoid ConcurrentModificationExceptions
406                    Calendar stale_time = Calendar.getInstance();
407                    stale_time.add( Calendar.MILLISECOND, -staleTime );
408                    Iterator it = openBuffers.keySet().iterator();
409                    List to_close = new ArrayList();
410                    while ( it.hasNext() ) {
411                        String path = ( String ) it.next();
412                        BufferReference br = ( BufferReference ) openBuffers.get( path );
413                        Calendar viewed = br.getViewed();
414                        View view = br.getView();
415                        if ( view == null ) {
416                            view = jEdit.getActiveView();
417                        }
418                        Buffer buffer = br.getBuffer();
419                        if ( buffer.equals( view.getEditPane().getBuffer() ) ) {
420                            // don't close the current buffer, even though it may
421                            // been open for more than stale time
422                            continue;
423                        }
424                        if ( buffer.isDirty() ) {
425                            continue;   // don't auto-close a dirty buffer
426                        }
427                        if ( viewed.before( stale_time ) ) {
428                            to_close.add( br );
429                        }
430                    }
431                    it = to_close.iterator();
432                    while ( it.hasNext() ) {
433                        BufferReference br = ( BufferReference ) it.next();
434                        View view = br.getView();
435                        if ( view == null ) {
436                            view = jEdit.getActiveView();
437                        }
438                        Buffer buffer = br.getBuffer();
439                        if ( jEdit.closeBuffer( view, buffer ) ) {
440                            openBuffers.remove( buffer.getPath() );
441                        }
442                    }
443                }
444                catch ( Exception e ) {
445                    e.printStackTrace();
446                    // ignored
447                }
448            }
449        }
450    }
451
452    /**
453     * @param buffer  the buffer to get properties for
454     * @return        a string representing the buffer-local properties. See
455     *      explanation and example of string at the top of this file.
456     */
457    private String getBufferLocalString( Buffer buffer ) {
458        // get the properties
459        String ls = buffer.getStringProperty( "lineSeparator" );
460        String enc = buffer.getStringProperty( Buffer.ENCODING );
461        boolean gz = buffer.getBooleanProperty( Buffer.GZIPPED );
462        String em;
463        if ( buffer.getMode() != null )
464            em = buffer.getMode().getName();
465        else
466            em = "";
467        String fm = buffer.getFoldHandler() == null ? "" : buffer.getFoldHandler().getName();
468        String wm = buffer.getStringProperty( "wrap" );
469        int ll = buffer.getIntegerProperty( "maxLineLength", 0 );
470        int tw = buffer.getIntegerProperty( "tabSize", 3 );
471        int iw = buffer.getIntegerProperty( "indentSize", 3 );
472        boolean tabs = buffer.getBooleanProperty( "noTabs" );
473
474        // build the string
475        StringBuffer prop = new StringBuffer();
476        if ( "\n".equals( ls ) ) {
477            prop.append( "n|" );
478        }
479        else if ( "\r".equals( ls ) ) {
480            prop.append( "r|" );
481        }
482        else {
483            prop.append( "rn|" );
484        }
485        prop.append( enc ).append( '|' );
486        prop.append( gz ? "t|" : "f|" );
487        prop.append( em ).append( '|' );
488        prop.append( fm ).append( '|' );
489        prop.append( wm ).append( '|' );
490        prop.append( String.valueOf( ll ) ).append( '|' );
491        prop.append( String.valueOf( tw ) ).append( '|' );
492        prop.append( String.valueOf( iw ) ).append( '|' );
493        prop.append( tabs ? 't' : 'f' );
494
495        return prop.toString();
496    }
497
498    /**
499     * Reads the properties for this plugin from the jEdit properties. Currently,
500     * there are 2 properties, "bufferlocal.staleTime", which is the number of
501     * minutes that a file can remain open without being used before it will be
502     * closed, and "bufferlocal.removeStale" which is a boolean to decide if
503     * stale files should be closed after the staleTime has been reached.
504     */
505    private void loadProperties() {
506        int newStaleTime = jEdit.getIntegerProperty( NAME + ".staleTime", staleTime ) * ONE_MINUTE;
507        boolean newRemoveStale = jEdit.getBooleanProperty( NAME + ".removeStale", removeStale );
508        if ( newStaleTime != staleTime || newRemoveStale != removeStale ) {
509            staleTime = newStaleTime;
510            removeStale = newRemoveStale;
511            restartThreads();
512        }
513    }
514
515    /**
516     * Gets a list of the currently open buffers and populates openBuffers.
517     */
518    private void initOpenBuffers() {
519        for ( View view : jEdit.getViews() ) {
520            initView( view );
521        }
522    }
523
524    private void initView( View view ) {
525        for ( EditPane editPane : view.getEditPanes() ) {
526            for ( Buffer buffer : editPane.getBufferSet().getAllBuffers() ) {
527                BufferReference br = new BufferReference( view, buffer );
528                openBuffers.put( buffer.getPath(), br );
529            }
530        }
531    }
532
533
534    /**
535     * A data object to track buffers
536     * @version   $Revision: 18720 $
537     */
538    public class BufferReference {
539        private View view;
540        private Buffer buffer;
541        private Calendar viewed;
542
543        /**
544         * Constructor for BufferReference
545         *
546         * @param view
547         * @param buffer
548         */
549        public BufferReference( View view, Buffer buffer ) {
550            this.view = view;
551            this.buffer = buffer;
552            viewed = Calendar.getInstance();
553        }
554
555        /** Sets the viewed attribute of the BufferReference object */
556        public void setViewed() {
557            viewed = Calendar.getInstance();
558        }
559
560        /**
561         * Gets the view attribute of the BufferReference object
562         *
563         * @return   The view value
564         */
565        public View getView() {
566            return view;
567        }
568
569        /**
570         * Gets the buffer attribute of the BufferReference object
571         *
572         * @return   The buffer value
573         */
574        public Buffer getBuffer() {
575            return buffer;
576        }
577
578        /**
579         * Gets the viewed attribute of the BufferReference object
580         *
581         * @return   The viewed value
582         */
583        public Calendar getViewed() {
584            return viewed;
585        }
586
587        /**
588         * @return   Description of the Returned Value
589         */
590        public String toString() {
591            StringBuffer sb = new StringBuffer( 50 );
592            sb.append( "BufferReference[" );
593            sb.append( buffer.getPath() ).append( ',' );
594            sb.append( viewed.getTime().toString() ).append( ']' );
595            return sb.toString();
596        }
597    }
598
599    private void adjustForPause() {
600        if ( jEdit.getBooleanProperty( "bufferlocal.whileActive" ) && pausedAt != null ) {
601            Date now = new Date();
602            long pausedFor = now.getTime() - pausedAt.getTime();
603            pausedAt = null;
604            for ( BufferReference br : openBuffers.values() ) {
605                br.getViewed().add( Calendar.MILLISECOND, ( int ) pausedFor );
606            }
607        }
608    }
609
610    public void windowActivated( WindowEvent e ) {
611        adjustForPause();
612        canClose = true;
613    }
614    public void windowClosed( WindowEvent e ) {
615        canClose = false;
616        pausedAt = new Date();
617    }
618    public void windowDeactivated( WindowEvent e ) {
619        canClose = false;
620        pausedAt = new Date();
621    }
622    public void windowDeiconified( WindowEvent e ) {
623        adjustForPause();
624        canClose = true;
625    }
626    public void windowIconified( WindowEvent e ) {
627        canClose = false;
628        pausedAt = new Date();
629    }
630    public void windowGainedFocus( WindowEvent e ) {
631        adjustForPause();
632        canClose = true;
633    }
634    public void windowLostFocus( WindowEvent e ) {
635        canClose = false;
636        pausedAt = new Date();
637    }
638    public void windowClosing( WindowEvent e ) {}
639    public void windowOpened( WindowEvent e ) {}
640    public void windowStateChanged( WindowEvent e ) {}
641
642}