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

Quick Search    Search Deep

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


1   /*
2    * @(#)VirtualFileSystemHelper.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.IOException;
15  import java.io.InputStream;
16  import java.io.OutputStream;
17  import java.net.MalformedURLException;
18  import java.net.URI;
19  import java.net.URL;
20  import java.net.UnknownServiceException;
21  import java.util.ArrayList;
22  import java.util.StringTokenizer;
23  
24  /**
25   *  The {@link VirtualFileSystemHelper} class specifies the
26   *  {@link VirtualFileSystem} operations that may have scheme-specific
27   *  handling.  By default, the {@link VirtualFileSystem} delegates its
28   *  operations to {@link VirtualFileSystemHelper}.  However, a subclass of
29   *  {@link VirtualFileSystemHelper} can be registered with the
30   *  {@link VirtualFileSystem} to handle the {@link VirtualFileSystem} operations
31   *  for a particular scheme.  A helper class is registered through the
32   *  {@link VirtualFileSystem#registerHelper(String, VirtualFileSystemHelper)}
33   *  method.<P>
34   *
35   *  <EM>Special implementation note</EM>:  classes that extend
36   *  {@link VirtualFileSystemHelper} must be completely thread-safe because a
37   *  single instance of each registered helper is held by the
38   *  {@link VirtualFileSystem} and reused for all threads.
39   *
40   *  @see VirtualFileSystem
41   */
42  public class VirtualFileSystemHelper
43  {
44    //--------------------------------------------------------------------------
45    //  constructors...
46    //--------------------------------------------------------------------------
47    protected VirtualFileSystemHelper()
48    {
49      //  NOP.
50    }
51  
52    //--------------------------------------------------------------------------
53    //  VirtualFileSystemHelper public API...
54    //--------------------------------------------------------------------------
55    /**
56     *  Returns a canonical form of the {@link URI}, if one is available.
57     *  <P>
58     *
59     *  The default implementation just returns the specified {@link URI}
60     *  as-is.
61     */
62    public URI canonicalize( URI uri ) throws IOException
63    {
64      return uri;
65    }
66  
67    /**
68     *  Tests whether the application can read the resource at the
69     *  specified {@link URI}.
70     *
71     *  @return  <CODE>true</CODE> if and only if the specified
72     *  {@link URI} points to a resource that exists <EM>and</EM> can be
73     *  read by the application; <CODE>false</CODE> otherwise.
74     */
75    public boolean canRead( URI uri )
76    {
77      return false;
78    }
79  
80    /**
81     *  Tests whether the application can modify the resource at the
82     *  specified {@link URI}.
83     *
84     *  @return  <CODE>true</CODE> if and only if the specified
85     *  {@link URI} points to a file that exists <EM>and</EM> the
86     *  application is allowed to write to the file; <CODE>false</CODE>
87     *  otherwise.
88     */
89    public boolean canWrite( URI uri )
90    {
91      return false;
92    }
93  
94    /**
95     * Tests whether the application can create the resource at the specified
96     * {@link URI}. This method tests that all components of the path can
97     * be created. If the resource pointed by the {@link URI} is read-only,
98     * this method returns <CODE>false</CODE>.
99     *
100    * @return <CODE>true</CODE> if the resource at the specified {@link URI}
101    * exists or can be created; <CODE>false</CODE> otherwise.
102    */
103   public boolean canCreate( URI uri )
104   {
105     return true;
106   }
107 
108   /**
109    * Tests whether the specified {@link URI} is valid. If the resource
110    * pointed by the {@link URI} exists the method returns <CODE>true</CODE>.
111    * If the resource does not exist, the method tests that all components
112    * of the path can be created.
113    *
114    * @return <CODE>true</CODE> if the {@link URI} is valid.
115    */
116   public boolean isValid( URI uri )
117   {
118     if ( exists( uri ) )
119     {
120       return true;
121     }
122 
123     return canCreate( uri );
124   }
125 
126   /**
127    *  Takes the given {@link URI} and checks if its {@link #toString()}
128    *  representation ends with the specified <CODE>oldSuffix</CODE>.  If
129    *  it does, the suffix is replaced with <CODE>newSuffix</CODE>.  Both
130    *  suffix parameters must include the leading dot ('.') if the dot is
131    *  part of the suffix.  If the specified {@link URI} does not end
132    *  with the <CODE>oldSuffix</CODE>, then the <CODE>newSuffix</CODE>
133    *  is simply appended to the end of the original {@link URI}.
134    */
135   public URI convertSuffix( URI uri, String oldSuffix, String newSuffix )
136   {
137     final String path = uri.getPath();
138     final String newPath;
139     if ( path.endsWith( oldSuffix ) )
140     {
141       final int suffixIndex = path.length() - oldSuffix.length();
142       newPath = path.substring( 0, suffixIndex ) + newSuffix;
143     }
144     else
145     {
146       newPath = path + newSuffix;
147     }
148 
149     return replacePathPart( uri, newPath );
150   }
151 
152   /**
153    *  Deletes the content pointed to by the specified {@link URI}.  If
154    *  the content is a file (or analogous to a file), then the file is
155    *  removed from its directory (or container).  If the content is a
156    *  directory (or analogous to a directory), then the directory is
157    *  removed only if it is empty (i.e. contains no other files or
158    *  directories).<P>
159    *
160    *  The default implementation simply returns <CODE>false</CODE>
161    *  without doing anything.
162    *
163    *  @return <CODE>true</CODE> if and only if the file or directory
164    *  is successfully deleted; <CODE>false</CODE> otherwise.
165    */
166   public boolean delete( URI uri )
167   {
168     return false;
169   }
170 
171   /**
172    *  This method ensures that the specified {@link URI} ends with the
173    *  specified <CODE>suffix</CODE>.  The suffix does not necessarily
174    *  have to start with a ".", so if a leading "." is required, the
175    *  specified suffix must contain it -- e.g. ".java", ".class".<P>
176    *
177    *  If the {@link URI} already ends in the specified suffix, then
178    *  the {@link URI} itself is returned.  Otherwise, a new
179    *  {@link URI} is created with the the specified suffix appended
180    *  to the original {@link URI}'s path part, and the new {@link URI}
181    *  is returned.<P>
182    *
183    *  The default implementation first checks with {@link
184    *  #hasSuffix(URI, String)} to see if the {@link URI} already ends
185    *  with the specified suffix.  If not, the suffix is simply appended
186    *  to the path part of the {@link URI}, and the new {@link URI} is
187    *  returned.
188    *
189    *  @return An {@link URI}, based on the specified {@link URI}, whose
190    *  path part ends with the specified suffix.
191    *
192    *  @exception NullPointerException if either the specified {@link
193    *  URI} or <CODE>suffix</CODE> is <CODE>null</CODE>.  The caller is
194    *  responsible for checking that they are not <CODE>null</CODE>.
195    */
196   public URI ensureSuffix( URI uri, String suffix )
197   {
198     if ( hasSuffix( uri, suffix ) )
199     {
200       return uri;
201     }
202     final String path = uri.getPath();
203     return replacePathPart( uri, path + suffix );
204   }
205 
206   /**
207    *  Compares the specified {@link URI} objects to determine whether
208    *  they point to the same resource.  This method returns
209    *  <CODE>true</CODE> if the {@link URI}s point to the same resource
210    *  and returns <CODE>false</CODE> if the {@link URI}s do not point
211    *  to the same resource.<P>
212    *
213    *  This method and all subclass implementations can assume that both
214    *  {@link URI} parameters are not <CODE>null</CODE>.  The
215    *  {@link VirtualFileSystem#equals(URI, URI)} method is responsible for
216    *  checking that the two {@link URI}s are not <CODE>null</CODE>.<P>
217    *
218    *  It can also be assumed that both {@link URI} parameters have
219    *  the same scheme and that the scheme is appropriate for this
220    *  <CODE>VirtualFileSystemHelper</CODE>.  This determination is also
221    *  the responsibility of {@link VirtualFileSystem#equals(URI, URI)}.<P>
222    *
223    *  The default implementation for this method delegates to
224    *  {@link URI#equals(Object)}.
225    */
226   public boolean equals( URI uri1, URI uri2 )
227   {
228     return uri1.equals( uri2 );
229   }
230 
231   /**
232    *  Tests whether a resource at the specified {@link URI} location
233    *  currently exists.  The test for existence only checks the actual
234    *  location and does not check any in-memory caches.  
235    *
236    *  The default implementation simply returns <CODE>false</CODE>
237    *  without doing anything.
238    *
239    *  @return <CODE>true</CODE> if and only if a resource already exists
240    *  at the specified {@link URI} location; <CODE>false</CODE>
241    *  otherwise.
242    */
243   public boolean exists( URI uri )
244   {
245     return false;
246   }
247 
248   /**
249    *  Returns the name of the file contained by the {@link URI}, not
250    *  including any scheme, authority, directory path, query, or
251    *  fragment.  This simply returns the simple filename.  For example,
252    *  if you pass in an {@link URI} whose string representation is:
253    *
254    *  <BLOCKQUOTE><CODE>
255    *    scheme://userinfo@host:1010/dir1/dir2/file.ext?query#fragment
256    *  </CODE></BLOCKQUOTE>
257    *
258    *  the returned value is "<CODE>file.ext</CODE>" (without the
259    *  quotes).<P>
260    *
261    *  @return The simple filename of the specified {@link URI}.  This
262    *  value should only be used for display purposes and not for opening
263    *  streams or otherwise trying to locate the document.
264    */
265   public String getFileName( URI uri )
266   {
267     if ( uri == null )
268     {
269       return "";  //NOTRANS
270     }
271 
272     final String path = uri.getPath();
273     if ( path == null || path.length() == 0 )
274     {
275       return "";  //NOTRANS
276     }
277     if ( path.equals( "/" ) )  //NOTRANS
278     {
279       return "/";  //NOTRANS
280     }
281     final int lastSep = path.lastIndexOf( '/' );  //NOTRANS
282     if ( lastSep == path.length() - 1 )
283     {
284       final int lastSep2 = path.lastIndexOf( '/', lastSep - 1 );  //NOTRANS
285       return path.substring( lastSep2 + 1, lastSep );
286     }
287     else
288     {
289       return path.substring( lastSep + 1 );
290     }
291   }
292 
293   /**
294    *  Returns the number of bytes contained in the resource that the
295    *  specified {@link URI} points to.  If the length cannot be
296    *  determined, <CODE>-1</CODE> is returned.<P>
297    *
298    *  The default implementation returns -1.
299    *
300    *  @return the size in bytes of the document at the specified
301    *  {@link URI}.
302    */
303   public long getLength( URI uri )
304   {
305     return -1;
306   }
307 
308   /**
309    *  Returns the name of the file contained by the {@link URI}, not
310    *  including any scheme, authority, directory path, file extension,
311    *  query, or fragment.  This simply returns the simple filename.  For
312    *  example, if you pass in an {@link URI} whose string representation
313    *  is:
314    *
315    *  <BLOCKQUOTE><CODE>
316    *    scheme://userinfo@host:1010/dir1/dir2/file.ext1.ext2?query#fragment
317    *  </CODE></BLOCKQUOTE>
318    *
319    *  the returned value is "<CODE>file</CODE>" (without the quotes).<P>
320    *
321    *  The returned file name should only be used for display purposes
322    *  and not for opening streams or otherwise trying to locate the
323    *  resource indicated by the {@link URI}.<P>
324    *
325    *  The default implementation first calls {@link #getFileName(URI)} to
326    *  get the file name part.  Then all characters starting with the
327    *  first occurrence of '.' are removed.  The remaining string is then
328    *  returned.
329    */
330   public String getName( URI uri )
331   {
332     final String fileName = getFileName( uri );
333     final int firstDot = fileName.indexOf( '.' );
334     return firstDot > 0 ? fileName.substring( 0, firstDot ) : fileName;
335   }
336 
337   /**
338    *  Returns the {@link URI} representing the parent directory of
339    *  the specified {@link URI}.  If there is no parent directory,
340    *  then <CODE>null</CODE> is returned.<P>
341    *
342    *  The default implementation returns the value of invoking
343    *  <CODE>uri.resolve( ".." )</CODE>.
344    */
345   public URI getParent( URI uri )
346   {
347     return uri != null ? uri.resolve( ".." ) : null; // NOTRANS
348   }
349 
350   /**
351    *  Returns the path part of the {@link URI}.  The returned string
352    *  is acceptable to use in one of the {@link URIFactory} methods
353    *  that takes a path.<P>
354    *
355    *  The default implementation delegates to {@link URI#getPath()}.
356    */
357   public String getPath( URI uri )
358   {
359     return uri.getPath();
360   }
361 
362   /**
363    *  Returns the path part of the {@link URI} without the last file
364    *  extension.  To clarify, the following examples demonstrate the
365    *  different cases:
366    *
367    *  <TABLE BORDER COLS=2 WIDTH="100%">
368    *    <TR>
369    *      <TD><CENTER>Path part of input {@link URI}</CENTER></TD>
370    *      <TD><CENTER>Output {@link String}</CENTER</TD>
371    *    </TR>
372    *    <TR>
373    *      <TD><CODE>/dir/file.ext</CODE></TD>
374    *      <TD><CODE>/dir/file</CODE></TD>
375    *    <TR>
376    *    <TR>
377    *      <TD><CODE>/dir/file.ext1.ext2</CODE></TD>
378    *      <TD><CODE>/dir/file.ext1</CODE></TD>
379    *    <TR>
380    *    <TR>
381    *      <TD><CODE>/dir1.ext1/dir2.ext2/file.ext1.ext2</CODE></TD>
382    *      <TD><CODE>/dir1.ext1/dir2.ext2/file.ext1</CODE></TD>
383    *    <TR>
384    *    <TR>
385    *      <TD><CODE>/file.ext</CODE></TD>
386    *      <TD><CODE>/file</CODE></TD>
387    *    <TR>
388    *    <TR>
389    *      <TD><CODE>/dir.ext/file</CODE></TD>
390    *      <TD><CODE>/dir.ext/file</CODE></TD>
391    *    <TR>
392    *    <TR>
393    *      <TD><CODE>/dir/file</CODE></TD>
394    *      <TD><CODE>/dir/file</CODE></TD>
395    *    <TR>
396    *    <TR>
397    *      <TD><CODE>/file</CODE></TD>
398    *      <TD><CODE>/file</CODE></TD>
399    *    <TR>
400    *    <TR>
401    *      <TD><CODE>/.ext</CODE></TD>
402    *      <TD><CODE>/</CODE></TD>
403    *    <TR>
404    *  </TABLE>
405    *
406    *  The default implementation gets the path from {@link
407    *  #getPath(URI)} and then trims off all of the characters beginning
408    *  with the last "." in the path, if and only if the last "." comes
409    *  after the last "/" in the path.  If the last "." comes before
410    *  the last "/" or if there is no "." at all, then the entire path
411    *  is returned.
412    */
413   public String getPathNoExt( URI uri )
414   {
415     final String path = getPath( uri );
416     final int lastSlash = path.lastIndexOf( "/" );  //NOTRANS
417     final int lastDot = path.lastIndexOf( "." );  //NOTRANS
418     if ( lastDot <= lastSlash )
419     {
420       //  When the lastDot < lastSlash, it means that one of the
421       //  directories has an extension, but the filename itself has
422       //  no extension.  In this case, returning the whole path is
423       //  the correct behavior.
424       //
425       //  The only time that lastDot and lastSlash can be equal occurs
426       //  when both of them are -1.  In that case, returning the whole
427       //  path is the correct behavior.
428       return path;
429     }
430     //  At this point, we know that lastDot must be non-negative, so
431     //  we can return the whole path string up to the last dot.
432     return path.substring( 0, lastDot );
433   }
434 
435   /**
436    *  Returns the platform-dependent String representation of the
437    *  {@link URI}; the returned string should be considered acceptable
438    *  for users to read.  In general, the returned string should omit
439    *  as many parts of the {@link URI} as possible.  For the "file"
440    *  scheme, therefore, the platform pathname should just be the
441    *  pathname alone (no scheme) using the appropriate file separator
442    *  character for the current platform.  For other schemes, it may
443    *  be necessary to reformat the {@link URI} string into a more
444    *  human-readable form.  That decision is left to each
445    *  {@link VirtualFileSystemHelper} implementor.<P>
446    *
447    *  The default implementation returns <CODE>uri.toString()</CODE>.
448    *  If the {@link URI} is <CODE>null</CODE>, the empty string is
449    *  returned.
450    *
451    *  @return  The path portion of the specified {@link URI} in
452    *  platform-dependent notation.  This value should only be used for
453    *  display purposes and not for opening streams or otherwise trying
454    *  to locate the document.
455    */
456   public String getPlatformPathName( URI uri )
457   {
458     return uri != null ? uri.toString() : "";  //NOTRANS
459   }
460 
461   /**
462    *  If a dot ('.') occurs in the filename part of the path part of
463    *  the {@link URI}, then all of the text starting at the last dot is
464    *  returned, including the dot.  If the last dot is also the last
465    *  character in the path, then the dot by itself is returned.  If
466    *  there is no dot in the file name (even if a dot occurs elsewhere
467    *  in the path), then the empty string is returned.<P>
468    *
469    *  Examples:
470    *  <UL>
471    *    <LI>file:/home/jsr198/foo.txt returns ".txt"
472    *    <LI>file:/home/jsr198/foo.txt.bak returns ".bak"
473    *    <LI>file:/home/jsr198/foo returns ""
474    *    <LI>file:/home/jsr198/foo. returns "."
475    *    <LI>file:/home/jsr198/foo/ returns ""
476    *    <LI>file:/home/jsr198.1/foo returns ""
477    *    <LI>file:/home/jsr198.1/foo.txt returns ".txt"
478    *  </UL>
479    */
480   public String getSuffix( URI uri )
481   {
482     final String path = uri.getPath();
483     final int lastDot = path.lastIndexOf( '.' );
484     final int lastSlash = path.lastIndexOf( '/' );
485 
486     return ( lastDot >= 0 && lastDot > lastSlash ) ? path.substring( lastDot ) : "";  //NOTRANS
487   }
488 
489   /**
490    *  Returns <CODE>true</CODE> if the path part of the {@link URI}
491    *  ends with the given <CODE>suffix</CODE> String.  The suffix can
492    *  be any String and doesn't necessarily have to be one that begins
493    *  with a dot ('.').  If you are trying to test whether the path
494    *  part of the {@link URI} ends with a particular file extension,
495    *  then the <CODE>suffix</CODE> parameter must begin with a '.'
496    *  character to ensure that you get the right return value.
497    */
498   public boolean hasSuffix( URI uri, String suffix )
499   {
500     final String path = uri.getPath();
501     return path.endsWith( suffix );
502   }
503 
504   /**
505    *  Returns <CODE>true</CODE> if <CODE>uri1</CODE> represents a
506    *  a directory and <CODE>uri2</CODE> points to a location within
507    *  <CODE>uri1</CODE>'s directory tree.
508    */
509   public boolean isBaseURIFor( URI uri1, URI uri2 )
510   {
511     if ( !isDirectoryPath( uri1 ) )
512     {
513       return false;
514     }
515 
516     final String uri1String = uri1.toString();
517     final String uri2String = uri2.toString();
518     return uri2String.startsWith( uri1String );
519   }
520 
521   /**
522    *  Tests whether the location indicated by the {@link URI} is
523    *  a directory.<P>
524    *
525    *  The default implementation always returns <CODE>false</CODE>.
526    *
527    *  @return  <CODE>true</CODE> if and only if the location indicated
528    *  by the {@link URI} exists <EM>and</EM> is a directory;
529    *  <CODE>false</CODE> otherwise.
530    */
531   public boolean isDirectory( URI uri )
532   {
533     return false;
534   }
535 
536   /**
537    *  Tests whether the location indicated by the {@link URI}
538    *  represents a directory path.  The directory path specified by
539    *  the {@link URI} need not exist.<P>
540    *
541    *  This method is intended to be a higher performance version of
542    *  the {@link #isDirectory(URI)} method.  Implementations of this
543    *  method should attempt to ascertain whether the specified {@link
544    *  URI} represents a directory path by simply examining the {@link
545    *  URI} itself.  Time consuming i/o operations should be
546    *  avoided.<P>
547    *
548    *  The default implementation returns <CODE>true</CODE> if the path
549    *  part of the {@link URI} ends with a '/' and the query and ref
550    *  parts of the {@link URI} are null.
551    *
552    *  @return <CODE>true</CODE> if the location indicated by the
553    *  {@link URI} represents a directory path; the directory path need
554    *  not exist.
555    */
556   public boolean isDirectoryPath( URI uri )
557   {
558     return ( uri != null &&
559              uri.getPath().endsWith( "/" ) &&  //NOTRANS
560              uri.getQuery() == null &&
561              uri.getFragment() == null );
562   }
563 
564   /**
565    *  Tests whether the resource indiciated by the {@link URI} is a
566    *  hidden file.  The exact definition of <EM>hidden</EM> is
567    *  scheme-dependent and possibly system-dependent.  On UNIX
568    *  systems, a file is considered to be hidden if its name begins
569    *  with a period character ('.').  On Win32 systems, a file is
570    *  considered to be hidden if it has been marked as such in the
571    *  file system.<P>
572    *
573    *  The default implementation always returns <CODE>false</CODE>.
574    */
575   public boolean isHidden( URI uri )
576   {
577     return false;
578   }
579 
580   /**
581    *  Returns <CODE>true</CODE> if the resource is read-only.  A return
582    *  value of <CODE>false</CODE> means that trying to get an
583    *  {@link OutputStream} or trying to write to an {@link OutputStream}
584    *  based on the {@link URI} will cause an IOException to be thrown.
585    *  If the read-only status cannot be determined for some reason,
586    *  this method returns <CODE>true</CODE>.<P>
587    *
588    *  The default implementation always returns <CODE>true</CODE>.  This
589    *  means that all resources are considered read-only unless a
590    *  scheme-specific {@link VirtualFileSystemHelper} is registered for the
591    *  specified {@link URI} and is able to determine that the resource
592    *  underlying the specified {@link URI} is not read-only.
593    */
594   public boolean isReadOnly( URI uri )
595   {
596     return true;
597   }
598 
599   /**
600    *  Tests whether the resource indiciated by the {@link URI} is
601    *  a regular file.  A <EM>regular</EM> is a file that is not a
602    *  directory and, in addition, satisfies other system-dependent
603    *  criteria.<P>
604    *
605    *  The default implementation returns the value of
606    *  <CODE>exists( uri ) && !isDirectory( uri )</CODE>.
607    *
608    *  @return  <CODE>true</CODE> if and only if the resource
609    *  indicated by the {@link URI} exists <EM>and</EM> is a normal
610    *  file.
611    */
612   public boolean isRegularFile( URI uri )
613   {
614     return exists( uri ) && !isDirectory( uri );
615   }
616 
617   /**
618    *  Returns the last modified time of the resource pointed to by the
619    *  {@link URI}.  The returned <CODE>long</CODE> is the number of
620    *  milliseconds since the epoch (00:00:00 GMT Jan 1, 1970).  If no
621    *  timestamp is available or if the {@link URI} passed in is
622    *  <CODE>null</CODE>, <CODE>-1</CODE> is returned.<P>
623    *
624    *  The default implementation returns -1.
625    *
626    *  @return  The last modified time of the document pointed to by the
627    *  specified {@link URI}.
628    */
629   public long lastModified( URI uri )
630   {
631     return -1;
632   }
633 
634   /**
635    *  Returns an array of {@link URI}s naming files and directories in
636    *  the directory indicated by the {@link URI}.  If the specified
637    *  {@link URI} does not represent a directory, then this method
638    *  returns <CODE>null</CODE>.  Otherwise, an array of {@link URI}s
639    *  is returned, one for each file or directory in the directory.
640    *  {@link URI}s representing the directory itself or its parent are
641    *  not included in the result.  There is no guarantee that the
642    *  {@link URI}s will occur in any particular order.<P>
643    *
644    *  The default implementation always returns an empty {@link URI}
645    *  array.
646    *
647    *  @return  An array of {@link URI}s naming the files and directories
648    *  in the directory indicated by the {@link URI}.  The array will
649    *  be empty if the directory is empty.  Returns <CODE>null</CODE>
650    *  if the {@link URI} does not represent a directory or if an
651    *  I/O error occurs.
652    */
653   public URI[] list( URI uri )
654   {
655     return new URI[0];
656   }
657 
658   /**
659    *  Returns an array of {@link URI}s naming files and directories in
660    *  the directory indicated by the {@link URI}; the specified
661    *  {@link URIFilter} is applied to determine which {@link URI}s will
662    *  be returned.  If the specified {@link URI} does not represent a
663    *  directory, then this method returns <CODE>null</CODE>.
664    *  Otherwise, an array of {@link URI}s is returned, one for each file
665    *  or directory in the directory that is accepted by the specified
666    *  filter.  {@link URI}s representing the directory itself or its
667    *  parent are not included in the result.  There is no guarantee that
668    *  the {@link URI}s will occur in any particular order.<P>
669    *
670    *  If the specified {@link URIFilter} is <CODE>null</CODE> then
671    *  no filtering behavior is done.<P>
672    *
673    *  The default implementation calls {@link #list(URI)} first and
674    *  then applies the {@link URIFilter} to the resulting list.
675    *
676    *  @return  An array of {@link URI}s naming the files and directories
677    *  in the directory indicated by the {@link URI} that are accepted
678    *  by the specified {@link URIFilter}.  The array will be empty if
679    *  the directory is empty.  Returns <CODE>null</CODE> if the
680    *  {@link URI} does not represent a directory or if an I/O error
681    *  occurs.
682    */
683   public URI[] list( URI uri, URIFilter filter )
684   {
685     final URI[] list = list( uri );
686     if ( list == null )
687     {
688       return null;
689     }
690     if ( filter == null )
691     {
692       return list;
693     }
694     final ArrayList filteredList = new ArrayList();
695     for ( int i = list.length - 1; i >= 0; i-- )
696     {
697       final URI fileURI = list[i];
698       if ( filter.accept( fileURI ) )
699       {
700         filteredList.add( fileURI );
701       }
702     }
703     return (URI[]) filteredList.toArray( new URI[filteredList.size()] );
704   }
705 
706   /**
707    *  Lists the root "file systems" that are supported by this helper.  The
708    *  returned URIs are used by file chooser dialogs to list all of the
709    *  roots that are available for the user to browse.  If no root file
710    *  systems are supported, this method must return <CODE>null</CODE> or
711    *  an empty URI array.  If the returned array is not empty, then each
712    *  URI contained in it must represent a directory and must not be null.
713    *  <P>
714    *
715    *  The default implementation always returns <CODE>null</CODE>.
716    */
717   public URI[] listRoots()
718   {
719     return null;
720   }
721 
722   /**
723    *  Creates the directory indicated by the {@link URI}.<P>
724    *
725    *  The default implementation always returns <CODE>false</CODE>.
726    *
727    *  @return  <CODE>true</CODE> if and only if the directory was
728    *  created; <CODE>false</CODE> otherwise.
729    */
730   public boolean mkdir( URI uri )
731   {
732     return false;
733   }
734 
735   /**
736    *  Creates the directory indicated by the specified {@link URI}
737    *  including any necessary but nonexistent parent directories.  Note
738    *  that if this operation fails, it may have succeeded in creating
739    *  some of the necessary parent directories.  This method returns
740    *  <CODE>true</CODE> if the directory was created along with all
741    *  necessary parent directories or if the directories already
742    *  exist; it returns <CODE>false</CODE> otherwise.
743    *
744    *  @return  <CODE>true</CODE> if all directories were created
745    *  successfully or exist; <CODE>false</CODE> if there was a
746    *  failure somewhere.
747    *  Note that even if <CODE>false</CODE> is returned, some directories
748    *  may still have been created.
749    */
750   public boolean mkdirs( URI uri )
751   {
752     return false;
753   }
754 
755   /**
756    * Creates a new empty temporary file in the specified directory using the
757    * given prefix and suffix strings to generate its name.
758    *
759    * Subclasses must implement this method given that default implementation
760    * does nothing and returns null.
761    *
762    * @param prefix The prefix string to be used in generating the file's name;
763    * must be at least three characters long
764    *
765    * @param suffix The directory in which the file is to be created, or
766    * null if the default temporary-file directory is to be used
767    *
768    * @param directory The directory in which the file is to be created,
769    * or null if the default temporary-file directory is to be used
770    *
771    * @return The <CODE>URI</CODE> to the temporary file.
772    */
773   public URI createTempFile( String prefix, String suffix, URI directory )
774     throws IOException
775   {
776     return null;
777   }
778 
779   /**
780    *  Opens an {@link InputStream} on the specified {@link URI}.<P>
781    *
782    *  The default implementation throws {@link UnknownServiceException}.
783    *
784    *  @return The {@link InputStream} associated with the {@link URI}.
785    *
786    *  @exception java.io.FileNotFoundException if the resource at the
787    *  specified URI does not exist.
788    *
789    *  @exception IOException  if an I/O error occurs when trying to open
790    *  the {@link InputStream}.
791    *
792    *  @exception  java.net.UnknownServiceException (a runtime exception) if
793    *  the scheme does not support opening an {@link InputStream}.
794    */
795   public InputStream openInputStream( URI uri )
796     throws IOException
797   {
798     throw new UnknownServiceException();
799   }
800 
801   /**
802    *  Opens an {@link OutputStream} on the {@link URI}.  If the file
803    *  does not exist, the file should be created.  If the directory
804    *  path to the file does not exist, all necessary directories
805    *  should be created.<P>
806    *
807    *  The default implementation throws {@link UnknownServiceException}.
808    *
809    *  @param  uri  An {@link OutputStream} is opened on the given
810    *  {@link URI}.  The operation is scheme-dependent.
811    *
812    *  @return The {@link OutputStream} associated with the {@link URI}.
813    *
814    *  @exception IOException if an I/O error occurs when trying to open
815    *  the {@link OutputStream}.
816    *
817    *  @exception java.net.UnknownServiceException (a runtime exception)
818    *  if the scheme does not support opening an {@link OutputStream}.
819    */
820   public OutputStream openOutputStream( URI uri )
821     throws IOException
822   {
823     throw new UnknownServiceException();
824   }
825 
826   /**
827    *  Renames the resource indicated by the first {@link URI} to the
828    *  name indicated by the second {@link URI}.<P>
829    *
830    *  The default implementation simply returns <CODE>false</CODE>
831    *  without doing anything.
832    *
833    *  If either {@link URI} parameter is <CODE>null</CODE> or if both
834    *  of the specified {@link URI} parameters refer to the same
835    *  resource, then the rename is not attempted and failure is
836    *  returned.<P>
837    *
838    *  If the specified {@link URI} parameters do not have the same
839    *  scheme, then the <CODE>VirtualFileSystem</CODE> handles the rename
840    *  by first copying the resource to the destination with {@link
841    *  VirtualFileSystem#copy(URI, URI)} and then deleting the original
842    *  resource with {@link VirtualFileSystem#delete(URI)}; if either
843    *  operation fails, then failure is returned.<P>
844    *
845    *  Otherwise, the scheme helper is called to perform the actual
846    *  rename operation.  Scheme helper implementations may therefore
847    *  assume that both {@link URI} parameters are not
848    *  <CODE>null</CODE>, do not refer to the same resource, and have
849    *  the same scheme.<P>
850    *
851    *  If the original {@link URI} refers to a nonexistent resource,
852    *  then the scheme helper implementations should return failure.
853    *  It is left up to the scheme helper implementations to decide
854    *  whether to overwrite the destination or return failure if the
855    *  destination {@link URI} refers to an existing resource.
856    *
857    *  @param oldURI the {@link URI} of the original resource
858    *  @param newURI the desired {@link URI} for the renamed resource
859    *
860    *  @return <CODE>true</CODE> if and only if the resource is
861    *  successfully renamed; <CODE>false</CODE> otherwise.
862    *
863    *  @see VirtualFileSystem#renameTo(URI, URI)
864    */
865   public boolean renameTo( URI oldURI, URI newURI )
866   {
867     return false;
868   }
869 
870   /**
871    *  Sets the last-modified timestamp of the resource indicated by
872    *  the {@link URI} to the time specified by <CODE>time</CODE>.
873    *  The time is specified in the number of milliseconds since
874    *  the epoch (00:00:00 GMT Jan 1, 1970).  The return value
875    *  indicates whether or not the setting of the timestamp
876    *  succeeded.<P>
877    *
878    *  The default implementation always returns <CODE>false</CODE>
879    *  without doing anything.
880    */
881   public boolean setLastModified( URI uri, long time )
882   {
883     return false;
884   }
885 
886   /**
887    *  Sets the read-only status of the resource indicated by the
888    *  {@link URI} according to the specified <CODE>readOnly</CODE>
889    *  flag.  The return value indicates whether or not the setting
890    *  of the read-only flag succeeded.<P>
891    *
892    *  The default implementation always returns <CODE>false</CODE>
893    *  without doing anything.
894    */
895   public boolean setReadOnly( URI uri, boolean readOnly )
896   {
897     return false;
898   }
899 
900   /**
901    *  Returns a displayable form of the complete {@link URI}.<P>
902    *
903    *  The default implementation delegates to {@link URI#toString()}.
904    */
905   public String toDisplayString( URI uri )
906   {
907     return uri == null ? "" : uri.toString();  //NOTRANS
908   }
909 
910   /**
911    *  This method attempts all possible ways of deriving a
912    *  <EM>relative URI reference</EM> as described in
913    *  <A HREF="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</A>
914    *  using the <CODE>uri</CODE> parameter as the {@link URI}
915    *  whose relative URI reference is to be determined and the
916    *  <CODE>base</CODE> parameter as the {@link URI} that serves as the
917    *  base document for the <CODE>uri</CODE> pararmeter.  If it is not
918    *  possible to produce a relative URI reference because the two
919    *  {@link URI}s are too different, then a full, absolute
920    *  reference for the <CODE>uri</CODE> parameter is returned.<P>
921    *
922    *  Whatever value is returned by this method, it can be used in
923    *  conjunction with the <CODE>base</CODE> {@link URI} to
924    *  reconstruct the fully-qualified {@link URI} by using one of the
925    *  {@link URI} constructors that takes a context {@link URI} plus
926    *  a {@link String} spec (i.e. the {@link String} returned by this
927    *  method).<P>
928    *
929    *  Both the <CODE>uri</CODE> and <CODE>base</CODE> parameters should
930    *  point to documents and be absolute {@link URI}s.  Specifically,
931    *  the <CODE>base</CODE> parameter does not need to be modified to
932    *  represent the base directory if the <CODE>base</CODE> parameter
933    *  already points to a document that is in the directory to which
934    *  the <CODE>uri</CODE> parameter will be made relative.  This
935    *  relationship between <CODE>uri</CODE> and <CODE>base</CODE> is
936    *  exactly how relative references are treated within HTML documents.
937    *  Relative references in an HTML page are resolved against the HTML
938    *  page's base URI.  The base URI is the HTML page itself, not the
939    *  directory that contains it.<P>
940    *
941    *  If either the <CODE>uri</CODE> or <CODE>base</CODE> parameter
942    *  needs to represent a directory rather than a file, they must end
943    *  with a "/" in the path part of the {@link URI}, such as:
944    *  <BLOCKQUOTE>
945    *    <CODE>http://host.com/root/my_directory/</CODE>
946    *  </BLOCKQUOTE>
947    *
948    *  <P>The algorithm used by this method to determine the relative
949    *  reference closely follows the recommendations made in
950    *  <A HREF="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</A>.  The
951    *  following steps are performed, in order, to determine the
952    *  relative reference:
953    *  <OL>
954    *    <LI>The scheme parts are checked first.  If they do not
955    *        match exactly, then an absolute reference is returned.
956    *    <LI>The authority parts are checked next.  If they do not
957    *        match exactly, then an absolute reference is returned.
958    *    <LI>If the scheme and authority parts match exactly, then
959    *        it is possible to calculate a relative reference.  The
960    *        path parts are then compared element-by-element to
961    *        determine the relative path, using the following steps,
962    *        in order:
963    *        <OL>
964    *          <LI>If no path elements are in common, then an absolute
965    *              path is used and relative-path determination stops.
966    *          <LI>Otherwise, any path parts that are in common are
967    *              omitted from the relative path.  Comparison of path
968    *              elements when the scheme is "file" is done using
969    *              instance of {@link java.io.File} so that, for example,
970    *              on Win32 the comparison is case-<EM>insensitive</EM>,
971    *              whereas on Unix the comparison is
972    *              case-<EM>sensitive</EM>.  When the scheme is not
973    *              "file", comparison is always case-sensitive.  
974    *          <LI>If, after matching as many path elements as possible,
975    *              there are still path elements remaining in the base
976    *              {@link URI} (except for the document name itself),
977    *              then a "../" sequence is prepended to the resulting
978    *              relative path for each base path element that was not
979    *              consumed while matching path elements.
980    *          <LI>If not all of the path elements in <CODE>uri</CODE>
981    *              were consumed, then those path elements are appended
982    *              to the resulting relative path as well.  If the first
983    *              remaining path element in <CODE>uri</CODE> contains
984    *              a ':' character and there is no "../" sequence was
985    *              prepended to the relative reference, then a "./"
986    *              sequence is prepended to prevent the ':' character
987    *              from being interpreted as a scheme delimiter (this
988    *              is a special case in <A
989    *              HREF="http://www.ietf.org/rfc/rfc2396.txt">RFC
990    *              2396</A>).
991    *        </OL>
992    *  </OL>
993    *
994    *  After the path part has been processed, no further processing
995    *  is done.  In particular, the query part and path part of the
996    *  <CODE>uri</CODE> are not appended.<P>
997    *
998    *  This method is implemented using the <EM>template method</EM>
999    *  design pattern, so it is possible for subclasses to override just
1000   *  part of the algorithm in order to handle scheme-specific
1001   *  details.
1002   */
1003  public String toRelativeSpec( URI uri, URI base )
1004  {
1005    return toRelativeSpec( uri, base, false );
1006  }
1007
1008  /**
1009   *  Variant of {@link #toRelativeSpec(URI, URI)} that has a flag
1010   *  that indicates whether the base {@link URI} should be fully
1011   *  consumed in the process of calculating the relative spec.<P>
1012   *
1013   *  If <CODE>mustConsumeBase</CODE> is <CODE>true</CODE>, then
1014   *  this method will return a non-<CODE>null</CODE> relative
1015   *  spec if and only if the base {@link URI} was fully consumed
1016   *  in the process of calculating the relative spec.  Otherwise,
1017   *  if any part of the base {@link URI} remained, then this
1018   *  method returns <CODE>null</CODE>.<P>
1019   *
1020   *  If <CODE>mustConsumeBase</CODE> is <CODE>false</CODE>, then
1021   *  this method will return a non-<CODE>null</CODE> relative
1022   *  spec regardless of how much of the base {@link URI} is
1023   *  consumed during the determination.
1024   */
1025  public String toRelativeSpec( URI uri, URI base, boolean mustConsumeBase )
1026  {
1027    //  The first step is to check that the scheme parts are
1028    //  identical.  If they are not identical, then the returned
1029    //  reference should just be absolute.
1030    if ( !haveSameScheme( uri, base ) ||
1031         !haveSameAuthority( uri, base ) )
1032    {
1033      return mustConsumeBase ? null : uri.toString();
1034    }
1035
1036    final StringBuffer relativeURI = new StringBuffer();
1037    final boolean baseFullyConsumed = appendRelativePath( uri, base, relativeURI, mustConsumeBase );
1038    if ( mustConsumeBase && !baseFullyConsumed )
1039    {
1040      return null;
1041    }
1042    return relativeURI.toString();
1043  }
1044
1045  /**
1046   *  This method gets the base directory fully containing the relative path.
1047   *
1048   *  The <CODE>uri</CODE> should be absolute and point to a directory.
1049   *  It must end with a "/" in the path part of the {@link URI}, such as:
1050   *  <BLOCKQUOTE>
1051   *    <CODE>http://host.com/root/my_directory/</CODE>
1052   *  </BLOCKQUOTE>
1053   *  If the <CODE>uri</CODE> does not end with a "/", it will be assumed
1054   *  that the <CODE>uri</CODE> points to a document. The document name will
1055   *  then be stripped in order to determine the parent directory.
1056   *
1057   *  The <CODE>relativeSpec</CODE> parameter should be a relative path.
1058   *  If the <CODE>relativeSpec</CODE> does not end with a "/", it will be
1059   *  assumed that the <CODE>relativeSpec</CODE> points to a document.
1060   *  The document name will then be stripped in order to determine the
1061   *  parent directory.
1062   *
1063   *  For example, if the <CODE>uri</CODE> points to:
1064   *  <BLOCKQUOTE>
1065   *    <CODE>file://c:/root/dir1/dir2/dir3/</CODE>
1066   *  </BLOCKQUOTE>
1067   *  and the <CODE>relativeSpec</CODE> is:
1068   *  <BLOCKQUOTE>
1069   *    <CODE>dir2/dir3</CODE>
1070   *  </BLOCKQUOTE>
1071   *  The returned value would be:
1072   *  <BLOCKQUOTE>
1073   *    <CODE>file://c:/root/dir1/</CODE>
1074   *  </BLOCKQUOTE>
1075   *
1076   *  If the <CODE>relativeSpec</CODE> path elements are not fully
1077   *  contained in the last part of the <CODE>uri</CODE> path the
1078   *  value returned is the uri itself if the uri path ends with a
1079   *  "/" or the uri parent otherwise.
1080   */
1081  public URI getBaseParent( URI uri, String relativeSpec )
1082  {
1083    String basePath = uri.getPath();
1084    final int basePathIndex = basePath.lastIndexOf( '/' );  //NOTRANS
1085
1086    // If the base path ends with a "/" get the parent uri.
1087    if ( basePathIndex < basePath.length() - 1 )
1088    {
1089      uri = getParent(uri);
1090      basePath = uri.getPath();
1091    }
1092
1093    final String baseDir = basePath.substring( 0, basePathIndex );
1094
1095    final int relPathIndex = relativeSpec.lastIndexOf( '/' );
1096
1097    // If the relative path does not containe a "/" we assume we
1098    // are dealing with a document whose base parent directory
1099    // is just the specified uri.
1100    if ( relPathIndex < 0 )
1101    {
1102      return uri;
1103    }
1104
1105    final String relDir =
1106                 relativeSpec.substring( 0, relativeSpec.lastIndexOf( '/' ) );  //NOTRANS
1107
1108    final StringTokenizer relTokenizer = new StringTokenizer( relDir, "/" );  //NOTRANS
1109    String relToken = relTokenizer.nextToken();
1110
1111    final StringTokenizer baseTokenizer = new StringTokenizer( baseDir, "/" );  //NOTRANS
1112
1113    int ctr = 0;
1114    boolean foundBasePath = false;
1115    while ( baseTokenizer.hasMoreTokens() )
1116    {
1117      final String baseToken = baseTokenizer.nextToken();
1118      if ( areEqualPathElems( relToken, baseToken ) )
1119      {
1120        final int baseCount = baseTokenizer.countTokens();
1121        final int relCount = relTokenizer.countTokens();
1122        if ( baseCount == relCount )
1123        {
1124          if ( relCount != 0 )
1125          {
1126            relToken = relTokenizer.nextToken();
1127            ctr++;
1128          }
1129          else
1130          {
1131            foundBasePath = true;
1132            break;
1133          }
1134        }
1135      }
1136      else if ( ctr != 0 )
1137      {
1138          ctr = 0;
1139          break;
1140      }
1141    }
1142
1143    if ( foundBasePath )
1144    {
1145      for ( int i = ctr; i >= 0; i-- )
1146      {
1147        uri = getParent( uri );
1148      }
1149    }
1150
1151    return uri;
1152  }
1153
1154  /**
1155   * Get an {@link URL} from an {@link URI}. This method just calls the 
1156   * {@link URI#toURL} method.
1157   */
1158  public URL toURL( URI uri ) throws MalformedURLException
1159  {
1160    return uri.toURL();  
1161  }
1162
1163  //--------------------------------------------------------------------------
1164  //  helpers for equals
1165  //--------------------------------------------------------------------------
1166  /**
1167   * Returns <code>true</code> if the URIs user infos are equal.
1168   */
1169  protected boolean haveSameUserInfo( URI uri1, URI uri2 )
1170  {
1171    return areEqual( uri1.getUserInfo(), uri2.getUserInfo() );
1172  }
1173
1174  /**
1175   * Returns <code>true</code> if the URIs hosts are equal.
1176   */
1177  protected boolean haveSameHost( URI uri1, URI uri2 )
1178  {
1179    return areEqual( uri1.getHost(), uri2.getHost() );
1180  }
1181
1182  /**
1183   * Returns <code>true</code> if the URIs paths are equal.
1184   */
1185  protected boolean haveSamePath( URI uri1, URI uri2 )
1186  {
1187    return areEqual( uri1.getPath(), uri2.getPath() );
1188  }
1189
1190  /**
1191   * Returns <code>true</code> if the URIs queries are equal.
1192   */
1193  protected boolean haveSameQuery( URI uri1, URI uri2 )
1194  {
1195    return areEqual( uri1.getQuery(), uri2.getQuery() );
1196  }
1197
1198  /**
1199   * Returns <code>true</code> if the URIs refs are equal.
1200   */
1201  protected boolean haveSameRef( URI uri1, URI uri2 )
1202  {
1203    return areEqual( uri1.getFragment(), uri2.getFragment() );
1204  }
1205
1206  /**
1207   * Returns <code>true</code> if the URIs ports are equal.
1208   */
1209  protected boolean haveSamePort( URI uri1, URI uri2 )
1210  {
1211    return uri1.getPort() == uri2.getPort();
1212  }
1213
1214  /**
1215   * Compares the two string for equality.
1216   */
1217  protected final boolean areEqual( String s1, String s2 )
1218  {
1219    return s1 == s2 || ( s1 != null && s1.equals( s2 ) );
1220  }
1221
1222  //--------------------------------------------------------------------------
1223  //  template method helpers for toRelativeSpec(...)
1224  //--------------------------------------------------------------------------
1225  /**
1226   *  This is a helper for the {@link #toRelativeSpec(URI, URI)} method,
1227   *  which uses the <EM>template method</EM> design pattern.<P>
1228   *
1229   *  By default, the <CODE>uri</CODE> and <CODE>base</CODE> parameters
1230   *  must have identical schemes as a prerequisite to being able to
1231   *  produce a relative {@link URI} spec.
1232   */
1233  protected boolean haveSameScheme( URI uri, URI base )
1234  {
1235    if ( uri == null || base == null )
1236    {
1237      return false;
1238    }
1239
1240    final String uriScheme = uri.getScheme();
1241    final String baseScheme = base.getScheme();
1242    return uriScheme.equals( baseScheme );
1243  }
1244
1245
1246  /**
1247   *  This is a helper for the {@link #toRelativeSpec(URI, URI)} method,
1248   *  which uses the <EM>template method</EM> design pattern.<P>
1249   *
1250   *  The "authority" part is a combination of the user info, hostname,
1251   *  and port number.  The full syntax in the {@link URI} string is:
1252   *  <BLOCKQUOTE>
1253   *    <CODE>userinfo@hostname:port</CODE>
1254   *  </BLOCKQUOTE>
1255   *
1256   *  It may appear in an {@link URI} such as:
1257   *  <BLOCKQUOTE>
1258   *    <CODE>ftp://jsr198eg@ide.com:21/builds/ri.zip</CODE>
1259   *  </BLOCKQUOTE>
1260   *
1261   *  The authority part may be <CODE>null</CODE>, if the {@link URI}
1262   *  scheme does not require one.<P>
1263   *
1264   *  By default, the <CODE>uri</CODE> and <CODE>base</CODE> parameters
1265   *  must have identical authority strings as a prerequisite to being
1266   *  able to produce a relative {@link URI} spec.
1267   */
1268  protected boolean haveSameAuthority( URI uri, URI base )
1269  {
1270    if ( uri == base )
1271    {
1272      return true;
1273    }
1274    else if ( uri == null || base == null )
1275    {
1276      return false;
1277    }
1278
1279    final String uriAuthority = uri.getAuthority();
1280    final String baseAuthority = base.getAuthority();
1281    if ( uriAuthority == null )
1282    {
1283      return baseAuthority == null;
1284    }
1285    else if ( baseAuthority != null )
1286    {
1287      return uriAuthority.equals( baseAuthority );
1288    }
1289    else
1290    {
1291      return false;
1292    }
1293  }
1294
1295
1296  /**
1297   *  This is a helper for the {@link #toRelativeSpec(URI, URI)} method,
1298   *  which uses the <EM>template method</EM> design pattern.<P>
1299   *
1300   *  @return  <CODE>true</CODE> if the entire base {@link URI} was
1301   *  consumed in the process of determining the relative path;
1302   *  <CODE>false</CODE> otherwise (i.e. not all of the base {@link URI}
1303   *  was consumed).
1304   */
1305  protected boolean appendRelativePath( URI uri, URI base, StringBuffer relativeURI,
1306                                        boolean mustConsumeBase )
1307  {
1308    //  By this point, it's known that the scheme and authority parts
1309    //  of the URIs match, so that it is possible to generate a relative
1310    //  URI spec that omits the scheme and authority parts.  The
1311    //  process of converting the path part to a relative form begins
1312    //  here.
1313    final String uriPath = uri.getPath();
1314    final int uriLastSlash = uriPath.lastIndexOf( '/' );
1315    //  Chop off the file name from the uri parameter so that its
1316    //  filename can't match a directory in the base URI that happens to
1317    //  have the same name.
1318    final String uriDir = uriPath.substring( 0, uriLastSlash );
1319    final String uriFileName = uriPath.substring( uriLastSlash + 1 );
1320    final StringTokenizer uriTokenizer = new StringTokenizer( uriDir, "/" );  //NOTRANS
1321
1322    final String basePath = base.getPath();
1323    final String baseDir = basePath.substring( 0, basePath.lastIndexOf( '/' ) );  //NOTRANS
1324    final StringTokenizer baseTokenizer = new StringTokenizer( baseDir, "/" );  //NOTRANS
1325    final int numBaseTokens = baseTokenizer.countTokens();
1326
1327    //  Iterate through the parts of the uriDir and baseDir, until either
1328    //  a non-matching part is found or either uriDir or baseDir runs out
1329    //  of tokens.
1330    int numMatches = 0;
1331    String uriToken = null;
1332    while ( uriTokenizer.hasMoreTokens() && baseTokenizer.hasMoreTokens() )
1333    {
1334      uriToken = uriTokenizer.nextToken();
1335      final String baseToken = baseTokenizer.nextToken();
1336      if ( areEqualPathElems( uriToken, baseToken ) )
1337      {
1338        numMatches++;
1339        uriToken = null;
1340        continue;
1341      }
1342      break;
1343    }
1344
1345    final boolean baseFullyConsumed =  ( numBaseTokens == numMatches );
1346    if ( mustConsumeBase && !baseFullyConsumed )
1347    {
1348      //  Abort processing and just return false.
1349      return false;
1350    }
1351
1352    if ( numMatches == 0 )
1353    {
1354      //  If there are no matching parts in uriDir and baseDir at all,
1355      //  then the absolute path should be used in the relative URI spec.
1356      //  This covers the drive-letter case on Win32.
1357      relativeURI.append( uriPath );
1358    }
1359    else
1360    {
1361      //  If there were some matches, first prepend one or more "../"
1362      //  sequences to the resulting relative URI spec if not all of
1363      //  the baseDir tokens were consumed.
1364      final int numDotDotsNeeded = numBaseTokens - numMatches;
1365      for ( int i = numDotDotsNeeded; i > 0; i-- )
1366      {
1367        relativeURI.append( "../" ); // NOTRANS
1368      }
1369
1370      if ( uriToken != null && uriToken.length() > 0 )
1371      {
1372        if ( numDotDotsNeeded == 0 && uriToken.indexOf( ':' ) >= 0 )  //NOTRANS
1373        {
1374          //  This is a special case in RFC 2396.  If the relative path
1375          //  contains a ':' in the first part of the path, then a
1376          //  "./" sequence needs to be prepended to prevent the ':'
1377          //  from being interpreted as delimiting the scheme.  The
1378          //  "./" does not need to be prepended if any "../" sequences
1379          //  have alread been prepended.  RFC 2396 states that a ':'
1380          //  can appear in the path part only after the first "/"
1381          //  character.
1382          relativeURI.append( "./" ); // NOTRANS  //NOTRANS
1383        }
1384        relativeURI.append( uriToken ).append( '/' );  //NOTRANS
1385      }
1386
1387      //  Append any other remaining path elements from the uri
1388      //  parameter.
1389      while ( uriTokenizer.hasMoreTokens() )
1390      {
1391        relativeURI.append( uriTokenizer.nextToken() ).append( '/' );  //NOTRANS
1392      }
1393
1394      //  Must append the filename too!  It was chopped off during the
1395      //  processing of path elements to prevent the filename from
1396      //  matching a directory name in the base URI.
1397      relativeURI.append( uriFileName );
1398    }
1399
1400    return baseFullyConsumed;
1401  }
1402
1403
1404  /**
1405   *  This is a helper for the
1406   *  {@link #appendRelativePath(URI, URI, StringBuffer, boolean)} method,
1407   *  which uses the <EM>template method</EM> design pattern.<P>
1408   *
1409   *  The two {@link String}s that are passed in represent elements
1410   *  of the path parts of the <CODE>uri</CODE> and <CODE>base</CODE>
1411   *  parameters that are passed into
1412   *  {@link #appendRelativePath(URI, URI, StringBuffer, boolean)}.<P>
1413   *
1414   *  By default, path elements are compared exactly in a
1415   *  case-sensitive manner using regular {@link String} comparison.
1416   */
1417  protected boolean areEqualPathElems( String uriElem, String baseElem )
1418  {
1419    return ( uriElem.equals( baseElem ) );
1420  }
1421
1422  private URI replacePathPart( URI uri, String newPath )
1423  {
1424    try
1425    {
1426      return new URI( uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), newPath, uri.getQuery(), uri.getFragment() );
1427    }
1428    catch ( Exception e )
1429    {
1430      e.printStackTrace();
1431      return null;
1432    }
1433  }
1434}