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

Quick Search    Search Deep

Source code: javax/ide/net/VirtualFileSystem.java


1   /*
2    * @(#)VirtualFileSystem.java
3    *
4    * Copyright 2003 by Oracle Corporation,
5    * 500 Oracle Parkway, Redwood Shores, California, 94065, U.S.A.
6    * All rights reserved.
7    *
8    * This software is the confidential and proprietary information
9    * of Oracle Corporation.
10   */
11  
12  package javax.ide.net;
13  
14  import java.io.File;
15  import java.io.FileOutputStream;
16  import java.io.IOException;
17  import java.io.InputStream;
18  import java.io.OutputStream;
19  
20  import java.net.MalformedURLException;
21  import java.net.URI;
22  import java.net.URL;
23  
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  
30  import javax.ide.spi.ProviderNotFoundException;
31  import javax.ide.Service;
32  
33  /**
34   *  The <code>VirtualFileSystem</code> class is responsible for encapsulating
35   *  the notion of file system operations on content that is pointed to by
36   *  an {@link URI}.<P>
37   *
38   *  The behavior of <code>VirtualFileSystem</code> can be extended by
39   *  subclasses of {@link VirtualFileSystemHelper}.  An instance of
40   *  <code>VirtualFileSystemHelper</code> is registered with
41   *  <code>VirtualFileSystem</code> in association with a particular
42   *  scheme.  Scheme-specific behavior can thus be encapsulated by a
43   *  specific implementation of <code>VirtualFileSystemHelper</code>.
44   *
45   *  IDE implementations do not need to register an implementation of this
46   *  service. The default registered implementation delegates most of its
47   *  operations to registered VirtualFileSystemHelpers.
48   */
49  public class VirtualFileSystem extends Service
50  {
51  
52    private static final int COPY_BUFFER_SIZE = 4096;
53  
54    /**
55     * The "file" URI scheme.
56     */
57    public static final String FILE_SCHEME     = "file"; // NOTRANS
58    /**
59     * The "http" URI scheme.
60     */
61    public static final String HTTP_SCHEME     = "http"; // NOTRANS
62    /**
63     * The "jar" URI scheme.
64     */
65    public static final String JAR_SCHEME      = "jar"; // NOTRANS
66  
67    private static final boolean _isCaseSensitive;
68  
69    private final HashMap _helpers = new HashMap();
70    private final VirtualFileSystemHelper _defaultHelper = 
71                                                  new VirtualFileSystemHelper();
72    private final ArrayList _existsTests = new ArrayList( 3 );
73  
74  
75    static
76    {
77      //  Determine whether the local file system is case-sensitive or not.
78      //  This will affect how URIs are sorted in the UI.
79      final File f1 = new File( "A" );
80      final File f2 = new File( "a" );
81      _isCaseSensitive = !f1.equals( f2 );
82    }
83  
84    //--------------------------------------------------------------------------
85    //  extension API...
86    //--------------------------------------------------------------------------
87    /**
88     *  Registers the specified {@link VirtualFileSystemHelper} as the object
89     *  that can handle {@link VirtualFileSystem} operations for {@link URI}s
90     *  of the specified <CODE>scheme</CODE>.<P>
91     *
92     *  If the <CODE>scheme</CODE> is <CODE>null</CODE> or the empty
93     *  string or if <CODE>helper</CODE> is <CODE>null</CODE>, this method
94     *  does nothing.
95     *  
96     *  @param scheme the URI scheme to register a helper for. Must not be null
97     *      or an empty String.
98     *  @param helper the helper to register for the specified scheme. Must not
99     *      be null.
100    */
101   public void registerHelper( String scheme, VirtualFileSystemHelper helper )
102   {
103     if ( scheme == null )
104     {
105       throw new NullPointerException( "scheme must not be null" );
106     }
107     // Trim whitespace from the scheme name because it is not significant.
108     scheme = scheme.trim();
109     if ( scheme.length() == 0 )
110     {
111       throw new IllegalArgumentException( "cannot use empty string for scheme" );
112     }
113     if ( helper == null )
114     {
115       throw new NullPointerException( "helper must not be null" );
116     }
117 
118     _helpers.put( scheme, helper );
119   }
120 
121   /**
122    *  Returns the {@link VirtualFileSystemHelper} class that is currently
123    *  registered to handle operations related to the specified
124    *  <CODE>scheme</CODE>.  If there is no registered helper, then a
125    *  default helper is returned that can produce a default result.
126    *  
127    *  @param scheme the scheme to look up the helper for. Must not be null.
128    *  @return the registered file system helper for the specified scheme, or
129    *      a default helper if no helper is registered.
130    */
131   public VirtualFileSystemHelper findHelper( String scheme )
132   {
133     if ( scheme == null )
134     {
135       throw new NullPointerException( "scheme must not be null" );
136     }
137     Object helper = _helpers.get( scheme );
138     return helper != null ? (VirtualFileSystemHelper) helper : _defaultHelper;
139   }
140 
141   /**
142    *  Returns the {@link VirtualFileSystemHelper} class that is currently
143    *  registered to handle operations related to the specified
144    *  {@link URI}.  If there is no registered helper, then a default
145    *  helper is returned that can produce a default result.
146    *  
147    *  @param uri a uri to find the helper for. May be null, in which case
148    *    the default helper is returned.
149    *  @return the helper for the scheme of the specified uri, or the 
150    *    default helper if the uri is null or no helper is registered for the
151    *    scheme of the uri.
152    */
153   public VirtualFileSystemHelper findHelper( URI uri )
154   {
155     if ( uri == null )
156     {
157       return _defaultHelper;
158     }
159 
160     final String scheme = uri.getScheme();
161     return findHelper( scheme );
162   }
163 
164   /**
165    *  Add an implementation of the {@link URIExistsTest} interface.
166    *  The <code>existsTest</code> object will be called by the 
167    *  <code>isBound()</code> method to determine if an {@link URI} is unique.
168    *  
169    *  @param existsTest the implementation of an existence test for uris. Must
170    *    not be null.
171    */
172   public void addExistsTest( URIExistsTest existsTest )
173   {
174     if ( existsTest == null )
175     {
176       throw new NullPointerException( "existsTest must not be null"  );
177     }
178     _existsTests.add( existsTest );
179   }
180 
181   //--------------------------------------------------------------------------
182   //  VirtualFileSystem operations...
183   //--------------------------------------------------------------------------
184   /**
185    *  Returns a canonical form of the {@link URI}, if one is available.
186    *  
187    *  @param uri the uri to canonicalize. 
188    *  @return a canonicalized form of the specified uri.
189    *  @throws java.io.IOException if the uri could not be canonicalized.
190    *  @see VirtualFileSystemHelper#canonicalize( URI )
191    */
192   public URI canonicalize( URI uri ) throws IOException
193   {
194     return findHelper( uri ).canonicalize( uri );
195   }
196 
197   /**
198    *  Tests whether the application can read the resource at the
199    *  specified {@link URI}. 
200    *  
201    *  @param uri the uri to check.
202    *  @return  <CODE>true</CODE> if and only if the specified
203    *  {@link URI} points to a resource that exists <EM>and</EM> can be
204    *  read by the application; <CODE>false</CODE> otherwise.
205    *  @see VirtualFileSystemHelper#canRead( URI )
206    */
207   public boolean canRead( URI uri )
208   {
209     return findHelper( uri ).canRead( uri );
210   }
211 
212   /**
213    *  Tests whether the application can modify the resource at the
214    *  specified {@link URI}.
215    *
216    *  @param uri the uri to check.
217    *  @return  <CODE>true</CODE> if and only if the specified
218    *  {@link URI} points to a file that exists <EM>and</EM> the
219    *  application is allowed to write to the file; <CODE>false</CODE>
220    *  otherwise.
221    *  @see VirtualFileSystemHelper#canWrite( URI )
222    */
223   public boolean canWrite( URI uri )
224   {
225     return findHelper( uri ).canWrite( uri );
226   }
227 
228   /**
229    * Tests whether the application can create the resource at the specified
230    * {@link URI}. This method tests that all components of the path can
231    * be created. If the resource pointed by the {@link URI} is read-only,
232    * this method returns <CODE>false</CODE>.
233    *
234    * @param uri the uri to check.
235    * @return <CODE>true</CODE> if the resource at the specified {@link URI}
236    * exists or can be created; <CODE>false</CODE> otherwise.
237    * @see VirtualFileSystemHelper#canCreate( java.net.URI )
238    */
239   public boolean canCreate( URI uri )
240   {
241     return findHelper( uri ).canCreate( uri );
242   }
243 
244   /**
245    * Tests whether the specified {@link URI} is valid. If the resource
246    * pointed by the {@link URI} exists the method returns <CODE>true</CODE>.
247    * If the resource does not exist, the method tests that all components
248    * of the path can be created.
249    *
250    * @param uri the uri to check.
251    * @return <CODE>true</CODE> if the {@link URI} is valid.
252    * @see VirtualFileSystemHelper#isValid( java.net.URI )
253    */
254   public boolean isValid( URI uri )
255   {
256     return findHelper( uri ).isValid( uri );
257   }
258 
259   /**
260    *  Takes the given {@link URI} and checks if its {@link #toString()}
261    *  representation ends with the specified <CODE>oldSuffix</CODE>.  If
262    *  it does, the suffix is replaced with <CODE>newSuffix</CODE>.  Both
263    *  suffix parameters must include the leading dot ('.') if the dot is
264    *  part of the suffix.  If the specified {@link URI} does not end
265    *  with the <CODE>oldSuffix</CODE>, then the <CODE>newSuffix</CODE>
266    *  is simply appended to the end of the original {@link URI}.
267    *  
268    *  @param uri the uri to check.
269    *  @param oldSuffix the old suffix to check for.
270    *  @param newSuffix the new suffix to use.
271    *  
272    *  @return a new uri with the old suffix replaced by the new suffix, or the
273    *      new suffix appended if the original uri did not end with 
274    *      <tt>oldSuffix</tt>.
275    *      
276    *  @see VirtualFileSystemHelper#convertSuffix( URI, String, String )   
277    */
278   public URI convertSuffix( URI uri, String oldSuffix, String newSuffix )
279   {
280     return findHelper( uri ).convertSuffix( uri, oldSuffix, newSuffix );
281   }
282 
283   /**
284    *  Copies the contents at <CODE>src</CODE> to <CODE>dst</CODE>. If the
285    *  destination directory does not exist, it will be created.
286    *  IOException is thrown if:
287    *  <ul>
288    *    <li>failed to read the source contents</li>
289    *    <li>failed to create destination directory</li>
290    *    <li>failed to write source contents to the destination file</li>
291    *  </ul>
292    *  
293    *  @param src the uri of the resource to copy from. Must not be null.
294    *  @param dst the uri of the resource to copy to. Must not be null.
295    *  @throws java.io.IOException if an error occurs copying the resource from
296    *    the old uri to the new uri.
297    */
298   public void copy( URI src, URI dst ) throws IOException
299   {
300     if ( src == null )
301     {
302       throw new NullPointerException( "src uri must not be null" );
303     }
304     if ( dst == null )
305     {
306       throw new NullPointerException( "dst uri must not be null" );
307     }
308   
309     InputStream in = null;
310     OutputStream out = null;
311     try
312     {
313       in = openInputStream( src );
314       out = openOutputStream( dst );
315       if (!mkdirs( dst ))
316       {
317         throw new IOException( "Failed to create parent directories for "+dst);
318       }
319       copy( in, out );
320     }
321     finally
322     {
323       try { if (  in != null )  in.close(); } catch ( IOException e ) { e.printStackTrace();  }
324       try { if ( out != null ) out.close(); } catch ( IOException e ) { e.printStackTrace(); }
325     }
326   }
327 
328 
329   /**
330    *  Copies the contents of <CODE>in</CODE> to <CODE>dst</CODE>.
331    *  
332    *  @param in an input stream to read data from. Must not be null.
333    *  @param dst a uri to copy data to. Must not be null.
334    *  @throws java.io.IOException if an error occurs reading or writing.
335    */
336   public void copy( InputStream in, URI dst ) throws IOException
337   {
338     if ( in == null )
339     {
340       throw new NullPointerException( "in must not be null" );
341     }
342     if ( dst == null )
343     {
344       throw new NullPointerException( "dst must not be null" );
345     }
346   
347     OutputStream out = null;
348     try
349     {
350       out = openOutputStream( dst );
351       mkdirs( dst );
352       copy( in, out );
353     }
354     finally
355     {
356       try { if (  in != null )  in.close(); } catch ( IOException e ) { e.printStackTrace();  }
357       try { if ( out != null ) out.close(); } catch ( IOException e ) { e.printStackTrace(); }
358     }
359   }
360 
361 
362   /**
363    *  Copies the contents of <CODE>src</CODE> to <CODE>dst</CODE>.
364    *  
365    *  @param src the uri of a resource to read data from. Must not be null.
366    *  @param dst a file object to write data to. Must not be null.
367    *  @throws java.io.IOException if an error occurs copying data.
368    */
369   public void copy( URI src, File dst ) throws IOException
370   {
371     if ( src == null )
372     {
373       throw new NullPointerException( "src must not be null" );
374     }
375     if ( dst == null )
376     {
377       throw new NullPointerException( "dst must not be null" );
378     }
379   
380     InputStream in = null;
381     OutputStream out = null;
382     try
383     {
384       in = openInputStream( src );
385       out = new FileOutputStream( dst );
386       dst.mkdirs();
387       copy( in, out );
388     }
389     finally
390     {
391       try { if (  in != null )  in.close(); } catch ( IOException e ) { e.printStackTrace(); }
392       try { if ( out != null ) out.close(); } catch ( IOException e ) { e.printStackTrace(); }
393     }
394   }
395 
396 
397   /**
398    *  Common code for copy routines.  By convention, the streams are
399    *  closed in the same method in which they were opened.  Thus,
400    *  this method does not close the streams when the copying is done.
401    */
402   private static void copy( InputStream in, OutputStream out )
403     throws IOException
404   {
405     final byte[] buffer = new byte[ COPY_BUFFER_SIZE ];
406     while ( true )
407     {
408       final int bytesRead = in.read( buffer );
409       if ( bytesRead < 0 )
410       {
411         break;
412       }
413       out.write( buffer, 0, bytesRead );
414     }
415   }
416 
417   /**
418    *  Deletes the resource pointed to by the specified {@link URI}.  If
419    *  the resource is a file (or analogous to a file), then the file is
420    *  removed from its directory (or container).  If the content is a
421    *  directory (or analogous to a directory), then the directory is
422    *  removed only if it is empty (i.e. contains no other files or
423    *  directories).
424    *
425    *  @param uri the uri of the resource to delete.
426    *  @return <CODE>true</CODE> if and only if the file or directory
427    *    is successfully deleted; <CODE>false</CODE> otherwise.
428    *  @see VirtualFileSystemHelper#delete( URI )
429    */
430   public boolean delete( URI uri )
431   {
432     return findHelper( uri ).delete( uri );
433   }
434 
435 
436   /**
437    *  This method ensures that the specified {@link URI} ends with the
438    *  specified <CODE>suffix</CODE>.  The suffix does not necessarily
439    *  have to start with a ".", so if a leading "." is required, the
440    *  specified suffix must contain it -- e.g. ".java", ".class".<P>
441    *
442    *  If the {@link URI} already ends in the specified suffix, then
443    *  the {@link URI} itself is returned.  Otherwise, a new
444    *  {@link URI} is created with the the specified suffix appended
445    *  to the original {@link URI}'s path part, and the new {@link URI}
446    *  is returned.
447    *  
448    *  @param uri the uri to check. Must not be null.
449    *  @param suffix the suffix to ensure the specified uri has. Must not be
450    *      null.
451    *
452    *  @return An {@link URI}, based on the specified {@link URI}, whose
453    *  path part ends with the specified suffix.
454    */
455   public URI ensureSuffix( URI uri, String suffix )
456   {
457     if ( uri == null )
458     {
459       throw new NullPointerException( "uri must not be null" );
460     }
461     if ( suffix == null )
462     {
463       throw new NullPointerException( "suffix must not be null" );
464     }
465     return findHelper( uri ).ensureSuffix( uri, suffix );
466   }
467 
468 
469   /**
470    *  Returns <CODE>true</CODE> if both of the specified {@link URI}
471    *  parameters point to the same {@link URI} instance or if
472    *  both {@link URI} parameters have the same scheme and the
473    *  scheme helper determines that the {@link URI} objects are
474    *  equal.
475    *  
476    *  @param uri1 the first uri to compare.
477    *  @param uri2 the second uri to compare.
478    *  
479    *  @return true if the two uris are both null or the same instance, or if
480    *      the schemes are equal and the registered VirtualFileSystemHelper
481    *      for the scheme returns true from 
482    *      {@link VirtualFileSystemHelper#equals( URI, URI ) equals(uri1,uri2)}.
483    *  @see VirtualFileSystemHelper#equals( URI, URI )
484    */
485   public boolean equals( URI uri1, URI uri2 )
486   {
487     if ( uri1 == uri2 )
488     {
489       return true;
490     }
491     else if ( !schemesAreEqual( uri1, uri2 ) )
492     {
493       return false;
494     }
495     return findHelper( uri1 ).equals( uri1, uri2 );
496   }
497 
498 
499   /**
500    *  Tests whether a resource at the specified {@link URI} location
501    *  currently exists.  The test for existence only checks the actual
502    *  location and does not check any in-memory caches.  To employ
503    *  additional existential tests that do check in-memory caches, see
504    *  {@link #isBound(URI)}.
505    *
506    *  @param uri the uri of a resource to check for existence. Must not be
507    *      null.
508    *  @return <CODE>true</CODE> if and only if a resource already exists
509    *  at the specified {@link URI} location; <CODE>false</CODE>
510    *  otherwise.
511    *  @see VirtualFileSystemHelper#exists( URI )
512    */
513   public boolean exists( URI uri )
514   {
515     if ( uri == null )
516     {
517       throw new NullPointerException( "uri must not be null" );
518     }
519     return findHelper( uri ).exists( uri );
520   }
521 
522 
523   /**
524    *  Returns the name of the file contained by the {@link URI}, not
525    *  including any scheme, authority, directory path, query, or
526    *  fragment.  This simply returns the simple filename.  For example,
527    *  if you pass in an {@link URI} whose string representation is:
528    *
529    *  <BLOCKQUOTE><CODE>
530    *    scheme://userinfo@host:1010/dir1/dir2/file.ext?query#fragment
531    *  </CODE></BLOCKQUOTE>
532    *
533    *  the returned value is "<CODE>file.ext</CODE>" (without the
534    *  quotes).
535    *
536    *  @param uri the uri to get the file name of. Must not be null.
537    *  @return The simple filename of the specified {@link URI}.  This
538    *  value should only be used for display purposes and not for opening
539    *  streams or otherwise trying to locate the document.
540    *  @see VirtualFileSystemHelper#getFileName( URI )
541    */
542   public String getFileName( URI uri )
543   {
544     if ( uri == null )
545     {
546       throw new NullPointerException( "uri must not be null" );
547     }
548     return findHelper( uri ).getFileName( uri );
549   }
550 
551 
552   /**
553    *  Returns the number of bytes contained in the resource that the
554    *  specified {@link URI} points to.  If the length cannot be
555    *  determined, <CODE>-1</CODE> is returned.<P>
556    *
557    *  @param uri the uri of the resource to get the size of. Must not be null.
558    *  @return the size in bytes of the document at the specified
559    *  {@link URI}.
560    *  @see VirtualFileSystemHelper#getLength( URI )
561    */
562   public long getLength( URI uri )
563   {
564     if ( uri == null )
565     {
566       throw new NullPointerException( "uri must not be null" );
567     }
568     return findHelper( uri ).getLength( uri );
569   }
570 
571 
572   /**
573    *  Returns the name of the file contained by the {@link URI}, not
574    *  including any scheme, authority, directory path, file extension,
575    *  query, or fragment.  This simply returns the simple filename.  For
576    *  example, if you pass in an {@link URI} whose string representation
577    *  is:
578    *
579    *  <BLOCKQUOTE><CODE>
580    *    scheme://userinfo@host:1010/dir1/dir2/file.ext1.ext2?query#fragment
581    *  </CODE></BLOCKQUOTE>
582    *
583    *  the returned value is "<CODE>file</CODE>" (without the quotes).<P>
584    *
585    *  The returned file name should only be used for display purposes
586    *  and not for opening streams or otherwise trying to locate the
587    *  resource indicated by the {@link URI}.<P>
588    *  
589    *  @param uri the uri to get the name of. Must not be null.
590    *  @return the simple name of the uri without an extension.
591    *  @see VirtualFileSystemHelper#getName( URI )
592    */
593   public String getName( URI uri )
594   {
595     if ( uri == null )
596     {
597       throw new NullPointerException( "uri must not be null" );
598     }
599     return findHelper( uri ).getName( uri );
600   }
601 
602 
603   /**
604    *  Returns the {@link URI} representing the parent of the specified 
605    *  {@link URI}.  If there is no parent then <CODE>null</CODE> is returned.
606    *  
607    *  @param uri the uri of the resource to get the parent of. Must not be
608    *    null.
609    *  @return the uri of the parent resource, or null if the specified resource
610    *    has no parent.
611    *    
612    *  @see VirtualFileSystemHelper#getParent( URI )
613    */
614   public URI getParent( URI uri )
615   {
616     if ( uri == null )
617     {
618       throw new NullPointerException( "uri must not be null" );
619     }  
620     return findHelper( uri ).getParent( uri );
621   }
622 
623   /**
624    *  Returns the path part of the {@link URI}.  The returned string
625    *  is acceptable to use in one of the {@link URIFactory} methods
626    *  that takes a path.<P>
627    *
628    *  This implementation delegates to {@link URI#getPath()}.
629    *  
630    *  @param uri the uri to get the path of. Must not be null.
631    *  @return the path of the specified uri.
632    *  
633    *  @see VirtualFileSystemHelper#getPath( URI )
634    */
635   public String getPath( URI uri )
636   {
637     if ( uri == null )
638     {
639       throw new NullPointerException( "uri must not be null" );
640     }  
641     return findHelper( uri ).getPath( uri );
642   }
643 
644 
645   /**
646    *  Returns the path part of the {@link URI} without the last file
647    *  extension.  To clarify, the following examples demonstrate the
648    *  different cases:
649    *
650    *  <TABLE BORDER COLS=2 WIDTH="100%">
651    *    <TR>
652    *      <TD><CENTER>Path part of input {@link URI}</CENTER></TD>
653    *      <TD><CENTER>Output {@link String}</CENTER</TD>
654    *    </TR>
655    *    <TR>
656    *      <TD><CODE>/dir/file.ext</CODE></TD>
657    *      <TD><CODE>/dir/file</CODE></TD>
658    *    </TR>
659    *    <TR>
660    *      <TD><CODE>/dir/file.ext1.ext2</CODE></TD>
661    *      <TD><CODE>/dir/file.ext1</CODE></TD>
662    *    </TR>
663    *    <TR>
664    *      <TD><CODE>/dir1.ext1/dir2.ext2/file.ext1.ext2</CODE></TD>
665    *      <TD><CODE>/dir1.ext1/dir2.ext2/file.ext1</CODE></TD>
666    *    </TR>
667    *    <TR>
668    *      <TD><CODE>/file.ext</CODE></TD>
669    *      <TD><CODE>/file</CODE></TD>
670    *    </TR>
671    *    <TR>
672    *      <TD><CODE>/dir.ext/file</CODE></TD>
673    *      <TD><CODE>/dir.ext/file</CODE></TD>
674    *    </TR>
675    *    <TR>
676    *      <TD><CODE>/dir/file</CODE></TD>
677    *      <TD><CODE>/dir/file</CODE></TD>
678    *    </TR>
679    *    <TR>
680    *      <TD><CODE>/file</CODE></TD>
681    *      <TD><CODE>/file</CODE></TD>
682    *    </TR>
683    *    <TR>
684    *      <TD><CODE>/.ext</CODE></TD>
685    *      <TD><CODE>/</CODE></TD>
686    *    </TR>
687    *  </TABLE>
688    *  
689    *  @param uri the uri to get the path of without extension. Must not be null.
690    *  @return the path of the specified uri without its extension.
691    *  
692    *  @see VirtualFileSystemHelper#getPathNoExt( URI )
693    */
694   public String getPathNoExt( URI uri )
695   {
696     if ( uri == null )
697     {
698       throw new NullPointerException( "uri must not be null" );
699     }  
700     return findHelper( uri ).getPathNoExt( uri );
701   }
702 
703 
704   /**
705    *  Returns the platform-dependent String representation of the
706    *  {@link URI}; the returned string should be considered acceptable
707    *  for users to read.  In general, the returned string should omit
708    *  as many parts of the {@link URI} as possible.  For the "file"
709    *  scheme, therefore, the platform pathname should just be the
710    *  pathname alone (no scheme) using the appropriate file separator
711    *  character for the current platform.  For other schemes, it may
712    *  be necessary to reformat the {@link URI} string into a more
713    *  human-readable form.  That decision is left to each
714    *  {@link VirtualFileSystemHelper} implementation.
715    *
716    *  @param uri the uri of the resource to get the platform path name of.
717    *    Must not be null.
718    *  @return  The path portion of the specified {@link URI} in
719    *  platform-dependent notation.  This value should only be used for
720    *  display purposes and not for opening streams or otherwise trying
721    *  to locate the document.
722    *  
723    *  @see VirtualFileSystemHelper#getPlatformPathName( URI )
724    */
725   public String getPlatformPathName( URI uri )
726   {
727     if ( uri == null )
728     {
729       throw new NullPointerException( "uri must not be null" );
730     }  
731     return findHelper( uri ).getPlatformPathName( uri );
732   }
733 
734 
735   /**
736    *  If a dot ('.') occurs in the filename part of the path part of
737    *  the {@link URI}, then all of the text starting at the last dot is
738    *  returned, including the dot.  If the last dot is also the last
739    *  character in the path, then the dot by itself is returned.  If
740    *  there is no dot in the file name (even if a dot occurs elsewhere
741    *  in the path), then the empty string is returned.<P>
742    *
743    *  Examples:
744    *  <UL>
745    *    <LI>file:/home/jsr198/foo.txt returns ".txt"
746    *    <LI>file:/home/jsr198/foo.txt.bak returns ".bak"
747    *    <LI>file:/home/jsr198/foo returns ""
748    *    <LI>file:/home/jsr198/foo. returns "."
749    *    <LI>file:/home/jsr198/foo/ returns ""
750    *    <LI>file:/home/jsr198.1/foo returns ""
751    *    <LI>file:/home/jsr198.1/foo.txt returns ".txt"
752    *  </UL>
753    *  
754    *  @param uri the uri to get the suffix of. Must not be null.
755    *  @return the suffix of the specified uri.
756    *  @see VirtualFileSystemHelper#getSuffix( URI )
757    */
758   public String getSuffix( URI uri )
759   {
760     if ( uri == null )
761     {
762       throw new NullPointerException( "uri must not be null" );
763     }  
764     return findHelper( uri ).getSuffix( uri );
765   }
766 
767 
768   /**
769    *  Returns <CODE>true</CODE> if the path part of the {@link URI}
770    *  ends with the given <CODE>suffix</CODE> String.  The suffix can
771    *  be any String and doesn't necessarily have to be one that begins
772    *  with a dot ('.').  If you are trying to test whether the path
773    *  part of the {@link URI} ends with a particular file extension,
774    *  then the <CODE>suffix</CODE> parameter must begin with a '.'
775    *  character to ensure that you get the right return value.
776    *  
777    *  @param uri the uri to check. Must not be null.
778    *  @param suffix the suffix to check for. Must not be null.
779    *  @return true if the specified suffix is present on the specified uri.
780    *  @see VirtualFileSystemHelper#hasSuffix( URI, String )
781    */
782   public boolean hasSuffix( URI uri, String suffix )
783   {
784     if ( uri == null )
785     {
786       throw new NullPointerException( "uri must not be null" );
787     }
788     if ( suffix == null )
789     {
790       throw new NullPointerException( "suffix must not be null" );
791     }    
792     return findHelper( uri ).hasSuffix( uri, suffix );
793   }
794 
795 
796   /**
797    *  Returns <CODE>true</CODE> if <CODE>uri1</CODE> represents a
798    *  a directory and <CODE>uri2</CODE> points to a location within
799    *  <CODE>uri1</CODE>'s directory tree.
800    *  
801    *  @param uri1 the uri of a directory resource.
802    *  @param uri2 the uri of a resource.
803    *  
804    *  @return true if uri2 is uri1 or points to a descendent resource of 
805    *    uri1. If either uri is null, returns false.
806    *  @see VirtualFileSystemHelper#isBaseURIFor( URI, URI )
807    */
808   public boolean isBaseURIFor( URI uri1, URI uri2 )
809   {
810     if ( uri1 == null || uri2 == null )
811     {
812       return false;
813     }
814     else if ( uri1 == uri2 )
815     {
816       return true;
817     }
818     else if ( !schemesAreEqual( uri1, uri2 ) )
819     {
820       return false;
821     }
822     return findHelper( uri1 ).isBaseURIFor( uri1, uri2 );
823   }
824 
825 
826   /**
827    *  This method tests whether the specified {@link URI} is bound to
828    *  an existing resource, which may reside in memory (not yet
829    *  saved) or already exist at the {@link URI} location.  This is
830    *  similar to {@link #exists(URI)} but differs in that additional
831    *  tests are applied that check whether the {@link URI} is bound
832    *  to an in-memory object before the actual {@link URI} location is
833    *  checked.<P>
834    *
835    *  More precisely, "bound" means either that an {@link URI} is being
836    *  used in such a way that a registered {@link URIExistsTest} is able
837    *  to detect the {@link URI}'s usage or that a document already
838    *  exists at the {@link URI} location as determined by {@link
839    *  #exists(URI)}).<P>
840    *
841    *  Checking for uniqueness of a newly generated {@link URI} is the
842    *  primary use for this method.
843    *  
844    *  @param uri the uri to check, must not be null.
845    *  @return true if the specified uri is bound.
846    */
847   public boolean isBound( URI uri )
848   {
849     if ( uri == null )
850     {
851       throw new NullPointerException( "uri must not be null" );
852     }
853     for ( Iterator iter = _existsTests.iterator(); iter.hasNext(); )
854     {
855       final URIExistsTest test = (URIExistsTest) iter.next();
856       if ( test.uriExists( uri ) )
857       {
858         return true;
859       }
860     }
861     return exists( uri );
862   }
863 
864 
865   /**
866    *  Returns <CODE>true</CODE> if the local file system is case
867    *  sensitive.  Returns <CODE>false</CODE> if the local file system is
868    *  not case sensitive.
869    *  
870    *  @return true if the local file system is case sensitive.
871    */
872   public static boolean isLocalFileSystemCaseSensitive()
873   {
874     return _isCaseSensitive;
875   }
876 
877 
878   /**
879    *  Tests whether the location indicated by the {@link URI} is
880    *  a directory resource.<P>
881    *
882    *  @param uri the uri to check. Must not be null.
883    *  @return  <CODE>true</CODE> if and only if the location indicated
884    *  by the {@link URI} exists <EM>and</EM> is a directory;
885    *  <CODE>false</CODE> otherwise.
886    *  @see VirtualFileSystemHelper#isDirectory( URI )
887    */
888   public boolean isDirectory( URI uri )
889   {
890     if ( uri == null )
891     {
892       throw new NullPointerException( "uri must not be null" );
893     }
894     return findHelper( uri ).isDirectory( uri );
895   }
896 
897 
898   /**
899    *  Tests whether the location indicated by the {@link URI}
900    *  represents a directory path.  The directory path specified by
901    *  the {@link URI} need not exist.<P>
902    *
903    *  This method is intended to be a higher performance version of
904    *  the {@link #isDirectory(URI)} method.  Implementations of this
905    *  method should attempt to ascertain whether the specified {@link
906    *  URI} represents a directory path by simply examining the {@link
907    *  URI} itself.  Time consuming i/o operations should be
908    *  avoided.<P>
909    *
910    *  @param uri the uri to check. Must not be null.
911    *  @return <CODE>true</CODE> if the location indicated by the
912    *  {@link URI} represents a directory path; the directory path need
913    *  not exist.
914    *  
915    *  @see VirtualFileSystemHelper#isDirectoryPath( URI )
916    */
917   public boolean isDirectoryPath( URI uri )
918   {
919     if ( uri == null )
920     {
921       throw new NullPointerException( "uri must not be null" );
922     }
923     return findHelper( uri ).isDirectoryPath( uri );
924   }
925 
926 
927   /**
928    *  Tests whether the resource indiciated by the {@link URI} is a
929    *  hidden file.  The exact definition of <EM>hidden</EM> is
930    *  scheme-dependent and possibly system-dependent.  On UNIX
931    *  systems, a file is considered to be hidden if its name begins
932    *  with a period character ('.').  On Win32 systems, a file is
933    *  considered to be hidden if it has been marked as such in the
934    *  file system.<P>
935    *  
936    *  @param uri the uri to check. Must not be null.
937    *  @return true if the uri represents a hidden resource.
938    *  
939    *  @see VirtualFileSystemHelper#isHidden( URI )
940    */
941   public boolean isHidden( URI uri )
942   {
943     if ( uri == null )
944     {
945       throw new NullPointerException( "uri must not be null" );
946     }    
947     return findHelper( uri ).isHidden( uri );
948   }
949 
950 
951   /**
952    *  Returns <CODE>true</CODE> if the resource is read-only.  A return
953    *  value of <CODE>false</CODE> means that trying to get an
954    *  {@link OutputStream} or trying to write to an {@link OutputStream}
955    *  based on the {@link URI} will cause an IOException to be thrown.
956    *  If the read-only status cannot be determined for some reason,
957    *  this method returns <CODE>true</CODE>.
958    *
959    *  @param uri the uri of the resource to check. Must not be null.
960    *  @return  <CODE>true</CODE> if the document pointed to by the
961    *  specified {@link URI} is read-only or if the read-only status
962    *  cannot be determined; <CODE>false</CODE> otherwise.
963    *  
964    *  @see VirtualFileSystemHelper#isReadOnly( URI )
965    */
966   public boolean isReadOnly( URI uri )
967   {
968     if ( uri == null )
969     {
970       throw new NullPointerException( "uri must not be null" );
971     }  
972     return findHelper( uri ).isReadOnly( uri );
973   }
974 
975 
976   /**
977    *  Tests whether the resource indiciated by the {@link URI} is
978    *  a regular file.  A <EM>regular</EM> is a file that is not a
979    *  directory and, in addition, satisfies other system-dependent
980    *  criteria.<P>
981    *
982    *  @param uri the uri of the resource to check. Must not be null.
983    *  @return  <CODE>true</CODE> if and only if the resource
984    *  indicated by the {@link URI} exists <EM>and</EM> is a normal
985    *  file.
986    *  @see VirtualFileSystemHelper#isRegularFile( URI )
987    */
988   public boolean isRegularFile( URI uri )
989   {
990     if ( uri == null )
991     {
992       throw new NullPointerException( "uri must not be null" );
993     }
994     return findHelper( uri ).isRegularFile( uri );
995   }
996 
997 
998   /**
999    *  Returns <CODE>true</CODE> if the specified {@link URI}
1000   *  corresponds to the root of a file system; <CODE>false</CODE>
1001   *  otherwise.
1002   *  
1003   *  @param uri the uri of a resource to check. Must not be null.
1004   *  @return <tt>true</tt> if the specified uri is a resource that is the
1005   *    root of a file system.
1006   */
1007  public boolean isRoot( URI uri )
1008  {
1009    if ( uri == null )
1010    {
1011      throw new NullPointerException( "uri must not be null" );
1012    }
1013    final URI[] roots = listRoots();
1014    final int n = roots.length;
1015    for ( int i = 0; i < n; i++ )
1016    {
1017      if ( equals( uri, roots[i] ) )
1018      {
1019        return true;
1020      }
1021    }
1022    return false;
1023  }
1024
1025
1026  /**
1027   *  Returns the last modified time of the resource pointed to by the
1028   *  {@link URI}.  The returned <CODE>long</CODE> is the number of
1029   *  milliseconds since the epoch (00:00:00 GMT Jan 1, 1970).  If no
1030   *  timestamp is available, <CODE>-1</CODE> is returned.
1031   *
1032   *  @param uri the uri of the resource to get the last modified time of. Must
1033   *    not be null.
1034   *  @return  The last modified time of the resource pointed to by the
1035   *  specified {@link URI} in milliseconds since the epoch.
1036   *  
1037   *  @see VirtualFileSystemHelper#lastModified( URI )
1038   */
1039  public long lastModified( URI uri )
1040  {
1041    if ( uri == null )
1042    {
1043      throw new NullPointerException( "uri must not be null" );
1044    }
1045    return findHelper( uri ).lastModified( uri );
1046  }
1047
1048
1049  /**
1050   *  Returns an array of {@link URI}s identifying resources in
1051   *  the directory resource indicated by the {@link URI}.  If the specified
1052   *  {@link URI} does not represent a directory, then this method
1053   *  returns <CODE>null</CODE>.  Otherwise, an array of {@link URI}s
1054   *  is returned, one for each file or directory in the directory.
1055   *  {@link URI}s representing the directory itself or its parent are
1056   *  not included in the result.  There is no guarantee that the
1057   *  {@link URI}s will be returned in any particular order.<P>
1058   *  
1059   *  @param uri the uri of a directory resource. Must not be null.
1060   *  @return  An array of {@link URI}s naming the files and directories
1061   *  in the directory indicated by the {@link URI}.  The array will
1062   *  be empty if the directory is empty.  Returns <CODE>null</CODE>
1063   *  if the {@link URI} does not represent a directory or if an
1064   *  I/O error occurs.
1065   *  
1066   *  @see VirtualFileSystemHelper#list( URI )
1067   */
1068  public URI[] list( URI uri )
1069  {
1070    if ( uri == null )
1071    {
1072      throw new NullPointerException( "uri must not be null" );
1073    }
1074    return findHelper( uri ).list( uri );
1075  }
1076
1077
1078  /**
1079   *  Returns an array of {@link URI}s identifying resources in
1080   *  the directory resource indicated by the {@link URI}; the specified
1081   *  {@link URIFilter} is applied to determine which {@link URI}s will
1082   *  be returned.  If the specified {@link URI} does not represent a
1083   *  directory, then this method returns <CODE>null</CODE>.
1084   *  Otherwise, an array of {@link URI}s is returned, one for each file
1085   *  or directory in the directory that is accepted by the specified
1086   *  filter.  {@link URI}s representing the directory itself or its
1087   *  parent are not included in the result.  There is no guarantee that
1088   *  the {@link URI}s will occur in any particular order.<P>
1089   *
1090   *  If the specified {@link URIFilter} is <CODE>null</CODE> then
1091   *  no filtering behavior is done.
1092   *
1093   *  @param uri the uri of a directory resource. Must not be null.
1094   *  @param filter a filter to use when retrieving the child resources
1095   *    of the specified uri. May be null, in which case no filtering is done.
1096   *  @return  An array of {@link URI}s naming the files and directories
1097   *  in the directory indicated by the {@link URI} that are accepted
1098   *  by the specified {@link URIFilter}.  The array will be empty if
1099   *  the directory is empty.  Returns <CODE>null</CODE> if the
1100   *  {@link URI} does not represent a directory or if an I/O error
1101   *  occurs.
1102   *  
1103   *  @see VirtualFileSystemHelper#list( URI, URIFilter )
1104   */
1105  public URI[] list( URI uri, URIFilter filter )
1106  {
1107    if ( uri == null )
1108    {
1109      throw new NullPointerException( "uri must not be null" );
1110    }
1111    return findHelper( uri ).list( uri, filter );
1112  }
1113
1114  /**
1115   *  Returns an array of {@link URI}s that represent the root resources
1116   *  available.  The determination of the roots is delegated to each 
1117   *  registered {@link VirtualFileSystemHelper}.
1118   *  
1119   *  @return an array of root resources retrieved from each registered 
1120   *      {@link VirtualFileSystemHelper}. 
1121   */
1122  public URI[] listRoots()
1123  {
1124    final ArrayList roots = new ArrayList( 40 );
1125    final ArrayList helperKeys = new ArrayList( _helpers.keySet() );
1126    Collections.sort( helperKeys );
1127    for ( Iterator iter = helperKeys.iterator(); iter.hasNext(); )
1128    {
1129      final String scheme = iter.next().toString();
1130      final VirtualFileSystemHelper helper = 
1131        (VirtualFileSystemHelper) _helpers.get( scheme );
1132      final URI[] schemeRoots = helper.listRoots();
1133      if ( schemeRoots != null )
1134      {
1135        roots.addAll( Arrays.asList( schemeRoots ) );
1136      }
1137    }
1138    return (URI[]) roots.toArray( new URI[ roots.size() ] );
1139  }
1140
1141  /**
1142   *  Creates the directory indicated by the {@link URI}.<P>
1143   *
1144   *  @param uri the uri of a potential directory resource. Must not be null.
1145   *  @return  <CODE>true</CODE> if and only if the directory was
1146   *  created; <CODE>false</CODE> otherwise.
1147   *  @see VirtualFileSystemHelper#mkdir( URI )
1148   */
1149  public boolean mkdir( URI uri )
1150  {
1151    if ( uri == null )
1152    {
1153      throw new NullPointerException( "uri must not be null" );
1154    }
1155    return findHelper( uri ).mkdir( uri );
1156  }
1157
1158
1159  /**
1160   *  Creates the directory indicated by the specified {@link URI}
1161   *  including any necessary but nonexistent parent directories.  Note
1162   *  that if this operation fails, it may have succeeded in creating
1163   *  some of the necessary parent directories.  This method returns
1164   *  <CODE>true</CODE> if the directory was created along with all
1165   *  necessary parent directories or if the directories already
1166   *  exist; it returns <CODE>false</CODE> otherwise.
1167   *
1168   *  @param uri the uri of a potential directory resource. Must not be null.
1169   *  @return  <CODE>true</CODE> if all directories were created
1170   *  successfully or exist; <CODE>false</CODE> if there was a
1171   *  failure somewhere.
1172   *  Note that even if <CODE>false</CODE> is returned, some directories
1173   *  may still have been created.
1174   *  @see VirtualFileSystemHelper#mkdirs( URI )
1175   */
1176  public boolean mkdirs( URI uri )
1177  {
1178    if ( uri == null )
1179    {
1180      throw new NullPointerException( "uri must not be null" );
1181    }
1182  
1183    return findHelper( uri ).mkdirs( uri );
1184  }
1185
1186  /**
1187   * Creates a new empty temporary file in the specified directory using the
1188   * given prefix and suffix strings to generate its name.
1189   *
1190   * @param prefix The prefix string to be used in generating the file's name;
1191   * must be at least three characters long
1192   *
1193   * @param suffix The directory in which the file is to be created, or
1194   * null if the default temporary-file directory is to be used
1195   *
1196   * @param directory The directory in which the file is to be created,
1197   * or null if the default temporary-file directory is to be used
1198   *
1199   * @return The <CODE>URI</CODE> to the temporary file.
1200   * @throws java.io.IOException if an error occurs creating the temporary
1201   *    file.
1202   *    
1203   * @see VirtualFileSystemHelper#createTempFile( String, String, URI )
1204   */
1205  public URI createTempFile( String prefix,
1206                                    String suffix,
1207                                    URI directory ) throws IOException
1208  {
1209    if ( prefix == null || prefix.length() < 3 )
1210    {
1211      throw new IllegalArgumentException( 
1212        "prefix must be at least three characters long" );
1213    }
1214    
1215    return ( directory != null 
1216            ? findHelper( directory ).createTempFile( prefix, suffix, directory )
1217            : findHelper( FILE_SCHEME ).createTempFile( prefix, suffix, null ) );
1218  }
1219
1220
1221  /**
1222   *  Opens an {@link InputStream} for the location indicated by the
1223   *  specified {@link URI}.
1224   *
1225   *  @param  uri  An {@link InputStream} is opened on the given
1226   *  {@link URI}.  Must not be null.
1227   *
1228   *  @return The {@link InputStream} associated with the {@link URI}.
1229   *
1230   *  @exception java.io.FileNotFoundException if the resource at the
1231   *  specified URI does not exist.
1232   *
1233   *  @exception IOException  if an I/O error occurs when trying to open
1234   *  the {@link InputStream}.
1235   *
1236   *  @exception  java.net.UnknownServiceException (a runtime exception) if
1237   *  the scheme does not support opening an {@link InputStream}.
1238   *  
1239   *  @see VirtualFileSystemHelper#openInputStream( URI )
1240   */
1241  public InputStream openInputStream( URI uri ) throws IOException
1242  {
1243    if ( uri == null )
1244    {
1245      throw new NullPointerException( "uri must not be null" );
1246    }
1247    return findHelper( uri ).openInputStream( uri );
1248  }
1249
1250
1251  /**
1252   *  Opens an {@link OutputStream} on the {@link URI}.  If the file
1253   *  does not exist, the file should be created.  If the directory
1254   *  path to the file does not exist, all necessary directories
1255   *  should be created.
1256   *
1257   *  @param  uri  An {@link OutputStream} is opened on the given
1258   *  {@link URI}.  Must not be null.
1259   *
1260   *  @return The {@link OutputStream} associated with the {@link URI}.
1261   *
1262   *  @exception IOException if an I/O error occurs when trying to open
1263   *  the {@link OutputStream}.
1264   *
1265   *  @exception java.net.UnknownServiceException (a runtime exception)
1266   *  if the scheme does not support opening an {@link OutputStream}.
1267   *  
1268   *  @see VirtualFileSystemHelper#openOutputStream( URI )
1269   */
1270  public OutputStream openOutputStream( URI uri ) throws IOException
1271  {
1272    if ( uri == null )
1273    {
1274      throw new NullPointerException( "uri must not be null" );
1275    }
1276    return findHelper( uri ).openOutputStream( uri );
1277  }
1278
1279
1280  /**
1281   *  Renames the resource indicated by the first {@link URI} to the
1282   *  name indicated by the second {@link URI}.<P>
1283   *
1284   *  If either {@link URI} parameter is <CODE>null</CODE> or if both
1285   *  of the specified {@link URI} parameters refer to the same
1286   *  resource, then the rename is not attempted and failure is
1287   *  returned.<P>
1288   *
1289   *  If the specified {@link URI} parameters do not have the same
1290   *  scheme, then the <CODE>VirtualFileSystem</CODE> handles the rename
1291   *  by first copying the resource to the destination with {@link
1292   *  VirtualFileSystem#copy(URI, URI)} and then deleting the original
1293   *  resource with {@link VirtualFileSystem#delete(URI)}; if either
1294   *  operation fails, then failure is returned.<P>
1295   *
1296   *  Otherwise, the scheme helper is called to perform the actual
1297   *  rename operation.  Scheme helper implementations may therefore
1298   *  assume that both {@link URI} parameters are not
1299   *  <CODE>null</CODE>, do not refer to the same resource, and have
1300   *  the same scheme.<P>
1301   *
1302   *  If the original {@link URI} refers to a nonexistent resource,
1303   *  then the scheme helper implementations should return failure.
1304   *  It is left up to the scheme helper implementations to decide
1305   *  whether to overwrite the destination or return failure if the
1306   *  destination {@link URI} refers to an existing resource.
1307   *
1308   *  @param oldURI the {@link URI} of the original resource
1309   *  @param newURI the desired {@link URI} for the renamed resource
1310   *
1311   *  @return <CODE>true</CODE> if and only if the resource is
1312   *  successfully renamed; <CODE>false</CODE> otherwise.
1313   */
1314  public boolean renameTo( URI oldURI, URI newURI )
1315  {
1316    if ( oldURI == null || newURI == null )
1317    {
1318      return false;
1319    }
1320    else if ( oldURI == newURI || oldURI.equals( newURI ) )
1321    {
1322      return false;
1323    }
1324    else if ( !schemesAreEqual( oldURI, newURI ) )
1325    {
1326      try
1327      {
1328        copy( oldURI, newURI );
1329      }
1330      catch ( IOException e )
1331      {
1332        return false;
1333      }
1334
1335      return delete( oldURI );
1336    }
1337    return findHelper( oldURI ).renameTo( oldURI, newURI );
1338  }
1339
1340
1341  /**
1342   *  Sets the last-modified timestamp of the resource indicated by
1343   *  the {@link URI} to the time specified by <CODE>time</CODE>.
1344   *  The time is specified in the number of milliseconds since
1345   *  the epoch (00:00:00 GMT Jan 1, 1970).  The return value
1346   *  indicates whether or not the setting of the timestamp
1347   *  succeeded.
1348   *  
1349   *  @param uri the uri of the resource to set the last modified timesamp of.
1350   *    Must not be null.
1351   *  @param time a last modified timestamp in milliseconds since the epoch.
1352   *  
1353   *  @return true if the last modified time of the specified resource was
1354   *    successful.
1355   *    
1356   *  @see VirtualFileSystemHelper#setLastModified( URI, long )
1357   */
1358  public boolean setLastModified( URI uri, long time )
1359  {
1360    if ( uri == null )
1361    {
1362      throw new NullPointerException( "uri must not be null" );
1363    }
1364  
1365    return findHelper( uri ).setLastModified( uri, time );
1366  }
1367
1368
1369  /**
1370   *  Sets the read-only status of the resource indicated by the
1371   *  {@link URI} according to the specified <CODE>readOnly</CODE>
1372   *  flag.  The return value indicates whether or not the setting
1373   *  of the read-only flag succeeded.
1374   *  
1375   *  @param uri the uri of a resource to set the read only flag of. Must not
1376   *      be null.
1377   *  @param readOnly whether the specified resource should be read only.
1378   *  
1379   *  @return true if the read only flag on the specified resource was
1380   *    successfully set.
1381   *  
1382   *  @see VirtualFileSystemHelper#setReadOnly( URI, boolean )
1383   */
1384  public boolean setReadOnly( URI uri, boolean readOnly )
1385  {
1386    if ( uri == null )
1387    {
1388      throw new NullPointerException( "uri must not be null" );
1389    }
1390  
1391    return findHelper( uri ).setReadOnly( uri, readOnly );
1392  }
1393
1394
1395  /**
1396   * Returns a displayable form of the complete {@link URI}.
1397   * 
1398   * @param uri the uri of a resource. Must not be null.
1399   * @return a human-readable string representing the resource.
1400   * 
1401   * @see VirtualFileSystemHelper#toDisplayString( URI )
1402   */
1403  public String toDisplayString( URI uri )
1404  {
1405    if ( uri == null )
1406    {
1407      throw new NullPointerException( "uri must not be null" );
1408    }
1409  
1410    return findHelper( uri ).toDisplayString( uri );
1411  }
1412
1413
1414  /**
1415   * Converts a uri to a relative spec.
1416   * 
1417   * @param uri the uri to convert. Must not be null.
1418   * @param base the base uri.
1419   * @return the relative spec of <tt>uri</tt>
1420   * @see VirtualFileSystemHelper#toRelativeSpec( URI, URI )
1421   */
1422  public String toRelativeSpec( URI uri, URI base )
1423  {
1424    if ( uri == null )
1425    {
1426      throw new NullPointerException( "uri must not be null" );
1427    }
1428    if ( base == null )
1429    {
1430      throw new NullPointerException( "base must not be null" );
1431    }  
1432    return findHelper( uri ).toRelativeSpec( uri, base );
1433  }
1434
1435
1436  /**
1437   * Converts a uri to a relative spec.
1438   * 
1439   * @param uri the uri to convert. Must not be null.
1440   * @param base the base uri.
1441   * @param mustConsumeBase If <CODE>mustConsumeBase</CODE> is 
1442   *  <CODE>false</CODE>, then
1443   *  this method will return a non-<CODE>null</CODE> relative
1444   *  spec regardless of how much of the base {@link URI} is
1445   *  consumed during the determination.
1446   * @return the relative spec of <tt>uri</tt>
1447   * @see VirtualFileSystemHelper#toRelativeSpec( URI, URI )
1448   */
1449  public String toRelativeSpec( URI uri, URI base, boolean mustConsumeBase )
1450  {
1451    return findHelper( uri ).toRelativeSpec( uri, base, mustConsumeBase );
1452  }
1453
1454
1455  /**
1456   * This method gets the base directory fully containing the relative path.
1457   * 
1458   *  @param uri the uri to get the base directory for.
1459   *  @param relativeSpec a relative path.
1460   *  @return the base directory fully containing the relative path.
1461   *  @see VirtualFileSystemHelper#getBaseParent(URI, String)
1462   */
1463  public URI getBaseParent( URI uri, String relativeSpec )
1464  {
1465    return findHelper( uri ).getBaseParent( uri, relativeSpec );
1466  }
1467
1468  /**
1469   * Get a {@link URL} from a {@link URI}.
1470   * 
1471   * @param uri the URI to convert to a URL.
1472   * @return a url representation of the uri.
1473   * 
1474   * @throws MalformedURLException if the uri could not be converted into
1475   *    a URL.
1476   */
1477  public URL toURL( URI uri ) throws MalformedURLException
1478  {
1479    return findHelper( uri ).toURL( uri );    
1480  }
1481  //--------------------------------------------------------------------------
1482  //  implementation details...
1483  //--------------------------------------------------------------------------
1484  /**
1485   *  Compares the schemes of the specified {@link URI} parameters
1486   *  for equality.  The schemes are considered to be unequal if
1487   *  either {@link URI} is <CODE>null</CODE>, even when both {@link
1488   *  URI} parameters are <CODE>null</CODE>.
1489   */
1490  private static boolean schemesAreEqual( URI uri1, URI uri2 )
1491  {
1492    if ( uri1 == null || uri2 == null )
1493    {
1494      return false;
1495    }
1496    final String p1 = uri1.getScheme();
1497    final String p2 = uri2.getScheme();
1498    return p1.equals( p2 );
1499  }
1500
1501  /**
1502   * Gets the VirtualFileSystem implementation for this IDE.
1503   * 
1504   * @return the virtual file system implementation for this ide.
1505   */
1506  public static VirtualFileSystem getVirtualFileSystem()
1507  {
1508    try
1509    {
1510      return (VirtualFileSystem) getService( VirtualFileSystem.class );
1511    }
1512    catch ( ProviderNotFoundException lnfe )
1513    {
1514      lnfe.printStackTrace();
1515      throw new IllegalStateException( "No virtual file system" );
1516    }
1517  }
1518
1519  public VirtualFileSystem(){}
1520}