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}