PageRenderTime 487ms CodeModel.GetById 424ms app.highlight 53ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-3-pre5/org/gjt/sp/jedit/io/VFS.java

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