Source code: javax/ide/net/URIFactory.java
1 package javax.ide.net;
2
3 import java.io.File;
4 import java.net.URISyntaxException;
5 import java.net.URI;
6 import java.net.URL;
7 import java.util.ArrayList;
8 import java.util.Iterator;
9
10 /**
11 * This class contains methods which create new instances of
12 * {@link URI}. In order for {@link URI}s to be used effectively as
13 * keys in a hashtable, the URI must be created in a very consistent
14 * manner. Therefore, when creating a new instance of {@link URI}, it
15 * is strongly recommended that you use one of the methods in this class
16 * rather than calling the {@link URI} constructor directly. This will
17 * help prevent subtle bugs that can come up when two {@link URI}
18 * instances that should be equal aren't equal (resulting in caching
19 * bugs) because of a difference in some detail of the URI that affects
20 * the result of the {@link Object#equals(Object)} method but doesn't
21 * affect the location pointed to by the {@link URI} (which means that
22 * code other than the caching will actually continue to work).<P>
23 *
24 * Additionally, by using the methods in this class to create instances
25 * of {@link URI}, dependencies on {@link URI} can be tracked more
26 * easily.
27 *
28 */
29 public final class URIFactory
30 {
31 public static final String JAR_URI_SEPARATOR = "!/"; // NOTRANS
32
33 /**
34 * This is a callback interface used by the {@link URIFactory}
35 * while it is in the process of producing a new unique
36 * {@link java.net.URI URI}.
37 */
38 public interface NameGenerator
39 {
40 /**
41 * This method is called by {@link URIFactory} from one of its
42 * <CODE>newUniqueURI()</CODE> methods to obtain a relative
43 * file name. The name is usually just a simple file name without any
44 * preceeding directory names, that will be used in the process of
45 * generating a unique {@link java.net.URI}.<P>
46 *
47 * The {@link URIFactory} is responsible for assembling a complete
48 * <CODE>URI</CODE> by combining a base <CODE>URI</CODE> with the
49 * relative file name returned by <CODE>nextName()</CODE>. The
50 * {@link URIFactory} is also responsible for checking that the
51 * newly created <CODE>URI</CODE> is unique among files on disk
52 * and within the IDE's caches. If the new <CODE>URI</CODE> is not
53 * unique, then {@link URIFactory} will issue another call to
54 * <CODE>nextName()</CODE> to get a new name. This process is
55 * repeated until either a unique <CODE>URI</CODE> is produced, or
56 * the {@link URIFactory} "times out" on the
57 * <CODE>NameGenerator</CODE> implementation after a very large
58 * number of iterations fails to produce a unique <CODE>URI</CODE>.
59 * <P>
60 *
61 * Therefore to interact properly with {@link URIFactory}, the
62 * <CODE>nextName()</CODE> implementation must return a different
63 * name each time it is invoked. More specifically, a particular
64 * instance of <CODE>NameGenerator</CODE> should not return any
65 * name more than once from its <CODE>nextName()</CODE> method.
66 * Of course, this restriction does not apply across different
67 * instances of <CODE>NameGenerator</CODE>.<P>
68 *
69 * The exact means by which a new name is produced is not specified
70 * and is left to the specific <CODE>NameGenerator</CODE> classes.
71 * However, the <CODE>nextName()</CODE> method should not attempt to
72 * create an <CODE>URI</CODE> and check for its uniqueness, as this
73 * may lead to problems in the future when the {@link URIFactory} and
74 * {@link VirtualFileSystem} classes are enhanced. For example, if
75 * individual <CODE>NameGenerator</CODE> implementations are
76 * attempting to determine uniqueness, bugs may surface later if the
77 * IDE's algorithm for determining uniqueness changes. How the IDE
78 * determines <CODE>URI</CODE>uniqueness is not documented and should
79 * be considered an implementation detail of the IDE framework.
80 */
81 public String nextName();
82 }
83
84 //--------------------------------------------------------------------------
85 // non-sanitizing factory methods...
86 //--------------------------------------------------------------------------
87 /**
88 * Creates a new {@link URI} that is the combination of the
89 * specified base {@link URI} and the relative spec string. The
90 * base {@link URI} is treated as a directory, whether or not the
91 * {@link URI} ends with the "/" character, and the relative spec
92 * is always treated as relative, even if it begins with a "/".<P>
93 *
94 * Non-sanitizing.
95 */
96 public static URI newURI( URI baseURI, String relativeSpec )
97 {
98 final String newPath = resolveRelative( baseURI.getPath(), relativeSpec );
99 if ( isJarURI( baseURI ) )
100 {
101 if ( newPath.indexOf( JAR_URI_SEPARATOR ) < 0 )
102 {
103 // Then this means that the relative spec must have contined
104 // some ".." sequences and cause the URI to ascend into the
105 // jar file's path itself.
106 return newJarFileURIImpl( newPath );
107 }
108 }
109 return URIFactory.replacePathPart( baseURI, newPath );
110 }
111
112 /**
113 * Creates a new {@link URI} that is the combination of the
114 * specified base {@link URI} and the relative spec string. The
115 * base {@link URI} is treated as a directory whether or not it
116 * ends with the "/" character. The returned {@link URI} will
117 * return with the "/" character in the path part.<P>
118 *
119 * Non-sanitizing.
120 */
121 public static URI newDirURI( URI baseURI, String relativeSpec )
122 {
123 return relativeSpec.endsWith( "/" ) ? //NOTRANS
124 newURI( baseURI, relativeSpec ) :
125 newURI( baseURI, relativeSpec + "/" ); //NOTRANS
126 }
127
128 /**
129 * Standard way of specifying a scheme with a file path. The file
130 * path is used in the {@link URI} verbatim, without any changes to
131 * the file separator character or any other characters. For an {@link URI} whose scheme is "file", the
132 * {@link #newFileURI(String)} factory method should be used instead.
133 * <P>
134 *
135 * Non-sanitizing.
136 */
137 public static URI newURI( String scheme, String path )
138 {
139 return newURI( scheme, null, null, -1, path, null, null );
140 }
141
142 /**
143 * Creates a new {@link URI} that is the combination of the
144 * specified <code>scheme</code> and the directory path. The directory
145 * path is used in the {@link URI} verbatim, without any changes to
146 * the file separator character or any other characters.
147 * The returned {@link URI} will return with the "/" character in the
148 * path part.<P>
149 *
150 * Non-sanitizing.
151 */
152 public static URI newDirURI( String scheme, String dirPath )
153 {
154 return dirPath.endsWith( "/" ) ? //NOTRANS
155 newURI( scheme, dirPath ) :
156 newURI( scheme, dirPath + "/" ); //NOTRANS
157 }
158
159 /**
160 * Creates a new unique URI using the scheme of the specified
161 * <code>baseURI</code>. The <code>nameGen</code> object is called to
162 * generate a unique name that will be appended to the base uri.
163 *
164 * Non-sanitizing.
165 */
166 public static URI newUniqueURI( URI baseURI, NameGenerator nameGen )
167 {
168 while ( true )
169 {
170 final String name = nameGen.nextName();
171 final URI uri = newURI( baseURI, name );
172 if ( uri == null )
173 {
174 return null;
175 }
176
177 // This will get expensive if many iterations are required to get
178 // a unique URI, especially if the URI points to a resource that
179 // requires network access.
180 if ( !VirtualFileSystem.getVirtualFileSystem().isBound( uri ) )
181 {
182 return uri;
183 }
184 }
185 }
186
187 /**
188 * Returns a new {@link URI} that is identical to the specified
189 * {@link URI} except that the scheme part of the {@link URI}
190 * has been replaced with the specified <CODE>newscheme</CODE>.<P>
191 *
192 * Non-sanitizing.
193 */
194 public static URI replaceSchemePart( URI uri, String newScheme )
195 {
196 final String userinfo = uri.getUserInfo();
197 final String host = uri.getHost();
198 final int port = uri.getPort();
199 final String path = uri.getPath();
200 final String query = uri.getQuery();
201 final String fragment = uri.getFragment();
202 return newURI( newScheme, userinfo, host, port, path, query, fragment );
203 }
204
205 /**
206 * Returns a new {@link URI} that is identical to the specified
207 * {@link URI} except that the port part of the {@link URI}
208 * has been replaced with the specified <CODE>newPort</CODE>.<P>
209 *
210 * Non-sanitizing.
211 */
212 public static URI replacePortPart( URI uri, int newPort )
213 {
214 final String scheme = uri.getScheme();
215 final String userinfo = uri.getUserInfo();
216 final String host = uri.getHost();
217 final String path = uri.getPath();
218 final String query = uri.getQuery();
219 final String fragment = uri.getFragment();
220 return newURI( scheme, userinfo, host, newPort, path, query, fragment );
221 }
222
223 /**
224 * Returns a new {@link URI} that is identical to the specified
225 * {@link URI} except that the host part of the {@link URI}
226 * has been replaced with the specified <CODE>newHost</CODE>.<P>
227 *
228 * Non-sanitizing.
229 */
230 public static URI replaceHostPart( URI uri, String newHost )
231 {
232 final String scheme = uri.getScheme();
233 final String userinfo = uri.getUserInfo();
234 final int port = uri.getPort();
235 final String path = uri.getPath();
236 final String query = uri.getQuery();
237 final String fragment = uri.getFragment();
238 return newURI( scheme, userinfo, newHost, port, path, query, fragment );
239 }
240
241 /**
242 * Returns a new {@link URI} that is identical to the specified
243 * {@link URI} except that the path part of {@link URI} has been
244 * replaced with the specified <CODE>newPath</CODE>.<P>
245 *
246 * Non-sanitizing.
247 */
248 public static URI replacePathPart( URI uri, String newPath )
249 {
250 final String scheme = uri.getScheme();
251 final String userinfo = uri.getUserInfo();
252 final String host = uri.getHost();
253 final int port = uri.getPort();
254 final String query = uri.getQuery();
255 final String fragment = uri.getFragment();
256 return newURI( scheme, userinfo, host, port, newPath, query, fragment );
257 }
258
259 /**
260 * Returns a new {@link URI} that is identical to the specified
261 * {@link URI} except that the fragment part of the {@link URI}
262 * has been replaced with the specified <CODE>newRef</CODE>.<P>
263 *
264 * Non-sanitizing.
265 */
266 public static URI replaceFragmentPart( URI uri, String newFragment )
267 {
268 final String scheme = uri.getScheme();
269 final String userinfo = uri.getUserInfo();
270 final String host = uri.getHost();
271 final int port = uri.getPort();
272 final String path = uri.getPath();
273 final String query = uri.getQuery();
274 return newURI( scheme, userinfo, host, port, path, query, newFragment );
275 }
276
277 /**
278 * Returns a new {@link URI} that is identical to the specified
279 * {@link URI} except that the query part of the {@link URI}
280 * has been replaced with the specified <CODE>newQuery</CODE>.<P>
281 *
282 * Non-sanitizing.
283 */
284 public static URI replaceQueryPart( URI uri, String newQuery )
285 {
286 final String scheme = uri.getScheme();
287 final String userinfo = uri.getUserInfo();
288 final String host = uri.getHost();
289 final int port = uri.getPort();
290 final String path = uri.getPath();
291 final String fragment = uri.getFragment();
292 return newURI( scheme, userinfo, host, port, path, newQuery, fragment );
293 }
294
295 //--------------------------------------------------------------------------
296 // factory methods taking string specifications...
297 //--------------------------------------------------------------------------
298 /*-
299 * NOTE: This implementation has known bugs. To avoid these bugs,
300 * you should strongly consider using any of the other URIFactory
301 * methods that do not require parsing a uriSpec string.
302 *
303 * Non-sanitizing.
304 */
305 public static URI newURI( String uriSpec )
306 {
307 return newURI( uriSpec, false, false );
308 }
309
310 /*-
311 * Do not make this public yet.
312 */
313 static URI newURI( String uriSpec, boolean forceDir, boolean assumeFile )
314 {
315 if ( uriSpec == null )
316 {
317 return null;
318 }
319 if ( forceDir && !uriSpec.endsWith( "/" ) ) //NOTRANS
320 {
321 uriSpec += "/"; //NOTRANS
322 }
323
324 final int uriSpecLen = uriSpec.length();
325 if ( uriSpecLen > 0 )
326 {
327 if ( uriSpec.charAt( 0 ) == File.separatorChar ||
328 ( uriSpecLen > 1 && uriSpec.charAt( 1 ) == ':' ) ||
329 ( uriSpec.indexOf( ':' ) < 0 && assumeFile ) )
330 {
331 return newFileURI( uriSpec );
332 }
333 }
334 if ( uriSpec.toLowerCase().startsWith( "file:" ) ) // NOTRANS
335 {
336 return newFileURI( uriSpec.substring( 5 ) );
337 }
338 else
339 {
340 try
341 {
342 // -- Bugs here if # or ? appear in the URI string.
343 // -- Need to use a customized URI parser to convert
344 // -- the string into an URI. This will do for now,
345 // -- since # and ? are infrequently used in URIs.
346 return new URI( uriSpec );
347 }
348 catch ( URISyntaxException e )
349 {
350 // Silent. The return value will be null, which the caller
351 // should interpret to mean that no valid URI was specified.
352 }
353 }
354 return null;
355 }
356
357
358 //--------------------------------------------------------------------------
359 // sanitizing factory methods...
360 //--------------------------------------------------------------------------
361 /**
362 * Creates a new {@link URI} using the "<CODE>file</CODE>" scheme.
363 * The specified <CODE>filePath</CODE> can be expressed in the
364 * notation of the platform that the Java VM is currently running on,
365 * or it can be expressed using the forward slash character ("/") as
366 * its file separator character, which is the standard file separator
367 * for {@link URI}s. Note that technically, the forward slash
368 * character is the only officially recognized hierarchy separator
369 * character for an URI.
370 *
371 * Sanitizing.
372 */
373 public static URI newFileURI( String filePath )
374 {
375 if ( filePath == null )
376 {
377 return null;
378 }
379 final String path = sanitizePath( filePath );
380 return newURI( VirtualFileSystem.FILE_SCHEME, path );
381 }
382
383
384 /**
385 * This method converts a {@link File} instance into an {@link URI}
386 * instance using an algorithm that is consistent with the other
387 * factory methods in <CODE>URIFactory</CODE>.<P>
388 *
389 * Sanitizing.
390 *
391 * @return An {@link URI} corresponding to the given {@link File}.
392 * The {@link URI} is produced using a mechanism that ensures
393 * uniformity of the {@link URI} format across platforms.
394 */
395 public static URI newFileURI( File file )
396 {
397 if ( file == null )
398 {
399 return null;
400 }
401 final String filePath = file.getAbsolutePath();
402 if ( file.isDirectory() )
403 {
404 return newDirURI( filePath );
405 }
406 else
407 {
408 return newFileURI( filePath );
409 }
410 }
411
412
413 /**
414 * Creates a new {@link URI} with the "file" scheme that is for the
415 * specified directory. Leading and trailing "/" characters are
416 * added if they are missing. If the specified <CODE>dirPath</CODE>
417 * is <CODE>null</CODE>, then the returned {@link URI} is
418 * <CODE>null</CODE>.<P>
419 *
420 * Sanitizing.
421 */
422 public static URI newDirURI( String dirPath )
423 {
424 if ( dirPath == null )
425 {
426 return null;
427 }
428
429 String path = sanitizePath( dirPath );
430 if ( !path.endsWith( "/" ) ) //NOTRANS
431 {
432 path = path + "/"; //NOTRANS
433 }
434 return newURI( VirtualFileSystem.FILE_SCHEME, path );
435 }
436
437
438 /**
439 * Creates a new {@link URI} with the "file" scheme that is for the
440 * specified directory. Leading and trailing "/" characters are
441 * added if they are missing. This method does not check whether the
442 * specified {@link File} is actually a directory on disk; it just
443 * assumes that it is. If the specified <CODE>dirPath</CODE> is
444 * <CODE>null</CODE>, then the returned {@link URI} is
445 * <CODE>null</CODE>.<P>
446 *
447 * Sanitizing.
448 */
449 public static URI newDirURI( File dir )
450 {
451 return dir != null ? newDirURI( dir.getAbsolutePath() ) : null;
452 }
453
454
455 //--------------------------------------------------------------------------
456 // jar URI factory methods...
457 //--------------------------------------------------------------------------
458 /**
459 * Builds an {@link URI} using the "<CODE>jar</CODE>" scheme based
460 * on the specified archive {@link File} and the entry name passed
461 * in. The entry name is relative to the root of the jar file, so
462 * it should not begin with a slash. The entry name may be the
463 * empty string or null, which means that the returned {@link URI}
464 * should represent the jar file itself.
465 *
466 * Sanitizing for archiveFile; non-sanitizing for entryName.
467 */
468 public static URI newJarURI( File archiveFile, String entryName )
469 {
470 final URI archiveURI = newFileURI( archiveFile );
471 return newJarURI( archiveURI, entryName );
472 }
473
474 /**
475 * Returns <CODE>true</CODE> if the specified {@link URI} has
476 * the "jar" scheme. Returns <CODE>false</CODE> if the specified
477 * {@link URI} is <CODE>null</CODE> or has a scheme other than
478 * "jar".
479 */
480 public static boolean isJarURI( URI jarURI )
481 {
482 return jarURI != null
483 ? jarURI.getScheme().equals( "jar" )
484 : false; // NOTRANS
485 }
486
487 /**
488 * Determine if a given URL represents an jar or zip file. The method
489 * does a simple check to determine if the pathname ends with
490 * .jar or .zip.
491 */
492 public static boolean isArchive( String pathname )
493 {
494 final int lastDot = pathname.lastIndexOf( '.' );
495 if ( lastDot < 0 )
496 {
497 return false;
498 }
499 final String ext = pathname.substring( lastDot ).toLowerCase();
500 return ext.equals( ".jar" ) || ext.equals( ".zip" ); // NOTRANS
501 }
502
503 /**
504 * Builds an {@link URI} using the "<CODE>jar</CODE>" scheme based
505 * on the specified archive {@link URI} and the entry name passed in.
506 * The entry name is relative to the root of the jar file, so it
507 * should not begin with a slash. The entry name may be the empty
508 * string or null, which means that the returned {@link URI}
509 * should represent the jar file itself.<P>
510 *
511 * Non-sanitizing for both archiveURI and entryName.
512 */
513 public static URI newJarURI( URI archiveURI, String entryName )
514 {
515 if ( isJarURI( archiveURI ) )
516 {
517 // If the specified URI is already a "jar" URI, we don't want to
518 // put another "jar:" scheme in front of it. Instead, we just
519 // want to append the entry name to the existing jar URI. First
520 // we need to verify that the "!/" delimiter is already there.
521 // Then we force the ending "/" onto the base URI, if it didn't
522 // already have it (it really should). Then the entry name is
523 // appended.
524 final String path = archiveURI.getPath();
525 final int bangSlash = path.indexOf( JAR_URI_SEPARATOR );
526 if ( bangSlash < 0 )
527 {
528 throw new IllegalArgumentException( "Bad jar uri: " + archiveURI ); // NOTRANS
529 }
530 final StringBuffer newPath = new StringBuffer( path );
531 if ( !path.endsWith( "/" ) ) //NOTRANS
532 {
533 newPath.append( '/' ); //NOTRANS
534 }
535 if ( entryName != null )
536 {
537 newPath.append( entryName );
538 }
539 return replacePathPart( archiveURI, newPath.toString() );
540 }
541 else
542 {
543 final StringBuffer path = new StringBuffer( archiveURI.toString() );
544 path.insert( 0, '/' );
545 path.append( JAR_URI_SEPARATOR );
546 if ( entryName != null )
547 {
548 path.append( entryName );
549 }
550 return newURI( VirtualFileSystem.JAR_SCHEME, path.toString() );
551 }
552 }
553
554
555 //--------------------------------------------------------------------------
556 // direct access factory methods...
557 //--------------------------------------------------------------------------
558 /**
559 * Creates a new {@link URI} whose parts have the exact values that
560 * are specified. <EM>In general, you should avoid calling this
561 * method directly.</EM><P>
562 *
563 * This method is the ultimate place where all of the other
564 * <CODE>URIFactory</CODE> methods end up when creating an
565 * {@link URI}.
566 *
567 * Non-sanitizing.
568 */
569 public static URI newURI( String scheme, String userinfo,
570 String host, int port,
571 String path, String query, String fragment )
572 {
573 try
574 {
575 return new URI( scheme, userinfo, host, port, path, query, fragment );
576 }
577 catch ( Exception e )
578 {
579 e.printStackTrace();
580 return null;
581 }
582 }
583
584 /**
585 * Creates a new {@link URI} form an {@link URL}.
586 * @param url The URL from which URI is derived.
587 * @return New URI.
588 */
589 public static URI newURI( URL url )
590 {
591 // the URI() constructor does not work well with jar: URLs. Basically,
592 // URI likes jar:/file:/path/to/file.jar!/entry/file.txt
593 // URL likes jar:file:/path/to/file.jar!/entry/file.txt
594
595 if ( "jar".equals( url.getProtocol() ) )
596 {
597 StringBuffer sb = new StringBuffer( url.toString() );
598 sb.insert( 4, "/" );
599 return newURI( sb.toString() );
600 }
601
602 try
603 {
604 return new URI( url.toString() );
605 }
606 catch ( URISyntaxException use )
607 {
608 use.printStackTrace();
609 return null;
610 }
611 //
612 // This code totally doesn't work.
613 // A URL like this: file:/some/path.txt
614 // ends up as a URI like this: file:///some/path.txt
615 // return newURI( url.getProtocol(),
616 // url.getUserInfo(),
617 // url.getHost(),
618 // url.getPort(),
619 // url.getPath(),
620 // url.getQuery(),
621 // url.getRef() );
622 }
623
624
625 //--------------------------------------------------------------------------
626 // implementation details...
627 //--------------------------------------------------------------------------
628
629
630 /**
631 * This "sanitizes" the specified string path by converting all
632 * {@link File#separatorChar} characters to forward slash ('/').
633 * Also, a leading forward slash is prepended if the path does
634 * not begin with one.
635 */
636 private static String sanitizePath( String path )
637 {
638 if ( File.separatorChar != '/' )
639 {
640 path = path.replace( File.separatorChar, '/' );
641 }
642 if ( !path.startsWith( "/" ) ) //NOTRANS
643 {
644 path = "/" + path; //NOTRANS
645 }
646 return path;
647 }
648
649 private static String resolveRelative( String basePath, String relPath )
650 {
651 // Concatenate the base path and relative spec together,
652 // eliminating any occurrences of "." or ".." in the relative
653 // spec. If "." or ".." occur in the baseURI, they are treated
654 // as literal names of directories.
655 //
656 // Note that the tokenization is done here directly for performance
657 // reasons rather than through a StringTokenizer.
658
659 // The basePath tokenization is set to return the "/" tokens
660 // as well. This is needed to detect a UNC path, which begins
661 // with two slashes.
662 final ArrayList pathElems = new ArrayList();
663 for ( int pos = 0 ;; )
664 {
665 final int newPos = basePath.indexOf( '/', pos );
666 final boolean done = newPos == -1;
667 final String substr = done
668 ? basePath.substring( pos )
669 : basePath.substring( pos, newPos );
670 if ( substr.length() > 0 || newPos == pos + 1 )
671 {
672 pathElems.add( substr );
673 }
674 if ( done )
675 {
676 break;
677 }
678 pos = newPos + 1;
679 }
680
681 // The relPath tokenization is not currently configured to return
682 // the "/" tokens, so there is an asymmetry here wrt basePath.
683 // There isn't a problem with this right now because file systems
684 // treat multiple slashes in the middle of the path as being a
685 // single delimiter, but there may be a very special-case problem
686 // with some schemes if there is a dependency on the occurrence
687 // of multiple slashes in the path part of the URI.
688 boolean lastElemIsRelativeDir = false;
689 for ( int pos = 0 ;; )
690 {
691 final int newPos = relPath.indexOf( '/', pos );
692 final boolean done = newPos == -1;
693 final String substr = done
694 ? relPath.substring( pos )
695 : relPath.substring( pos, newPos );
696 if ( substr.length() > 0 )
697 {
698 if ( substr.equals( ".." ) ) // NOTRANS
699 {
700 final int n = pathElems.size();
701 if ( n > 0 )
702 {
703 pathElems.remove( n - 1 );
704 }
705 lastElemIsRelativeDir = true;
706 }
707 else if ( substr.equals( "." ) ) //NOTRANS
708 {
709 lastElemIsRelativeDir = true;
710 }
711 else
712 {
713 pathElems.add( substr );
714 lastElemIsRelativeDir = false;
715 }
716 }
717 if ( done )
718 {
719 break;
720 }
721 pos = newPos + 1;
722 }
723
724 // Put the path elements together to form the new path specification.
725 final StringBuffer newPath = new StringBuffer();
726 if ( basePath.startsWith( "//" ) ) //NOTRANS
727 {
728 newPath.append( "//" ); //NOTRANS
729 }
730 else if ( basePath.startsWith( "/" ) ) //NOTRANS
731 {
732 newPath.append( '/' ); //NOTRANS
733 }
734 for ( Iterator iter = pathElems.iterator(); iter.hasNext(); )
735 {
736 newPath.append( iter.next().toString() ).append( '/' ); //NOTRANS
737 }
738 if ( !lastElemIsRelativeDir &&
739 !relPath.endsWith( "/" ) && //NOTRANS
740 relPath.length() != 0 )
741 {
742 final int length = newPath.length();
743 if ( length > 0 )
744 {
745 newPath.setLength( length - 1 );
746 }
747 }
748
749 return newPath.toString();
750 }
751
752 private static URI newJarFileURIImpl( String uriStr )
753 {
754 try
755 {
756 // No choice here -- must use the URI constructor directly
757 // (instead of using the preferred approach of going through
758 // URIFactory) because we have no way of knowing what's in
759 // the URI string. There may be problems on some JDK
760 // implementations if the jar/zip file resides on a path that
761 // has '#', '?', or whitespace in the name. That's because
762 // Sun's JDK has a number of bugs in the way it handles URIs.
763 return new URI( uriStr );
764 }
765 catch ( URISyntaxException e )
766 {
767 e.printStackTrace();
768 return null;
769 }
770 }
771 /**
772 * Private constructor prevents instantiation.
773 */
774 private URIFactory()
775 {
776 // NOP.
777 }
778
779 }