PageRenderTime 180ms CodeModel.GetById 132ms app.highlight 42ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-2-pre14/org/gjt/sp/jedit/io/VFS.java

#
Java | 1035 lines | 463 code | 100 blank | 472 comment | 67 complexity | d597803a2e2d111a5e4f4ab4ae7d4e5f MD5 | raw file
   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
  23package org.gjt.sp.jedit.io;
  24
  25//{{{ Imports
  26import gnu.regexp.*;
  27import java.awt.Color;
  28import java.awt.Component;
  29import java.io.*;
  30import java.util.*;
  31import org.gjt.sp.jedit.buffer.BufferIORequest;
  32import org.gjt.sp.jedit.msg.PropertiesChanged;
  33import org.gjt.sp.jedit.*;
  34import 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 4881 2003-09-08 01:24:11Z spestov $
  95 */
  96public 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}