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

Quick Search    Search Deep

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 }