Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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>&lt;SERVICE CLASS="org.gjt.sp.jedit.io.VFS" NAME="<i>name</i>"&gt;
44   *    new <i>MyVFS</i>();
45   *&lt;/SERVICE&gt;</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}