Source code: org/gjt/sp/jedit/io/VFS.java
1 /*
2 * VFS.java - Virtual filesystem implementation
3 * :tabSize=8:indentSize=8:noTabs=false:
4 * :folding=explicit:collapseFolds=1:
5 *
6 * Copyright (C) 2000, 2003 Slava Pestov
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 */
22
23 package org.gjt.sp.jedit.io;
24
25 //{{{ Imports
26 import gnu.regexp.*;
27 import java.awt.Color;
28 import java.awt.Component;
29 import java.io.*;
30 import java.util.*;
31 import org.gjt.sp.jedit.buffer.BufferIORequest;
32 import org.gjt.sp.jedit.msg.PropertiesChanged;
33 import org.gjt.sp.jedit.*;
34 import org.gjt.sp.util.Log;
35 //}}}
36
37 /**
38 * A virtual filesystem implementation.<p>
39 *
40 * Plugins can provide virtual file systems by defining entries in their
41 * <code>services.xml</code> files like so:
42 *
43 * <pre><SERVICE CLASS="org.gjt.sp.jedit.io.VFS" NAME="<i>name</i>">
44 * new <i>MyVFS</i>();
45 *</SERVICE></pre>
46 *
47 * URLs of the form <code><i>name</i>:<i>path</i></code> will then be handled
48 * by the VFS named <code><i>name</i></code>.<p>
49 *
50 * See {@link org.gjt.sp.jedit.ServiceManager} for details.<p>
51 *
52 * <h3>Session objects:</h3>
53 *
54 * A session is used to persist things like login information, any network
55 * sockets, etc. File system implementations that do not need this kind of
56 * persistence return a dummy object as a session.<p>
57 *
58 * Methods whose names are prefixed with "_" expect to be given a
59 * previously-obtained session object. A session must be obtained from the AWT
60 * thread in one of two ways:
61 *
62 * <ul>
63 * <li>{@link #createVFSSession(String,Component)}</li>
64 * <li>{@link #showBrowseDialog(Object[],Component)}</li>
65 * </ul>
66 *
67 * When done, the session must be disposed of using
68 * {@link #_endVFSSession(Object,Component)}.<p>
69 *
70 * <h3>Thread safety:</h3>
71 *
72 * The following methods cannot be called from an I/O thread:
73 *
74 * <ul>
75 * <li>{@link #createVFSSession(String,Component)}</li>
76 * <li>{@link #insert(View,Buffer,String)}</li>
77 * <li>{@link #load(View,Buffer,String)}</li>
78 * <li>{@link #save(View,Buffer,String)}</li>
79 * <li>{@link #showBrowseDialog(Object[],Component)}</li>
80 * </ul>
81 *
82 * All remaining methods are required to be thread-safe in subclasses.
83 *
84 * <h3>Implementing a VFS</h3>
85 *
86 * You can override as many or as few methods as you want. Make sure
87 * {@link #getCapabilities()} returns a value reflecting the functionality
88 * implemented by your VFS.
89 *
90 * @see VFSManager#getVFSForPath(String)
91 * @see VFSManager#getVFSForProtocol(String)
92 *
93 * @author Slava Pestov
94 * @author $Id: VFS.java,v 1.39 2003/09/08 01:24:11 spestov Exp $
95 */
96 public abstract class VFS
97 {
98 //{{{ Capabilities
99
100 /**
101 * Read capability.
102 * @since jEdit 2.6pre2
103 */
104 public static final int READ_CAP = 1 << 0;
105
106 /**
107 * Write capability.
108 * @since jEdit 2.6pre2
109 */
110 public static final int WRITE_CAP = 1 << 1;
111
112 /**
113 * @deprecated Do not define this capability.<p>
114 *
115 * This was the official API for adding items to a file
116 * system browser's <b>Plugins</b> menu in jEdit 4.1 and earlier. In
117 * jEdit 4.2, there is a different way of doing this, you must provide
118 * a <code>browser.actions.xml</code> file in your plugin JAR, and
119 * define <code>plugin.<i>class</i>.browser-menu-item</code>
120 * or <code>plugin.<i>class</i>.browser-menu</code> properties.
121 * See {@link org.gjt.sp.jedit.EditPlugin} for details.
122 */
123 public static final int BROWSE_CAP = 1 << 2;
124
125 /**
126 * Delete file capability.
127 * @since jEdit 2.6pre2
128 */
129 public static final int DELETE_CAP = 1 << 3;
130
131 /**
132 * Rename file capability.
133 * @since jEdit 2.6pre2
134 */
135 public static final int RENAME_CAP = 1 << 4;
136
137 /**
138 * Make directory capability.
139 * @since jEdit 2.6pre2
140 */
141 public static final int MKDIR_CAP = 1 << 5;
142
143 /**
144 * Low latency capability. If this is not set, then a confirm dialog
145 * will be shown before doing a directory search in this VFS.
146 * @since jEdit 4.1pre1
147 */
148 public static final int LOW_LATENCY_CAP = 1 << 6;
149
150 /**
151 * Case insensitive file system capability.
152 * @since jEdit 4.1pre1
153 */
154 public static final int CASE_INSENSITIVE_CAP = 1 << 7;
155
156 //}}}
157
158 //{{{ Extended attributes
159 /**
160 * File type.
161 * @since jEdit 4.2pre1
162 */
163 public static final String EA_TYPE = "type";
164
165 /**
166 * File status (read only, read write, etc).
167 * @since jEdit 4.2pre1
168 */
169 public static final String EA_STATUS = "status";
170
171 /**
172 * File size.
173 * @since jEdit 4.2pre1
174 */
175 public static final String EA_SIZE = "size";
176
177 /**
178 * File last modified date.
179 * @since jEdit 4.2pre1
180 */
181 public static final String EA_MODIFIED = "modified";
182 //}}}
183
184 //{{{ VFS constructor
185 /**
186 * @deprecated Use the form where the constructor takes a capability
187 * list.
188 */
189 public VFS(String name)
190 {
191 this(name,0);
192 } //}}}
193
194 //{{{ VFS constructor
195 /**
196 * Creates a new virtual filesystem.
197 * @param name The name
198 * @param caps The capabilities
199 */
200 public VFS(String name, int caps)
201 {
202 this.name = name;
203 this.caps = caps;
204 // reasonable defaults (?)
205 this.extAttrs = new String[] { EA_SIZE, EA_TYPE };
206 } //}}}
207
208 //{{{ VFS constructor
209 /**
210 * Creates a new virtual filesystem.
211 * @param name The name
212 * @param caps The capabilities
213 * @param extAttrs The extended attributes
214 * @since jEdit 4.2pre1
215 */
216 public VFS(String name, int caps, String[] extAttrs)
217 {
218 this.name = name;
219 this.caps = caps;
220 this.extAttrs = extAttrs;
221 } //}}}
222
223 //{{{ getName() method
224 /**
225 * Returns this VFS's name. The name is used to obtain the
226 * label stored in the <code>vfs.<i>name</i>.label</code>
227 * property.
228 */
229 public String getName()
230 {
231 return name;
232 } //}}}
233
234 //{{{ getCapabilities() method
235 /**
236 * Returns the capabilities of this VFS.
237 * @since jEdit 2.6pre2
238 */
239 public int getCapabilities()
240 {
241 return caps;
242 } //}}}
243
244 //{{{ getExtendedAttributes() method
245 /**
246 * Returns the extended attributes supported by this VFS.
247 * @since jEdit 4.2pre1
248 */
249 public String[] getExtendedAttributes()
250 {
251 return extAttrs;
252 } //}}}
253
254 //{{{ showBrowseDialog() method
255 /**
256 * Displays a dialog box that should set up a session and return
257 * the initial URL to browse.
258 * @param session Where the VFS session will be stored
259 * @param comp The component that will parent error dialog boxes
260 * @return The URL
261 * @since jEdit 2.7pre1
262 */
263 public String showBrowseDialog(Object[] session, Component comp)
264 {
265 return null;
266 } //}}}
267
268 //{{{ getFileName() method
269 /**
270 * Returns the file name component of the specified path.
271 * @param path The path
272 * @since jEdit 3.1pre4
273 */
274 public String getFileName(String path)
275 {
276 if(path.equals("/"))
277 return path;
278
279 if(path.endsWith("/") || path.endsWith(File.separator))
280 path = path.substring(0,path.length() - 1);
281
282 int index = Math.max(path.lastIndexOf('/'),
283 path.lastIndexOf(File.separatorChar));
284 if(index == -1)
285 index = path.indexOf(':');
286
287 // don't want getFileName("roots:") to return ""
288 if(index == -1 || index == path.length() - 1)
289 return path;
290
291 return path.substring(index + 1);
292 } //}}}
293
294 //{{{ getParentOfPath() method
295 /**
296 * Returns the parent of the specified path. This must be
297 * overridden to return a non-null value for browsing of this
298 * filesystem to work.
299 * @param path The path
300 * @since jEdit 2.6pre5
301 */
302 public String getParentOfPath(String path)
303 {
304 // ignore last character of path to properly handle
305 // paths like /foo/bar/
306 int count = Math.max(0,path.length() - 2);
307 int index = path.lastIndexOf(File.separatorChar,count);
308 if(index == -1)
309 index = path.lastIndexOf('/',count);
310 if(index == -1)
311 {
312 // this ensures that getFileParent("protocol:"), for
313 // example, is "protocol:" and not "".
314 index = path.lastIndexOf(':');
315 }
316
317 return path.substring(0,index + 1);
318 } //}}}
319
320 //{{{ constructPath() method
321 /**
322 * Constructs a path from the specified directory and
323 * file name component. This must be overridden to return a
324 * non-null value, otherwise browsing this filesystem will
325 * not work.<p>
326 *
327 * Unless you are writing a VFS, this method should not be called
328 * directly. To ensure correct behavior, you <b>must</b> call
329 * {@link org.gjt.sp.jedit.MiscUtilities#constructPath(String,String)}
330 * instead.
331 *
332 * @param parent The parent directory
333 * @param path The path
334 * @since jEdit 2.6pre2
335 */
336 public String constructPath(String parent, String path)
337 {
338 return parent + path;
339 } //}}}
340
341 //{{{ getFileSeparator() method
342 /**
343 * Returns the file separator used by this VFS.
344 * @since jEdit 2.6pre9
345 */
346 public char getFileSeparator()
347 {
348 return '/';
349 } //}}}
350
351 //{{{ getTwoStageSaveName() method
352 /**
353 * Returns a temporary file name based on the given path.
354 *
355 * By default jEdit first saves a file to <code>#<i>name</i>#save#</code>
356 * and then renames it to the original file. However some virtual file
357 * systems might not support the <code>#</code> character in filenames,
358 * so this method permits the VFS to override this behavior.
359 *
360 * @param path The path name
361 * @since jEdit 4.1pre7
362 */
363 public String getTwoStageSaveName(String path)
364 {
365 return MiscUtilities.constructPath(getParentOfPath(path),
366 '#' + getFileName(path) + "#save#");
367 } //}}}
368
369 //{{{ reloadDirectory() method
370 /**
371 * Called before a directory is reloaded by the file system browser.
372 * Can be used to flush a cache, etc.
373 * @since jEdit 4.0pre3
374 */
375 public void reloadDirectory(String path) {} //}}}
376
377 //{{{ createVFSSession() method
378 /**
379 * Creates a VFS session. This method is called from the AWT thread,
380 * so it should not do any I/O. It could, however, prompt for
381 * a login name and password, for example.
382 * @param path The path in question
383 * @param comp The component that will parent any dialog boxes shown
384 * @return The session
385 * @since jEdit 2.6pre3
386 */
387 public Object createVFSSession(String path, Component comp)
388 {
389 return new Object();
390 } //}}}
391
392 //{{{ load() method
393 /**
394 * Loads the specified buffer. The default implementation posts
395 * an I/O request to the I/O thread.
396 * @param view The view
397 * @param buffer The buffer
398 * @param path The path
399 */
400 public boolean load(View view, Buffer buffer, String path)
401 {
402 if((getCapabilities() & READ_CAP) == 0)
403 {
404 VFSManager.error(view,path,"vfs.not-supported.load",new String[] { name });
405 return false;
406 }
407
408 Object session = createVFSSession(path,view);
409 if(session == null)
410 return false;
411
412 if((getCapabilities() & WRITE_CAP) == 0)
413 buffer.setReadOnly(true);
414
415 BufferIORequest request = new BufferIORequest(
416 BufferIORequest.LOAD,view,buffer,session,this,path);
417 if(buffer.isTemporary())
418 // this makes HyperSearch much faster
419 request.run();
420 else
421 VFSManager.runInWorkThread(request);
422
423 return true;
424 } //}}}
425
426 //{{{ save() method
427 /**
428 * Saves the specifies buffer. The default implementation posts
429 * an I/O request to the I/O thread.
430 * @param view The view
431 * @param buffer The buffer
432 * @param path The path
433 */
434 public boolean save(View view, Buffer buffer, String path)
435 {
436 if((getCapabilities() & WRITE_CAP) == 0)
437 {
438 VFSManager.error(view,path,"vfs.not-supported.save",new String[] { name });
439 return false;
440 }
441
442 Object session = createVFSSession(path,view);
443 if(session == null)
444 return false;
445
446 /* When doing a 'save as', the path to save to (path)
447 * will not be the same as the buffer's previous path
448 * (buffer.getPath()). In that case, we want to create
449 * a backup of the new path, even if the old path was
450 * backed up as well (BACKED_UP property set) */
451 if(!path.equals(buffer.getPath()))
452 buffer.unsetProperty(Buffer.BACKED_UP);
453
454 VFSManager.runInWorkThread(new BufferIORequest(
455 BufferIORequest.SAVE,view,buffer,session,this,path));
456 return true;
457 } //}}}
458
459 //{{{ insert() method
460 /**
461 * Inserts a file into the specified buffer. The default implementation
462 * posts an I/O request to the I/O thread.
463 * @param view The view
464 * @param buffer The buffer
465 * @param path The path
466 */
467 public boolean insert(View view, Buffer buffer, String path)
468 {
469 if((getCapabilities() & READ_CAP) == 0)
470 {
471 VFSManager.error(view,path,"vfs.not-supported.load",new String[] { name });
472 return false;
473 }
474
475 Object session = createVFSSession(path,view);
476 if(session == null)
477 return false;
478
479 VFSManager.runInWorkThread(new BufferIORequest(
480 BufferIORequest.INSERT,view,buffer,session,this,path));
481 return true;
482 } //}}}
483
484 // A method name that starts with _ requires a session object
485
486 //{{{ _canonPath() method
487 /**
488 * Returns the canonical form of the specified path name. For example,
489 * <code>~</code> might be expanded to the user's home directory.
490 * @param session The session
491 * @param path The path
492 * @param comp The component that will parent error dialog boxes
493 * @exception IOException if an I/O error occurred
494 * @since jEdit 4.0pre2
495 */
496 public String _canonPath(Object session, String path, Component comp)
497 throws IOException
498 {
499 return path;
500 } //}}}
501
502 //{{{ _listDirectory() method
503 /**
504 * A convinience method that matches file names against globs, and can
505 * optionally list the directory recursively.
506 * @param session The session
507 * @param directory The directory. Note that this must be a full
508 * URL, including the host name, path name, and so on. The
509 * username and password (if needed by the VFS) is obtained from the
510 * session instance.
511 * @param glob Only file names matching this glob will be returned
512 * @param recursive If true, subdirectories will also be listed.
513 * @param comp The component that will parent error dialog boxes
514 * @exception IOException if an I/O error occurred
515 * @since jEdit 4.1pre1
516 */
517 public String[] _listDirectory(Object session, String directory,
518 String glob, boolean recursive, Component comp)
519 throws IOException
520 {
521 Log.log(Log.DEBUG,this,"Listing " + directory);
522 ArrayList files = new ArrayList(100);
523
524 RE filter;
525 try
526 {
527 filter = new RE(MiscUtilities.globToRE(glob),
528 RE.REG_ICASE);
529 }
530 catch(REException e)
531 {
532 Log.log(Log.ERROR,this,e);
533 return null;
534 }
535
536 _listDirectory(session,new ArrayList(),files,directory,filter,
537 recursive,comp);
538
539 String[] retVal = (String[])files.toArray(new String[files.size()]);
540
541 Arrays.sort(retVal,new MiscUtilities.StringICaseCompare());
542
543 return retVal;
544 } //}}}
545
546 //{{{ _listDirectory() method
547 /**
548 * Lists the specified directory.
549 * @param session The session
550 * @param directory The directory. Note that this must be a full
551 * URL, including the host name, path name, and so on. The
552 * username and password (if needed by the VFS) is obtained from the
553 * session instance.
554 * @param comp The component that will parent error dialog boxes
555 * @exception IOException if an I/O error occurred
556 * @since jEdit 2.7pre1
557 */
558 public DirectoryEntry[] _listDirectory(Object session, String directory,
559 Component comp)
560 throws IOException
561 {
562 VFSManager.error(comp,directory,"vfs.not-supported.list",new String[] { name });
563 return null;
564 } //}}}
565
566 //{{{ _getDirectoryEntry() method
567 /**
568 * Returns the specified directory entry.
569 * @param session The session
570 * @param path The path
571 * @param comp The component that will parent error dialog boxes
572 * @exception IOException if an I/O error occurred
573 * @return The specified directory entry, or null if it doesn't exist.
574 * @since jEdit 2.7pre1
575 */
576 public DirectoryEntry _getDirectoryEntry(Object session, String path,
577 Component comp)
578 throws IOException
579 {
580 return null;
581 } //}}}
582
583 //{{{ DirectoryEntry class
584 /**
585 * A directory entry.
586 * @since jEdit 2.6pre2
587 */
588 public static class DirectoryEntry implements Serializable
589 {
590 //{{{ File types
591 public static final int FILE = 0;
592 public static final int DIRECTORY = 1;
593 public static final int FILESYSTEM = 2;
594 //}}}
595
596 //{{{ Instance variables
597 public String name;
598 public String path;
599
600 /**
601 * @since jEdit 4.2pre5
602 */
603 public String symlinkPath;
604
605 public String deletePath;
606 public int type;
607 public long length;
608 public boolean hidden;
609 public boolean canRead;
610 public boolean canWrite;
611 //}}}
612
613 //{{{ DirectoryEntry constructor
614 /**
615 * @since jEdit 4.2pre2
616 */
617 public DirectoryEntry()
618 {
619 } //}}}
620
621 //{{{ DirectoryEntry constructor
622 public DirectoryEntry(String name, String path, String deletePath,
623 int type, long length, boolean hidden)
624 {
625 this.name = name;
626 this.path = path;
627 this.deletePath = deletePath;
628 this.symlinkPath = path;
629 this.type = type;
630 this.length = length;
631 this.hidden = hidden;
632 if(path != null)
633 {
634 // maintain backwards compatibility
635 VFS vfs = VFSManager.getVFSForPath(path);
636 canRead = ((vfs.getCapabilities() & READ_CAP) != 0);
637 canWrite = ((vfs.getCapabilities() & WRITE_CAP) != 0);
638 }
639 } //}}}
640
641 protected boolean colorCalculated;
642 protected Color color;
643
644 //{{{ getExtendedAttribute() method
645 /**
646 * Returns the value of an extended attribute. Note that this
647 * returns formatted strings (eg, "10 Mb" for a file size of
648 * 1048576 bytes). If you need access to the raw data, access
649 * fields and methods of this class.
650 * @param name The extended attribute name
651 * @since jEdit 4.2pre1
652 */
653 public String getExtendedAttribute(String name)
654 {
655 if(name.equals(EA_TYPE))
656 {
657 switch(type)
658 {
659 case FILE:
660 return jEdit.getProperty("vfs.browser.type.file");
661 case DIRECTORY:
662 return jEdit.getProperty("vfs.browser.type.directory");
663 case FILESYSTEM:
664 return jEdit.getProperty("vfs.browser.type.filesystem");
665 default:
666 throw new IllegalArgumentException();
667 }
668 }
669 else if(name.equals(EA_STATUS))
670 {
671 if(canRead)
672 {
673 if(canWrite)
674 return jEdit.getProperty("vfs.browser.status.rw");
675 else
676 return jEdit.getProperty("vfs.browser.status.ro");
677 }
678 else
679 {
680 if(canWrite)
681 return jEdit.getProperty("vfs.browser.status.append");
682 else
683 return jEdit.getProperty("vfs.browser.status.no");
684 }
685 }
686 else if(name.equals(EA_SIZE))
687 {
688 if(type != FILE)
689 return null;
690 else
691 return MiscUtilities.formatFileSize(length);
692 }
693 else
694 return null;
695 } //}}}
696
697 //{{{ getColor() method
698 public Color getColor()
699 {
700 if(!colorCalculated)
701 {
702 colorCalculated = true;
703 color = getDefaultColorFor(name);
704 }
705
706 return color;
707 } //}}}
708
709 //{{{ toString() method
710 public String toString()
711 {
712 return name;
713 } //}}}
714 } //}}}
715
716 //{{{ _delete() method
717 /**
718 * Deletes the specified URL.
719 * @param session The VFS session
720 * @param path The path
721 * @param comp The component that will parent error dialog boxes
722 * @exception IOException if an I/O error occurs
723 * @since jEdit 2.7pre1
724 */
725 public boolean _delete(Object session, String path, Component comp)
726 throws IOException
727 {
728 return false;
729 } //}}}
730
731 //{{{ _rename() method
732 /**
733 * Renames the specified URL. Some filesystems might support moving
734 * URLs between directories, however others may not. Do not rely on
735 * this behavior.
736 * @param session The VFS session
737 * @param from The old path
738 * @param to The new path
739 * @param comp The component that will parent error dialog boxes
740 * @exception IOException if an I/O error occurs
741 * @since jEdit 2.7pre1
742 */
743 public boolean _rename(Object session, String from, String to,
744 Component comp) throws IOException
745 {
746 return false;
747 } //}}}
748
749 //{{{ _mkdir() method
750 /**
751 * Creates a new directory with the specified URL.
752 * @param session The VFS session
753 * @param directory The directory
754 * @param comp The component that will parent error dialog boxes
755 * @exception IOException if an I/O error occurs
756 * @since jEdit 2.7pre1
757 */
758 public boolean _mkdir(Object session, String directory, Component comp)
759 throws IOException
760 {
761 return false;
762 } //}}}
763
764 //{{{ _backup() method
765 /**
766 * Backs up the specified file. This should only be overriden by
767 * the local filesystem VFS.
768 * @param session The VFS session
769 * @param path The path
770 * @param comp The component that will parent error dialog boxes
771 * @exception IOException if an I/O error occurs
772 * @since jEdit 3.2pre2
773 */
774 public void _backup(Object session, String path, Component comp)
775 throws IOException
776 {
777 } //}}}
778
779 //{{{ _createInputStream() method
780 /**
781 * Creates an input stream. This method is called from the I/O
782 * thread.
783 * @param session the VFS session
784 * @param path The path
785 * @param ignoreErrors If true, file not found errors should be
786 * ignored
787 * @param comp The component that will parent error dialog boxes
788 * @exception IOException If an I/O error occurs
789 * @since jEdit 2.7pre1
790 */
791 public InputStream _createInputStream(Object session,
792 String path, boolean ignoreErrors, Component comp)
793 throws IOException
794 {
795 VFSManager.error(comp,path,"vfs.not-supported.load",new String[] { name });
796 return null;
797 } //}}}
798
799 //{{{ _createOutputStream() method
800 /**
801 * Creates an output stream. This method is called from the I/O
802 * thread.
803 * @param session the VFS session
804 * @param path The path
805 * @param comp The component that will parent error dialog boxes
806 * @exception IOException If an I/O error occurs
807 * @since jEdit 2.7pre1
808 */
809 public OutputStream _createOutputStream(Object session,
810 String path, Component comp)
811 throws IOException
812 {
813 VFSManager.error(comp,path,"vfs.not-supported.save",new String[] { name });
814 return null;
815 } //}}}
816
817 //{{{ _saveComplete() method
818 /**
819 * Called after a file has been saved.
820 * @param session The VFS session
821 * @param buffer The buffer
822 * @param path The path the buffer was saved to (can be different from
823 * {@link org.gjt.sp.jedit.Buffer#getPath()} if the user invoked the
824 * <b>Save a Copy As</b> command, for example).
825 * @param comp The component that will parent error dialog boxes
826 * @exception IOException If an I/O error occurs
827 * @since jEdit 4.1pre9
828 */
829 public void _saveComplete(Object session, Buffer buffer, String path,
830 Component comp) throws IOException {} //}}}
831
832 //{{{ _endVFSSession() method
833 /**
834 * Finishes the specified VFS session. This must be called
835 * after all I/O with this VFS is complete, to avoid leaving
836 * stale network connections and such.
837 * @param session The VFS session
838 * @param comp The component that will parent error dialog boxes
839 * @exception IOException if an I/O error occurred
840 * @since jEdit 2.7pre1
841 */
842 public void _endVFSSession(Object session, Component comp)
843 throws IOException
844 {
845 } //}}}
846
847 //{{{ getDefaultColorFor() method
848 /**
849 * Returns color of the specified file name, by matching it against
850 * user-specified regular expressions.
851 * @since jEdit 4.0pre1
852 */
853 public static Color getDefaultColorFor(String name)
854 {
855 synchronized(lock)
856 {
857 if(colors == null)
858 loadColors();
859
860 for(int i = 0; i < colors.size(); i++)
861 {
862 ColorEntry entry = (ColorEntry)colors.elementAt(i);
863 if(entry.re.isMatch(name))
864 return entry.color;
865 }
866
867 return null;
868 }
869 } //}}}
870
871 //{{{ DirectoryEntryCompare class
872 /**
873 * Implementation of {@link org.gjt.sp.jedit.MiscUtilities.Compare}
874 * interface that compares {@link VFS.DirectoryEntry} instances.
875 * @since jEdit 4.2pre1
876 */
877 public static class DirectoryEntryCompare implements MiscUtilities.Compare
878 {
879 private boolean sortIgnoreCase, sortMixFilesAndDirs;
880
881 /**
882 * Creates a new <code>DirectoryEntryCompare</code>.
883 * @param sortMixFilesAndDirs If false, directories are
884 * put at the top of the listing.
885 * @param sortIgnoreCase If false, upper case comes before
886 * lower case.
887 */
888 public DirectoryEntryCompare(boolean sortMixFilesAndDirs,
889 boolean sortIgnoreCase)
890 {
891 this.sortMixFilesAndDirs = sortMixFilesAndDirs;
892 this.sortIgnoreCase = sortIgnoreCase;
893 }
894
895 public int compare(Object obj1, Object obj2)
896 {
897 VFS.DirectoryEntry file1 = (VFS.DirectoryEntry)obj1;
898 VFS.DirectoryEntry file2 = (VFS.DirectoryEntry)obj2;
899
900 if(!sortMixFilesAndDirs)
901 {
902 if(file1.type != file2.type)
903 return file2.type - file1.type;
904 }
905
906 return MiscUtilities.compareStrings(file1.name,
907 file2.name,sortIgnoreCase);
908 }
909 } //}}}
910
911 //{{{ Private members
912 private String name;
913 private int caps;
914 private String[] extAttrs;
915 private static Vector colors;
916 private static Object lock = new Object();
917
918 //{{{ Class initializer
919 static
920 {
921 EditBus.addToBus(new EBComponent()
922 {
923 public void handleMessage(EBMessage msg)
924 {
925 if(msg instanceof PropertiesChanged)
926 {
927 synchronized(lock)
928 {
929 colors = null;
930 }
931 }
932 }
933 });
934 } //}}}
935
936 //{{{ _listDirectory() method
937 private void _listDirectory(Object session, ArrayList stack,
938 ArrayList files, String directory, RE glob, boolean recursive,
939 Component comp) throws IOException
940 {
941 if(stack.contains(directory))
942 {
943 Log.log(Log.ERROR,this,
944 "Recursion in _listDirectory(): "
945 + directory);
946 return;
947 }
948 else
949 stack.add(directory);
950
951 VFS.DirectoryEntry[] _files = _listDirectory(session,directory,
952 comp);
953 if(_files == null || _files.length == 0)
954 return;
955
956 for(int i = 0; i < _files.length; i++)
957 {
958 VFS.DirectoryEntry file = _files[i];
959
960 if(file.type == VFS.DirectoryEntry.DIRECTORY
961 || file.type == VFS.DirectoryEntry.FILESYSTEM)
962 {
963 if(recursive)
964 {
965 // resolve symlinks to avoid loops
966 String canonPath = _canonPath(session,file.path,comp);
967 if(!MiscUtilities.isURL(canonPath))
968 canonPath = MiscUtilities.resolveSymlinks(canonPath);
969
970 _listDirectory(session,stack,files,
971 canonPath,glob,recursive,
972 comp);
973 }
974 }
975 else
976 {
977 if(!glob.isMatch(file.name))
978 continue;
979
980 Log.log(Log.DEBUG,this,file.path);
981
982 files.add(file.path);
983 }
984 }
985 } //}}}
986
987 //{{{ loadColors() method
988 private static void loadColors()
989 {
990 synchronized(lock)
991 {
992 colors = new Vector();
993
994 if(!jEdit.getBooleanProperty("vfs.browser.colorize"))
995 return;
996
997 String glob;
998 int i = 0;
999 while((glob = jEdit.getProperty("vfs.browser.colors." + i + ".glob")) != null)
1000 {
1001 try
1002 {
1003 colors.addElement(new ColorEntry(
1004 new RE(MiscUtilities.globToRE(glob)),
1005 jEdit.getColorProperty(
1006 "vfs.browser.colors." + i + ".color",
1007 Color.black)));
1008 }
1009 catch(REException e)
1010 {
1011 Log.log(Log.ERROR,VFS.class,"Invalid regular expression: "
1012 + glob);
1013 Log.log(Log.ERROR,VFS.class,e);
1014 }
1015
1016 i++;
1017 }
1018 }
1019 } //}}}
1020
1021 //{{{ ColorEntry class
1022 static class ColorEntry
1023 {
1024 RE re;
1025 Color color;
1026
1027 ColorEntry(RE re, Color color)
1028 {
1029 this.re = re;
1030 this.color = color;
1031 }
1032 } //}}}
1033
1034 //}}}
1035}