/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
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
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}