001    /**
002     * <copyright> 
003     *
004     * Copyright (c) 2002-2006 IBM Corporation and others.
005     * All rights reserved.   This program and the accompanying materials
006     * are made available under the terms of the Eclipse Public License v1.0
007     * which accompanies this distribution, and is available at
008     * http://www.eclipse.org/legal/epl-v10.html
009     * 
010     * Contributors: 
011     *   IBM - Initial API and implementation
012     *
013     * </copyright>
014     *
015     * $Id: URI.java,v 1.34 2008/10/02 16:06:51 emerks Exp $
016     */
017    package org.eclipse.emf.common.util;
018    
019    import java.io.File;
020    import java.lang.ref.WeakReference;
021    import java.util.ArrayList;
022    import java.util.Arrays;
023    import java.util.Collections;
024    import java.util.HashMap;
025    import java.util.HashSet;
026    import java.util.Iterator;
027    import java.util.List;
028    import java.util.Map;
029    import java.util.Set;
030    import java.util.StringTokenizer;
031    
032    /**
033     * A representation of a Uniform Resource Identifier (URI), as specified by
034     * <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>, with certain
035     * enhancements.  A <code>URI</code> instance can be created by specifying
036     * values for its components, or by providing a single URI string, which is
037     * parsed into its components.  Static factory methods whose names begin
038     * with "create" are used for both forms of object creation.  No public or
039     * protected constructors are provided; this class can not be subclassed.
040     *
041     * <p>Like <code>String</code>, <code>URI</code> is an immutable class;
042     * a <code>URI</code> instance offers several by-value methods that return a
043     * new <code>URI</code> object based on its current state.  Most useful,
044     * a relative <code>URI</code> can be {@link #resolve(URI) resolve}d against
045     * a base absolute <code>URI</code> -- the latter typically identifies the
046     * document in which the former appears.  The inverse to this is {@link
047     * #deresolve(URI) deresolve}, which answers the question, "what relative
048     * URI will resolve, against the given base, to this absolute URI?"
049     *
050     * <p>In the <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC</a>, much
051     * attention is focused on a hierarchical naming system used widely to
052     * locate resources via common protocols such as HTTP, FTP, and Gopher, and
053     * to identify files on a local file system.  Accordingly, most of this
054     * class's functionality is for handling such URIs, which can be identified
055     * via {@link #isHierarchical isHierarchical}.
056     *
057     * <p><a name="device_explanation">
058     * The primary enhancement beyond the RFC description is an optional
059     * device component.  Instead of treating the device as just another segment
060     * in the path, it can be stored as a separate component (almost a
061     * sub-authority), with the root below it.  For example, resolving
062     * <code>/bar</code> against <code>file:///c:/foo</code> would result in
063     * <code>file:///c:/bar</code> being returned.  Also, you cannot take
064     * the parent of a device, so resolving <code>..</code> against
065     * <code>file:///c:/</code> would not yield <code>file:///</code>, as you
066     * might expect.  This feature is useful when working with file-scheme
067     * URIs, as devices do not typically occur in protocol-based ones.  A
068     * device-enabled <code>URI</code> is created by parsing a string with
069     * {@link #createURI(String) createURI}; if the first segment of the path
070     * ends with the <code>:</code> character, it is stored (including the colon)
071     * as the device, instead.  Alternately, either the {@link
072     * #createHierarchicalURI(String, String, String, String, String) no-path}
073     * or the {@link #createHierarchicalURI(String, String, String, String[],
074     * String, String) absolute-path} form of <code>createHierarchicalURI()</code>
075     * can be used, in which a non-null <code>device</code> parameter can be
076     * specified.
077     *
078     * <p><a name="archive_explanation"> 
079     * The other enhancement provides support for the almost-hierarchical
080     * form used for files within archives, such as the JAR scheme, defined
081     * for the Java Platform in the documentation for {@link
082     * java.net.JarURLConnection}. By default, this support is enabled for
083     * absolute URIs with scheme equal to "jar", "zip", or "archive" (ignoring case), and
084     * is implemented by a hierarchical URI, whose authority includes the
085     * entire URI of the archive, up to and including the <code>!</code>
086     * character.  The URI of the archive must have no fragment.  The whole
087     * archive URI must have no device and an absolute path.  Special handling
088     * is supported for {@link #createURI(String) creating}, {@link
089     * #validArchiveAuthority validating}, {@link #devicePath getting the path}
090     * from, and {@link #toString() displaying} archive URIs. In all other
091     * operations, including {@link #resolve(URI) resolving} and {@link
092     * #deresolve(URI) deresolving}, they are handled like any ordinary URI.
093     * The schemes that identify archive URIs can be changed from their default
094     * by setting the <code>org.eclipse.emf.common.util.URI.archiveSchemes</code>
095     * system property. Multiple schemes should be space separated, and the test
096     * of whether a URI's scheme matches is always case-insensitive.
097     *
098     * <p>This implementation does not impose all of the restrictions on
099     * character validity that are specified in the RFC.  Static methods whose
100     * names begin with "valid" are used to test whether a given string is valid
101     * value for the various URI components.  Presently, these tests place no
102     * restrictions beyond what would have been required in order for {@link
103     * #createURI(String) createURI} to have parsed them correctly from a single
104     * URI string.  If necessary in the future, these tests may be made more
105     * strict, to better conform to the RFC.
106     * 
107     * <p>Another group of static methods, whose names begin with "encode", use
108     * percent escaping to encode any characters that are not permitted in the
109     * various URI components. Another static method is provided to {@link
110     * #decode decode} encoded strings.  An escaped character is represented as
111     * a percent symbol (<code>%</code>), followed by two hex digits that specify
112     * the character code.  These encoding methods are more strict than the
113     * validation methods described above.  They ensure validity according to the
114     * RFC, with one exception: non-ASCII characters.
115     *
116     * <p>The RFC allows only characters that can be mapped to 7-bit US-ASCII
117     * representations.  Non-ASCII, single-byte characters can be used only via
118     * percent escaping, as described above.  This implementation uses Java's
119     * Unicode <code>char</code> and <code>String</code> representations, and
120     * makes no attempt to encode characters 0xA0 and above.  Characters in the
121     * range 0x80-0x9F are still escaped.  In this respect, EMF's notion of a URI
122     * is actually more like an IRI (Internationalized Resource Identifier), for
123     * which an RFC is now in <href="http://www.w3.org/International/iri-edit/draft-duerst-iri-09.txt">draft
124     * form</a>.
125     *
126     * <p>Finally, note the difference between a <code>null</code> parameter to
127     * the static factory methods and an empty string.  The former signifies the
128     * absence of a given URI component, while the latter simply makes the
129     * component blank.  This can have a significant effect when resolving.  For
130     * example, consider the following two URIs: <code>/bar</code> (with no
131     * authority) and <code>///bar</code> (with a blank authority).  Imagine
132     * resolving them against a base with an authority, such as
133     * <code>http://www.eclipse.org/</code>.  The former case will yield
134     * <code>http://www.eclipse.org/bar</code>, as the base authority will be
135     * preserved.  In the latter case, the empty authority will override the
136     * base authority, resulting in <code>http:///bar</code>!
137     */
138    public final class URI
139    {
140      // Common to all URI types.
141      private final int hashCode;
142      private static final int HIERARICHICAL_FLAG = 0x0100;
143      private final String scheme;  // null -> relative URI reference
144      private final String authority;
145      private final String fragment;
146      private URI cachedTrimFragment;
147      private String cachedToString;
148      //private final boolean iri;
149      //private URI cachedASCIIURI;
150    
151      // Applicable only to a hierarchical URI.
152      private final String device;
153      private static final int ABSOLUTE_PATH_FLAG = 0x0010;
154      private final String[] segments; // empty last segment -> trailing separator
155      private final String query;
156    
157      // A cache of URIs, keyed by the strings from which they were created.
158      // The fragment of any URI is removed before caching it here, to minimize
159      // the size of the cache in the usual case where most URIs only differ by
160      // the fragment.
161      private static final URICache uriCache = new URICache();
162        
163      private static class URICache extends HashMap<String,WeakReference<URI>>
164      {
165        private static final long serialVersionUID = 1L;
166    
167        static final int MIN_LIMIT = 1000;
168        int count;
169        int limit = MIN_LIMIT;
170    
171        public synchronized URI get(String key)
172        {
173          WeakReference<URI> reference = super.get(key);
174          return reference == null ? null : reference.get();
175        }
176             
177        public synchronized void put(String key, URI value)
178        {
179          super.put(key, new WeakReference<URI>(value));
180          if (++count > limit)
181          {
182            cleanGCedValues();
183          }
184        }
185          
186        private void cleanGCedValues()
187        {
188          for (Iterator<Map.Entry<String,WeakReference<URI>>> i = entrySet().iterator(); i.hasNext(); )
189          {
190            Map.Entry<String,WeakReference<URI>> entry = i.next();
191            WeakReference<URI> reference = entry.getValue();
192            if (reference.get() == null)
193            {
194              i.remove();
195            }
196          }
197          count = 0;
198          limit = Math.max(MIN_LIMIT, size() / 2);
199        }
200      }
201    
202      // The lower-cased schemes that will be used to identify archive URIs.
203      private static final Set<String> archiveSchemes;
204    
205      // Identifies a file-type absolute URI.
206      private static final String SCHEME_FILE = "file";
207      private static final String SCHEME_JAR = "jar";
208      private static final String SCHEME_ZIP = "zip";
209      private static final String SCHEME_ARCHIVE = "archive";
210      private static final String SCHEME_PLATFORM = "platform";
211    
212      // Special segment values interpreted at resolve and resolve time.
213      private static final String SEGMENT_EMPTY = "";
214      private static final String SEGMENT_SELF = ".";
215      private static final String SEGMENT_PARENT = "..";
216      private static final String[] NO_SEGMENTS = new String[0];
217    
218      // Separators for parsing a URI string.
219      private static final char SCHEME_SEPARATOR = ':';
220      private static final String AUTHORITY_SEPARATOR = "//";
221      private static final char DEVICE_IDENTIFIER = ':';
222      private static final char SEGMENT_SEPARATOR = '/';
223      private static final char QUERY_SEPARATOR = '?';
224      private static final char FRAGMENT_SEPARATOR = '#';
225      private static final char USER_INFO_SEPARATOR = '@';
226      private static final char PORT_SEPARATOR = ':';
227      private static final char FILE_EXTENSION_SEPARATOR = '.';
228      private static final char ARCHIVE_IDENTIFIER = '!';
229      private static final String ARCHIVE_SEPARATOR = "!/";
230    
231      // Characters to use in escaping.
232      private static final char ESCAPE = '%';
233      private static final char[] HEX_DIGITS = {
234        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
235    
236      // Some character classes, as defined in RFC 2396's BNF for URI.
237      // These are 128-bit bitmasks, stored as two longs, where the Nth bit is set
238      // iff the ASCII character with value N is included in the set.  These are
239      // created with the highBitmask() and lowBitmask() methods defined below,
240      // and a character is tested against them using matches().
241      //
242      private static final long ALPHA_HI = highBitmask('a', 'z') | highBitmask('A', 'Z');
243      private static final long ALPHA_LO = lowBitmask('a', 'z')  | lowBitmask('A', 'Z');
244      private static final long DIGIT_HI = highBitmask('0', '9');
245      private static final long DIGIT_LO = lowBitmask('0', '9');
246      private static final long ALPHANUM_HI = ALPHA_HI | DIGIT_HI;
247      private static final long ALPHANUM_LO = ALPHA_LO | DIGIT_LO;
248      private static final long HEX_HI = DIGIT_HI | highBitmask('A', 'F') | highBitmask('a', 'f');
249      private static final long HEX_LO = DIGIT_LO | lowBitmask('A', 'F')  | lowBitmask('a', 'f');
250      private static final long UNRESERVED_HI = ALPHANUM_HI | highBitmask("-_.!~*'()"); 
251      private static final long UNRESERVED_LO = ALPHANUM_LO | lowBitmask("-_.!~*'()");
252      private static final long RESERVED_HI = highBitmask(";/?:@&=+$,");
253      private static final long RESERVED_LO = lowBitmask(";/?:@&=+$,");
254      private static final long URIC_HI = RESERVED_HI | UNRESERVED_HI;  // | ucschar | escaped
255      private static final long URIC_LO = RESERVED_LO | UNRESERVED_LO;
256    
257      // Additional useful character classes, including characters valid in certain
258      // URI components and separators used in parsing them out of a string. 
259      //
260      private static final long SEGMENT_CHAR_HI = UNRESERVED_HI | highBitmask(";:@&=+$,");  // | ucschar | escaped
261      private static final long SEGMENT_CHAR_LO = UNRESERVED_LO | lowBitmask(";:@&=+$,");
262      private static final long PATH_CHAR_HI = SEGMENT_CHAR_HI | highBitmask('/');  // | ucschar | escaped
263      private static final long PATH_CHAR_LO = SEGMENT_CHAR_LO | lowBitmask('/');
264    //  private static final long SCHEME_CHAR_HI = ALPHANUM_HI | highBitmask("+-.");
265    //  private static final long SCHEME_CHAR_LO = ALPHANUM_LO | lowBitmask("+-.");
266      private static final long MAJOR_SEPARATOR_HI = highBitmask(":/?#");
267      private static final long MAJOR_SEPARATOR_LO = lowBitmask(":/?#");
268      private static final long SEGMENT_END_HI = highBitmask("/?#");
269      private static final long SEGMENT_END_LO = lowBitmask("/?#");
270    
271      // The intent of this was to switch over to encoding platform resource URIs
272      // by default, but allow people to use a system property to avoid this.
273      // However, that caused problems for people and we had to go back to not
274      // encoding and introduce yet another factory method that explicitly enables
275      // encoding.
276      //
277      private static final boolean ENCODE_PLATFORM_RESOURCE_URIS =
278        System.getProperty("org.eclipse.emf.common.util.URI.encodePlatformResourceURIs") != null &&
279        !"false".equalsIgnoreCase(System.getProperty("org.eclipse.emf.common.util.URI.encodePlatformResourceURIs"));
280    
281      // Static initializer for archiveSchemes.
282      static
283      {
284        Set<String> set = new HashSet<String>();
285        String propertyValue = System.getProperty("org.eclipse.emf.common.util.URI.archiveSchemes");
286    
287        if (propertyValue == null)
288        {
289          set.add(SCHEME_JAR);
290          set.add(SCHEME_ZIP);
291          set.add(SCHEME_ARCHIVE);
292        }
293        else
294        { 
295          for (StringTokenizer t = new StringTokenizer(propertyValue); t.hasMoreTokens(); )
296          {
297            set.add(t.nextToken().toLowerCase());
298          }
299        }
300        
301        archiveSchemes = Collections.unmodifiableSet(set);
302      }
303    
304      // Returns the lower half bitmask for the given ASCII character.
305      private static long lowBitmask(char c)
306      {
307        return c < 64 ? 1L << c : 0L;
308      }
309    
310      // Returns the upper half bitmask for the given ACSII character.
311      private static long highBitmask(char c)
312      {
313        return c >= 64 && c < 128 ? 1L << (c - 64) : 0L;
314      }
315    
316      // Returns the lower half bitmask for all ASCII characters between the two
317      // given characters, inclusive.
318      private static long lowBitmask(char from, char to)
319      {
320        long result = 0L;
321        if (from < 64 && from <= to)
322        {
323          to = to < 64 ? to : 63;
324          for (char c = from; c <= to; c++)
325          {
326            result |= (1L << c);
327          }
328        }
329        return result;
330      }
331    
332      // Returns the upper half bitmask for all AsCII characters between the two
333      // given characters, inclusive.
334      private static long highBitmask(char from, char to)
335      {
336        return to < 64 ? 0 : lowBitmask((char)(from < 64 ? 0 : from - 64), (char)(to - 64));
337      }
338    
339      // Returns the lower half bitmask for all the ASCII characters in the given
340      // string.
341      private static long lowBitmask(String chars)
342      {
343        long result = 0L;
344        for (int i = 0, len = chars.length(); i < len; i++)
345        {
346          char c = chars.charAt(i);
347          if (c < 64) result |= (1L << c);
348        }
349        return result;
350      }
351    
352      // Returns the upper half bitmask for all the ASCII characters in the given
353      // string.
354      private static long highBitmask(String chars)
355      {
356        long result = 0L;
357        for (int i = 0, len = chars.length(); i < len; i++)
358        {
359          char c = chars.charAt(i);
360          if (c >= 64 && c < 128) result |= (1L << (c - 64));
361        }
362        return result;
363      }
364    
365      // Returns whether the given character is in the set specified by the given
366      // bitmask.
367      private static boolean matches(char c, long highBitmask, long lowBitmask)
368      {
369        if (c >= 128) return false;
370        return c < 64 ?
371          ((1L << c) & lowBitmask) != 0 :
372          ((1L << (c - 64)) & highBitmask) != 0;
373      }
374    
375      // Debugging method: converts the given long to a string of binary digits.
376    /*
377      private static String toBits(long l)
378      {
379        StringBuffer result = new StringBuffer();
380        for (int i = 0; i < 64; i++)
381        {
382          boolean b = (l & 1L) != 0;
383          result.insert(0, b ? '1' : '0');
384          l >>= 1;
385        }
386        return result.toString();
387      }
388    */
389    
390      /**
391       * Static factory method for a generic, non-hierarchical URI.  There is no
392       * concept of a relative non-hierarchical URI; such an object cannot be
393       * created.
394       *
395       * @exception java.lang.IllegalArgumentException if <code>scheme</code> is
396       * null, if <code>scheme</code> is an <a href="#archive_explanation">archive
397       * URI</a> scheme, or if <code>scheme</code>, <code>opaquePart</code>, or
398       * <code>fragment</code> is not valid according to {@link #validScheme
399       * validScheme}, {@link #validOpaquePart validOpaquePart}, or {@link
400       * #validFragment validFragment}, respectively.
401       */
402      public static URI createGenericURI(String scheme, String opaquePart,
403                                         String fragment)
404      {
405        if (scheme == null)
406        {
407          throw new IllegalArgumentException("relative non-hierarchical URI");
408        }
409    
410        if (isArchiveScheme(scheme))
411        {
412          throw new IllegalArgumentException("non-hierarchical archive URI");
413        }
414    
415        validateURI(false, scheme, opaquePart, null, false, NO_SEGMENTS, null, fragment);
416        return new URI(false, scheme, opaquePart, null, false, NO_SEGMENTS, null, fragment);
417      }
418    
419      /**
420       * Static factory method for a hierarchical URI with no path.  The
421       * URI will be relative if <code>scheme</code> is non-null, and absolute
422       * otherwise.  An absolute URI with no path requires a non-null
423       * <code>authority</code> and/or <code>device</code>.
424       *
425       * @exception java.lang.IllegalArgumentException if <code>scheme</code> is
426       * non-null while <code>authority</code> and <code>device</code> are null,
427       * if <code>scheme</code> is an <a href="#archive_explanation">archive
428       * URI</a> scheme, or if <code>scheme</code>, <code>authority</code>,
429       * <code>device</code>, <code>query</code>, or <code>fragment</code> is not
430       * valid according to {@link #validScheme validSheme}, {@link
431       * #validAuthority validAuthority}, {@link #validDevice validDevice},
432       * {@link #validQuery validQuery}, or {@link #validFragment validFragment},
433       * respectively.
434       */
435      public static URI createHierarchicalURI(String scheme, String authority,
436                                              String device, String query,
437                                              String fragment)
438      {
439        if (scheme != null && authority == null && device == null)
440        {
441          throw new IllegalArgumentException(
442            "absolute hierarchical URI without authority, device, path");
443        }
444    
445        if (isArchiveScheme(scheme))
446        {
447          throw new IllegalArgumentException("archive URI with no path");
448        }
449    
450        validateURI(true, scheme, authority, device, false, NO_SEGMENTS, query, fragment);
451        return new URI(true, scheme, authority, device, false, NO_SEGMENTS, query, fragment);
452      }
453    
454      /**
455       * Static factory method for a hierarchical URI with absolute path.
456       * The URI will be relative if <code>scheme</code> is non-null, and
457       * absolute otherwise. 
458       *
459       * @param segments an array of non-null strings, each representing one
460       * segment of the path.  As an absolute path, it is automatically
461       * preceded by a <code>/</code> separator.  If desired, a trailing
462       * separator should be represented by an empty-string segment as the last
463       * element of the array. 
464       *
465       * @exception java.lang.IllegalArgumentException if <code>scheme</code> is
466       * an <a href="#archive_explanation">archive URI</a> scheme and 
467       * <code>device</code> is non-null, or if <code>scheme</code>,
468       * <code>authority</code>, <code>device</code>, <code>segments</code>,
469       * <code>query</code>, or <code>fragment</code> is not valid according to
470       * {@link #validScheme validScheme}, {@link #validAuthority validAuthority}
471       * or {@link #validArchiveAuthority validArchiveAuthority}, {@link
472       * #validDevice validDevice}, {@link #validSegments validSegments}, {@link
473       * #validQuery validQuery}, or {@link #validFragment validFragment}, as
474       * appropriate.
475       */
476      public static URI createHierarchicalURI(String scheme, String authority,
477                                              String device, String[] segments,
478                                              String query, String fragment)
479      {
480        if (isArchiveScheme(scheme) && device != null)
481        {
482          throw new IllegalArgumentException("archive URI with device");
483        }
484    
485        segments = fix(segments);
486        validateURI(true, scheme, authority, device, true, segments, query, fragment);
487        return new URI(true, scheme, authority, device, true, segments, query, fragment);
488      }
489    
490      /**
491       * Static factory method for a relative hierarchical URI with relative
492       * path.
493       *
494       * @param segments an array of non-null strings, each representing one
495       * segment of the path.  A trailing separator is represented by an
496       * empty-string segment at the end of the array.
497       *
498       * @exception java.lang.IllegalArgumentException if <code>segments</code>,
499       * <code>query</code>, or <code>fragment</code> is not valid according to 
500       * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
501       * {@link #validFragment validFragment}, respectively.
502       */
503      public static URI createHierarchicalURI(String[] segments, String query,
504                                              String fragment)
505      {
506        segments = fix(segments);
507        validateURI(true, null, null, null, false, segments, query, fragment);
508        return new URI(true, null, null, null, false, segments, query, fragment);
509      }
510    
511      // Converts null to length-zero array, and clones array to ensure
512      // immutability.
513      private static String[] fix(String[] segments)
514      {
515        return segments == null ? NO_SEGMENTS : (String[])segments.clone();
516      }
517      
518      /**
519       * Static factory method based on parsing a URI string, with 
520       * <a href="#device_explanation">explicit device support</a> and handling
521       * for <a href="#archive_explanation">archive URIs</a> enabled. The
522       * specified string is parsed as described in <a
523       * href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>, and an
524       * appropriate <code>URI</code> is created and returned.  Note that
525       * validity testing is not as strict as in the RFC; essentially, only
526       * separator characters are considered.  This method also does not perform
527       * encoding of invalid characters, so it should only be used when the URI
528       * string is known to have already been encoded, so as to avoid double 
529       * encoding.
530       *
531       * @exception java.lang.IllegalArgumentException if any component parsed
532       * from <code>uri</code> is not valid according to {@link #validScheme
533       * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
534       * #validAuthority validAuthority}, {@link #validArchiveAuthority
535       * validArchiveAuthority}, {@link #validDevice validDevice}, {@link
536       * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
537       * #validFragment validFragment}, as appropriate.
538       */
539      public static URI createURI(String uri)
540      {
541        return createURIWithCache(uri); 
542      }
543    
544      /**
545       * Static factory method that encodes and parses the given URI string.
546       * Appropriate encoding is performed for each component of the URI.
547       * If more than one <code>#</code> is in the string, the last one is
548       * assumed to be the fragment's separator, and any others are encoded.
549       * This method is the simplest way to safely parse an arbitrary URI string.
550       *  
551       * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
552       * unescaped if they already begin a valid three-character escape sequence;
553       * <code>false</code> to encode all <code>%</code> characters.  This
554       * capability is provided to allow partially encoded URIs to be "fixed",
555       * while avoiding adding double encoding; however, it is usual just to
556       * specify <code>false</code> to perform ordinary encoding.
557       *
558       * @exception java.lang.IllegalArgumentException if any component parsed
559       * from <code>uri</code> is not valid according to {@link #validScheme
560       * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
561       * #validAuthority validAuthority}, {@link #validArchiveAuthority
562       * validArchiveAuthority}, {@link #validDevice validDevice}, {@link
563       * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
564       * #validFragment validFragment}, as appropriate.
565       */
566      public static URI createURI(String uri, boolean ignoreEscaped)
567      {
568        return createURIWithCache(encodeURI(uri, ignoreEscaped, FRAGMENT_LAST_SEPARATOR));
569      }
570    
571      /**
572       * When specified as the last argument to {@link #createURI(String, boolean, int)
573       * createURI}, indicates that there is no fragment, so any <code>#</code> characters
574       * should be encoded.
575       * @see #createURI(String, boolean, int)
576       */
577      public static final int FRAGMENT_NONE = 0;
578    
579      /**
580       * When specified as the last argument to {@link #createURI(String, boolean, int)
581       * createURI}, indicates that the first <code>#</code> character should be taken as
582       * the fragment separator, and any others should be encoded.
583       * @see #createURI(String, boolean, int)
584       */
585      public static final int FRAGMENT_FIRST_SEPARATOR = 1;
586    
587      /**
588       * When specified as the last argument to {@link #createURI(String, boolean, int)
589       * createURI}, indicates that the last <code>#</code> character should be taken as
590       * the fragment separator, and any others should be encoded.
591       * @see #createURI(String, boolean, int)
592       */
593      public static final int FRAGMENT_LAST_SEPARATOR = 2; 
594    
595      /**
596       * Static factory method that encodes and parses the given URI string.
597       * Appropriate encoding is performed for each component of the URI.
598       * Control is provided over which, if any, <code>#</code> should be 
599       * taken as the fragment separator and which should be encoded.
600       * This method is the preferred way to safely parse an arbitrary URI string
601       * that is known to contain <code>#</code> characters in the fragment or to
602       * have no fragment at all.
603       * 
604       * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
605       * unescaped if they already begin a valid three-character escape sequence;
606       * <code>false</code> to encode all <code>%</code> characters.  This
607       * capability is provided to allow partially encoded URIs to be "fixed",
608       * while avoiding adding double encoding; however, it is usual just to
609       * specify <code>false</code> to perform ordinary encoding.
610       * 
611       * @param fragmentLocationStyle one of {@link #FRAGMENT_NONE},
612       * {@link #FRAGMENT_FIRST_SEPARATOR}, or {@link #FRAGMENT_LAST_SEPARATOR},
613       * indicating which, if any, of the <code>#</code> characters should be
614       * considered the fragment separator. Any others will be encoded.
615       *
616       * @exception java.lang.IllegalArgumentException if any component parsed
617       * from <code>uri</code> is not valid according to {@link #validScheme
618       * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
619       * #validAuthority validAuthority}, {@link #validArchiveAuthority
620       * validArchiveAuthority}, {@link #validDevice validDevice}, {@link
621       * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
622       * #validFragment validFragment}, as appropriate.
623       */
624      public static URI createURI(String uri, boolean ignoreEscaped, int fragmentLocationStyle)
625      {
626        return createURIWithCache(encodeURI(uri, ignoreEscaped, fragmentLocationStyle));
627      }
628    
629      /**
630       * Static factory method based on parsing a URI string, with 
631       * <a href="#device_explanation">explicit device support</a> enabled.  
632       * Note that validity testing is not a strict as in the RFC; essentially,
633       * only separator characters are considered.  So, for example, non-Latin
634       * alphabet characters appearing in the scheme would not be considered an
635       * error.
636       *
637       * @exception java.lang.IllegalArgumentException if any component parsed
638       * from <code>uri</code> is not valid according to {@link #validScheme
639       * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
640       * #validAuthority validAuthority}, {@link #validArchiveAuthority
641       * validArchiveAuthority}, {@link #validDevice validDevice}, {@link
642       * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
643       * #validFragment validFragment}, as appropriate.
644       *
645       * @deprecated Use {@link #createURI(String) createURI}, which now has explicit
646       * device support enabled. The two methods now operate identically.
647       */
648      @Deprecated
649      public static URI createDeviceURI(String uri)
650      {
651        return createURIWithCache(uri);
652      }
653    
654      // Uses a cache to speed up creation of a URI from a string.  The cache
655      // is consulted to see if the URI, less any fragment, has already been
656      // created.  If needed, the fragment is re-appended to the cached URI,
657      // which is considerably more efficient than creating the whole URI from
658      // scratch.  If the URI wasn't found in the cache, it is created using
659      // parseIntoURI() and then cached.  This method should always be used
660      // by string-parsing factory methods, instead of parseIntoURI() directly.
661      /**
662       * This method was included in the public API by mistake.
663       * 
664       * @deprecated Please use {@link #createURI(String) createURI} instead.
665       */
666      @Deprecated
667      public static URI createURIWithCache(String uri)
668      {
669        int i = uri.indexOf(FRAGMENT_SEPARATOR);
670        String base = i == -1 ? uri : uri.substring(0, i);
671        String fragment = i == -1 ? null : uri.substring(i + 1);
672    
673        URI result = uriCache.get(base);
674    
675        if (result == null)
676        {
677          result = parseIntoURI(base);
678          uriCache.put(base, result);
679        }
680    
681        if (fragment != null)
682        {
683          result = result.appendFragment(fragment);
684        }
685        return result;
686      }
687    
688      // String-parsing implementation.
689      private static URI parseIntoURI(String uri)
690      {
691        boolean hierarchical = true;
692        String scheme = null;
693        String authority = null;
694        String device = null;
695        boolean absolutePath = false;
696        String[] segments = NO_SEGMENTS;
697        String query = null;
698        String fragment = null;
699    
700        int i = 0;
701        int j = find(uri, i, MAJOR_SEPARATOR_HI, MAJOR_SEPARATOR_LO);
702    
703        if (j < uri.length() && uri.charAt(j) == SCHEME_SEPARATOR)
704        {
705          scheme = uri.substring(i, j);
706          i = j + 1;
707        }
708    
709        boolean archiveScheme = isArchiveScheme(scheme);
710        if (archiveScheme)
711        {
712          j = uri.lastIndexOf(ARCHIVE_SEPARATOR);
713          if (j == -1)
714          {
715            throw new IllegalArgumentException("no archive separator");
716          }
717          hierarchical = true;
718          authority = uri.substring(i, ++j);
719          i = j;
720        }
721        else if (uri.startsWith(AUTHORITY_SEPARATOR, i))
722        {
723          i += AUTHORITY_SEPARATOR.length();
724          j = find(uri, i, SEGMENT_END_HI, SEGMENT_END_LO);
725          authority = uri.substring(i, j);
726          i = j;
727        }
728        else if (scheme != null &&
729                 (i == uri.length() || uri.charAt(i) != SEGMENT_SEPARATOR))
730        {
731          hierarchical = false;
732          j = uri.indexOf(FRAGMENT_SEPARATOR, i);
733          if (j == -1) j = uri.length();
734          authority = uri.substring(i, j);
735          i = j;
736        }
737    
738        if (!archiveScheme && i < uri.length() && uri.charAt(i) == SEGMENT_SEPARATOR)
739        {
740          j = find(uri, i + 1, SEGMENT_END_HI, SEGMENT_END_LO);
741          String s = uri.substring(i + 1, j);
742          
743          if (s.length() > 0 && s.charAt(s.length() - 1) == DEVICE_IDENTIFIER)
744          {
745            device = s;
746            i = j;
747          }
748        }
749    
750        if (i < uri.length() && uri.charAt(i) == SEGMENT_SEPARATOR)
751        {
752          i++;
753          absolutePath = true;
754        }
755    
756        if (segmentsRemain(uri, i))
757        {
758          List<String> segmentList = new ArrayList<String>();
759    
760          while (segmentsRemain(uri, i))
761          {
762            j = find(uri, i, SEGMENT_END_HI, SEGMENT_END_LO);
763            segmentList.add(uri.substring(i, j));
764            i = j;
765    
766            if (i < uri.length() && uri.charAt(i) == SEGMENT_SEPARATOR)
767            {
768              if (!segmentsRemain(uri, ++i)) segmentList.add(SEGMENT_EMPTY);
769            }
770          }
771          segments = new String[segmentList.size()];
772          segmentList.toArray(segments);
773        }
774    
775        if (i < uri.length() && uri.charAt(i) == QUERY_SEPARATOR)
776        {
777          j = uri.indexOf(FRAGMENT_SEPARATOR, ++i);
778          if (j == -1) j = uri.length();
779          query = uri.substring(i, j);
780          i = j;
781        }
782    
783        if (i < uri.length()) // && uri.charAt(i) == FRAGMENT_SEPARATOR (implied)
784        {
785          fragment = uri.substring(++i);
786        }
787    
788        validateURI(hierarchical, scheme, authority, device, absolutePath, segments, query, fragment);
789        return new URI(hierarchical, scheme, authority, device, absolutePath, segments, query, fragment);
790      }
791    
792      // Checks whether the string contains any more segments after the one that
793      // starts at position i.
794      private static boolean segmentsRemain(String uri, int i)
795      {
796        return i < uri.length() && uri.charAt(i) != QUERY_SEPARATOR &&
797          uri.charAt(i) != FRAGMENT_SEPARATOR;
798      }
799    
800      // Finds the next occurrence of one of the characters in the set represented
801      // by the given bitmask in the given string, beginning at index i. The index
802      // of the first found character, or s.length() if there is none, is
803      // returned.  Before searching, i is limited to the range [0, s.length()].
804      //
805      private static int find(String s, int i, long highBitmask, long lowBitmask)
806      {
807        int len = s.length();
808        if (i >= len) return len;
809    
810        for (i = i > 0 ? i : 0; i < len; i++)
811        {
812          if (matches(s.charAt(i), highBitmask, lowBitmask)) break;
813        }
814        return i;
815      }
816    
817      /**
818       * Static factory method based on parsing a {@link java.io.File} path
819       * string.  The <code>pathName</code> is converted into an appropriate
820       * form, as follows: platform specific path separators are converted to
821       * <code>/<code>; the path is encoded; and a "file" scheme and, if missing,
822       * a leading <code>/</code>, are added to an absolute path.  The result
823       * is then parsed using {@link #createURI(String) createURI}.
824       *
825       * <p>The encoding step escapes all spaces, <code>#</code> characters, and
826       * other characters disallowed in URIs, as well as <code>?</code>, which
827       * would delimit a path from a query.  Decoding is automatically performed
828       * by {@link #toFileString toFileString}, and can be applied to the values
829       * returned by other accessors by via the static {@link #decode(String)
830       * decode} method.
831       *
832       * <p>A relative path with a specified device (something like
833       * <code>C:myfile.txt</code>) cannot be expressed as a valid URI.
834       * 
835       * @exception java.lang.IllegalArgumentException if <code>pathName</code>
836       * specifies a device and a relative path, or if any component of the path
837       * is not valid according to {@link #validAuthority validAuthority}, {@link
838       * #validDevice validDevice}, or {@link #validSegments validSegments},
839       * {@link #validQuery validQuery}, or {@link #validFragment validFragment}.
840       */
841      public static URI createFileURI(String pathName)
842      {
843        File file = new File(pathName);
844        String uri = File.separatorChar != '/' ? pathName.replace(File.separatorChar, SEGMENT_SEPARATOR) : pathName;
845        uri = encode(uri, PATH_CHAR_HI, PATH_CHAR_LO, false);
846        if (file.isAbsolute())
847        {
848          URI result = createURI((uri.charAt(0) == SEGMENT_SEPARATOR ? "file:" : "file:/") + uri);
849          return result;
850        }
851        else
852        {
853          URI result = createURI(uri);
854          if (result.scheme() != null)
855          {
856            throw new IllegalArgumentException("invalid relative pathName: " + pathName);
857          }
858          return result;
859        }
860      }
861    
862      /**
863       * Static factory method based on parsing a workspace-relative path string.
864       *
865       * <p>The <code>pathName</code> must be of the form:
866       * <pre>
867       *   /project-name/path</pre>
868       *
869       * <p>Platform-specific path separators will be converted to slashes.
870       * If not included, the leading path separator will be added.  The
871       * result will be of this form, which is parsed using {@link #createURI(String)
872       * createURI}:
873       * <pre>
874       *   platform:/resource/project-name/path</pre>
875       *
876       * <p>This scheme supports relocatable projects in Eclipse and in
877       * stand-alone EMF.
878       *
879       * <p>Path encoding is performed only if the
880       * <code>org.eclipse.emf.common.util.URI.encodePlatformResourceURIs</code> 
881       * system property is set to "true". Decoding can be performed with the
882       * static {@link #decode(String) decode} method.
883       * 
884       * @exception java.lang.IllegalArgumentException if any component parsed
885       * from the path is not valid according to {@link #validDevice validDevice},
886       * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
887       * {@link #validFragment validFragment}.
888       *
889       * @see org.eclipse.core.runtime.Platform#resolve
890       * @see #createPlatformResourceURI(String, boolean)
891       * @deprecated Use {@link #createPlatformResourceURI(String, boolean)} instead.
892       */
893      @Deprecated
894      public static URI createPlatformResourceURI(String pathName)
895      {
896        return createPlatformResourceURI(pathName, ENCODE_PLATFORM_RESOURCE_URIS);
897      }
898    
899      /**
900       * Static factory method based on parsing a workspace-relative path string,
901       * with an option to encode the created URI.
902       *
903       * <p>The <code>pathName</code> must be of the form:
904       * <pre>
905       *   /project-name/path</pre>
906       *
907       * <p>Platform-specific path separators will be converted to slashes.
908       * If not included, the leading path separator will be added.  The
909       * result will be of this form, which is parsed using {@link #createURI(String)
910       * createURI}:
911       * <pre>
912       *   platform:/resource/project-name/path</pre>
913       *
914       * <p>This scheme supports relocatable projects in Eclipse and in
915       * stand-alone EMF.
916       *
917       * <p>Depending on the <code>encode</code> argument, the path may be
918       * automatically encoded to escape all spaces, <code>#</code> characters,
919       * and other characters disallowed in URIs, as well as <code>?</code>,
920       * which would delimit a path from a query.  Decoding can be performed with
921       * the static {@link #decode(String) decode} method. It is strongly
922       * recommended to specify <code>true</code> to enable encoding, unless the
923       * path string has already been encoded.
924       * 
925       * @exception java.lang.IllegalArgumentException if any component parsed
926       * from the path is not valid according to {@link #validDevice validDevice},
927       * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
928       * {@link #validFragment validFragment}.
929       *
930       * @see org.eclipse.core.runtime.Platform#resolve
931       */
932      public static URI createPlatformResourceURI(String pathName, boolean encode)
933      {
934        return createPlatformURI("platform:/resource", "platform:/resource/", pathName, encode);
935      }
936    
937      /**
938       * Static factory method based on parsing a plug-in-based path string,
939       * with an option to encode the created URI.
940       *
941       * <p>The <code>pathName</code> must be of the form:
942       * <pre>
943       *   /plugin-id/path</pre>
944       *
945       * <p>Platform-specific path separators will be converted to slashes.
946       * If not included, the leading path separator will be added.  The
947       * result will be of this form, which is parsed using {@link #createURI(String)
948       * createURI}:
949       * <pre>
950       *   platform:/plugin/plugin-id/path</pre>
951       *
952       * <p>This scheme supports relocatable plug-in content in Eclipse.
953       *
954       * <p>Depending on the <code>encode</code> argument, the path may be
955       * automatically encoded to escape all spaces, <code>#</code> characters,
956       * and other characters disallowed in URIs, as well as <code>?</code>,
957       * which would delimit a path from a query.  Decoding can be performed with
958       * the static {@link #decode(String) decode} method. It is strongly
959       * recommended to specify <code>true</code> to enable encoding, unless the
960       * path string has already been encoded.
961       * 
962       * @exception java.lang.IllegalArgumentException if any component parsed
963       * from the path is not valid according to {@link #validDevice validDevice},
964       * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
965       * {@link #validFragment validFragment}.
966       *
967       * @see org.eclipse.core.runtime.Platform#resolve
968       * @since org.eclipse.emf.common 2.3
969       */
970      public static URI createPlatformPluginURI(String pathName, boolean encode)
971      {
972        return createPlatformURI("platform:/plugin", "platform:/plugin/", pathName, encode);
973      }
974    
975      // Private constructor for use of platform factory methods.
976      private static URI createPlatformURI(String unrootedBase, String rootedBase, String pathName, boolean encode)
977      {
978        if (File.separatorChar != SEGMENT_SEPARATOR)
979        {
980          pathName = pathName.replace(File.separatorChar, SEGMENT_SEPARATOR);
981        }
982    
983        if (encode)
984        {
985          pathName = encode(pathName, PATH_CHAR_HI, PATH_CHAR_LO, false);
986        }
987        URI result = createURI((pathName.charAt(0) == SEGMENT_SEPARATOR ? unrootedBase : rootedBase) + pathName);
988        return result;
989      }
990      
991      // Private constructor for use of static factory methods.
992      private URI(boolean hierarchical, String scheme, String authority,
993                  String device, boolean absolutePath, String[] segments,
994                  String query, String fragment)
995      {
996        int hashCode = 0;
997        //boolean iri = false;
998    
999        if (scheme != null)
1000        {
1001          hashCode ^= scheme.toLowerCase().hashCode();
1002        }
1003        if (authority != null)
1004        {
1005          hashCode ^= authority.hashCode();
1006          //iri = iri || containsNonASCII(authority);
1007        }
1008        if (device != null)
1009        {
1010          hashCode ^= device.hashCode();
1011          //iri = iri || containsNonASCII(device);
1012        }
1013        if (query != null)
1014        {
1015          hashCode ^= query.hashCode();
1016          //iri = iri || containsNonASCII(query);
1017        }
1018        if (fragment != null)
1019        {
1020          hashCode ^= fragment.hashCode();
1021          //iri = iri || containsNonASCII(fragment);
1022        }
1023    
1024        for (int i = 0, len = segments.length; i < len; i++)
1025        {
1026          hashCode ^= segments[i].hashCode();
1027          //iri = iri || containsNonASCII(segments[i]);
1028        }
1029    
1030        if (hierarchical)
1031        {
1032          hashCode |= HIERARICHICAL_FLAG;
1033        }
1034        else
1035        {
1036          hashCode &= ~HIERARICHICAL_FLAG;
1037        }
1038        if (absolutePath)
1039        {
1040          hashCode |= ABSOLUTE_PATH_FLAG;
1041        }
1042        else
1043        {
1044          hashCode &= ~ABSOLUTE_PATH_FLAG;
1045        }
1046        this.hashCode = hashCode;
1047        //this.iri = iri;
1048        this.scheme = scheme == null ? null : scheme.intern();
1049        this.authority = authority;
1050        this.device = device;
1051        this.segments = segments;
1052        this.query = query;
1053        this.fragment = fragment;
1054      }
1055      
1056      // Validates all of the URI components.  Factory methods should call this
1057      // before using the constructor, though they must ensure that the
1058      // inter-component requirements described in their own Javadocs are all
1059      // satisfied, themselves.  If a new URI is being constructed out of
1060      // an existing URI, this need not be called.  Instead, just the new
1061      // components may be validated individually.
1062      private static void validateURI(boolean hierarchical, String scheme,
1063                                        String authority, String device,
1064                                        boolean absolutePath, String[] segments,
1065                                        String query, String fragment)
1066      {
1067        if (!validScheme(scheme))
1068        {
1069          throw new IllegalArgumentException("invalid scheme: " + scheme);
1070        }
1071        if (!hierarchical && !validOpaquePart(authority))
1072        {
1073          throw new IllegalArgumentException("invalid opaquePart: " + authority);
1074        }
1075        if (hierarchical && !isArchiveScheme(scheme) && !validAuthority(authority))
1076        {
1077          throw new IllegalArgumentException("invalid authority: " + authority);
1078        }
1079        if (hierarchical && isArchiveScheme(scheme) && !validArchiveAuthority(authority))
1080        {
1081          throw new IllegalArgumentException("invalid authority: " + authority);
1082        }
1083        if (!validDevice(device))
1084        {
1085          throw new IllegalArgumentException("invalid device: " + device);
1086        }
1087        if (!validSegments(segments))
1088        {
1089          String s = segments == null ? "invalid segments: null" :
1090            "invalid segment: " + firstInvalidSegment(segments);
1091          throw new IllegalArgumentException(s);
1092        }
1093        if (!validQuery(query))
1094        {
1095          throw new IllegalArgumentException("invalid query: " + query);
1096        }
1097        if (!validFragment(fragment))
1098        {
1099          throw new IllegalArgumentException("invalid fragment: " + fragment);
1100        }
1101      }
1102    
1103      // Alternate, stricter implementations of the following validation methods
1104      // are provided, commented out, for possible future use...
1105    
1106      /**
1107       * Returns <code>true</code> if the specified <code>value</code> would be
1108       * valid as the scheme component of a URI; <code>false</code> otherwise.
1109       *
1110       * <p>A valid scheme may be null or contain any characters except for the
1111       * following: <code>: / ? #</code>
1112       */
1113      public static boolean validScheme(String value)
1114      {
1115        return value == null || !contains(value, MAJOR_SEPARATOR_HI, MAJOR_SEPARATOR_LO);  
1116    
1117      // <p>A valid scheme may be null, or consist of a single letter followed
1118      // by any number of letters, numbers, and the following characters:
1119      // <code>+ - .</code>
1120    
1121        //if (value == null) return true;
1122        //return value.length() != 0 &&
1123        //  matches(value.charAt(0), ALPHA_HI, ALPHA_LO) &&
1124        //  validate(value, SCHEME_CHAR_HI, SCHEME_CHAR_LO, false, false);
1125      }
1126    
1127      /**
1128       * Returns <code>true</code> if the specified <code>value</code> would be
1129       * valid as the opaque part component of a URI; <code>false</code>
1130       * otherwise.
1131       *
1132       * <p>A valid opaque part must be non-null, non-empty, and not contain the
1133       * <code>#</code> character.  In addition, its first character must not be
1134       * <code>/</code>
1135       */
1136      public static boolean validOpaquePart(String value)
1137      {
1138        return value != null && value.indexOf(FRAGMENT_SEPARATOR) == -1 &&
1139        value.length() > 0 && value.charAt(0) != SEGMENT_SEPARATOR;
1140    
1141      // <p>A valid opaque part must be non-null and non-empty. It may contain
1142      // any allowed URI characters, but its first character may not be
1143      // <code>/</code> 
1144    
1145        //return value != null && value.length() != 0 &&
1146        //  value.charAt(0) != SEGMENT_SEPARATOR &&
1147        //  validate(value, URIC_HI, URIC_LO, true, true);
1148      }
1149    
1150      /**
1151       * Returns <code>true</code> if the specified <code>value</code> would be
1152       * valid as the authority component of a URI; <code>false</code> otherwise.
1153       *
1154       * <p>A valid authority may be null or contain any characters except for
1155       * the following: <code>/ ? #</code>
1156       */
1157      public static boolean validAuthority(String value)
1158      {
1159        return value == null || !contains(value, SEGMENT_END_HI, SEGMENT_END_LO);
1160    
1161      // A valid authority may be null or contain any allowed URI characters except
1162      // for the following: <code>/ ?</code>
1163    
1164        //return value == null || validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true);
1165      }
1166    
1167      /**
1168       * Returns <code>true</code> if the specified <code>value</code> would be
1169       * valid as the authority component of an <a
1170       * href="#archive_explanation">archive URI</a>; <code>false</code>
1171       * otherwise.
1172       *
1173       * <p>To be valid, the authority, itself, must be a URI with no fragment,
1174       * followed by the character <code>!</code>.
1175       */
1176      public static boolean validArchiveAuthority(String value)
1177      {
1178        if (value != null && value.length() > 0 &&
1179            value.charAt(value.length() - 1) == ARCHIVE_IDENTIFIER)
1180        {
1181          try
1182          {
1183            URI archiveURI = createURI(value.substring(0, value.length() - 1));
1184            return !archiveURI.hasFragment();
1185          }
1186          catch (IllegalArgumentException e)
1187          {
1188            // Ignore the exception and return false.
1189          }
1190        }
1191        return false;
1192      }
1193    
1194      /**
1195       * Tests whether the specified <code>value</code> would be valid as the
1196       * authority component of an <a href="#archive_explanation">archive
1197       * URI</a>. This method has been replaced by {@link #validArchiveAuthority
1198       * validArchiveAuthority} since the same form of URI is now supported
1199       * for schemes other than "jar". This now simply calls that method.
1200       * 
1201       * @deprecated As of EMF 2.0, replaced by {@link #validArchiveAuthority
1202       * validArchiveAuthority}.
1203       */
1204      @Deprecated
1205      public static boolean validJarAuthority(String value)
1206      {
1207        return validArchiveAuthority(value);
1208      }
1209    
1210      /**
1211       * Returns <code>true</code> if the specified <code>value</code> would be
1212       * valid as the device component of a URI; <code>false</code> otherwise.
1213       *
1214       * <p>A valid device may be null or non-empty, containing any characters
1215       * except for the following: <code>/ ? #</code>  In addition, its last
1216       * character must be <code>:</code>
1217       */
1218      public static boolean validDevice(String value)
1219      {    
1220        if (value == null) return true;
1221        int len = value.length();
1222        return len > 0 && value.charAt(len - 1) == DEVICE_IDENTIFIER &&
1223          !contains(value, SEGMENT_END_HI, SEGMENT_END_LO);
1224      }
1225    
1226      /**
1227       * Returns <code>true</code> if the specified <code>value</code> would be
1228       * a valid path segment of a URI; <code>false</code> otherwise.
1229       *
1230       * <p>A valid path segment must be non-null and not contain any of the
1231       * following characters: <code>/ ? #</code>
1232       */
1233      public static boolean validSegment(String value)
1234      {
1235        return value != null && !contains(value, SEGMENT_END_HI, SEGMENT_END_LO);
1236    
1237      // <p>A valid path segment must be non-null and may contain any allowed URI
1238      // characters except for the following: <code>/ ?</code> 
1239    
1240        //return value != null && validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true);
1241      }
1242    
1243      /**
1244       * Returns <code>true</code> if the specified <code>value</code> would be
1245       * a valid path segment array of a URI; <code>false</code> otherwise.
1246       *
1247       * <p>A valid path segment array must be non-null and contain only path
1248       * segments that are valid according to {@link #validSegment validSegment}.
1249       */
1250      public static boolean validSegments(String[] value)
1251      {
1252        if (value == null) return false;
1253        for (int i = 0, len = value.length; i < len; i++)
1254        {
1255          if (!validSegment(value[i])) return false;
1256        }
1257        return true;
1258      }
1259    
1260      // Returns null if the specified value is null or would be a valid path
1261      // segment array of a URI; otherwise, the value of the first invalid
1262      // segment. 
1263      private static String firstInvalidSegment(String[] value)
1264      {
1265        if (value == null) return null;
1266        for (int i = 0, len = value.length; i < len; i++)
1267        {
1268          if (!validSegment(value[i])) return value[i];
1269        }
1270        return null;
1271      }
1272    
1273      /**
1274       * Returns <code>true</code> if the specified <code>value</code> would be
1275       * valid as the query component of a URI; <code>false</code> otherwise.
1276       *
1277       * <p>A valid query may be null or contain any characters except for
1278       * <code>#</code>
1279       */
1280      public static boolean validQuery(String value)
1281      {
1282        return value == null || value.indexOf(FRAGMENT_SEPARATOR) == -1;
1283    
1284      // <p>A valid query may be null or contain any allowed URI characters.
1285    
1286        //return value == null || validate(value, URIC_HI, URIC_LO, true, true);
1287    }
1288    
1289      /**
1290       * Returns <code>true</code> if the specified <code>value</code> would be
1291       * valid as the fragment component of a URI; <code>false</code> otherwise.
1292       *
1293       * <p>A fragment is taken to be unconditionally valid.
1294       */
1295      public static boolean validFragment(String value)
1296      {
1297        return true;
1298    
1299      // <p>A valid fragment may be null or contain any allowed URI characters.
1300    
1301        //return value == null || validate(value, URIC_HI, URIC_LO, true, true);
1302      }
1303    
1304      // Searches the specified string for any characters in the set represented
1305      // by the 128-bit bitmask.  Returns true if any occur, or false otherwise.
1306      private static boolean contains(String s, long highBitmask, long lowBitmask)
1307      {
1308        for (int i = 0, len = s.length(); i < len; i++)
1309        {
1310          if (matches(s.charAt(i), highBitmask, lowBitmask)) return true;
1311        }
1312        return false;
1313      }
1314    
1315      // Tests the non-null string value to see if it contains only ASCII
1316      // characters in the set represented by the specified 128-bit bitmask,
1317      // as well as, optionally, non-ASCII characters 0xA0 and above, and,
1318      // also optionally, escape sequences of % followed by two hex digits.
1319      // This method is used for the new, strict URI validation that is not
1320      // not currently in place.
1321    /*
1322      private static boolean validate(String value, long highBitmask, long lowBitmask,
1323                                         boolean allowNonASCII, boolean allowEscaped)
1324      {
1325        for (int i = 0, length = value.length(); i < length; i++)
1326        { 
1327          char c = value.charAt(i);
1328    
1329          if (matches(c, highBitmask, lowBitmask)) continue;
1330          if (allowNonASCII && c >= 160) continue;
1331          if (allowEscaped && isEscaped(value, i))
1332          {
1333            i += 2;
1334            continue;
1335          }
1336          return false;
1337        }
1338        return true;
1339      }
1340    */
1341    
1342      /**
1343       * Returns <code>true</code> if this is a relative URI, or
1344       * <code>false</code> if it is an absolute URI.
1345       */
1346      public boolean isRelative()
1347      {
1348        return scheme == null;
1349      }
1350    
1351      /**
1352       * Returns <code>true</code> if this a a hierarchical URI, or
1353       * <code>false</code> if it is of the generic form.
1354       */
1355      public boolean isHierarchical()
1356      {
1357        return (hashCode & HIERARICHICAL_FLAG) != 0;
1358      }
1359    
1360      /**
1361       * Returns <code>true</code> if this is a hierarchical URI with an authority
1362       * component; <code>false</code> otherwise. 
1363       */
1364      public boolean hasAuthority()
1365      {
1366        return isHierarchical() && authority != null;
1367      }
1368    
1369      /**
1370       * Returns <code>true</code> if this is a non-hierarchical URI with an
1371       * opaque part component; <code>false</code> otherwise.
1372       */
1373      public boolean hasOpaquePart()
1374      {
1375        // note: hierarchical -> authority != null
1376        return !isHierarchical();
1377      }
1378    
1379      /**
1380       * Returns <code>true</code> if this is a hierarchical URI with a device
1381       * component; <code>false</code> otherwise.
1382       */
1383      public boolean hasDevice()
1384      {
1385        // note: device != null -> hierarchical
1386        return device != null;
1387      }
1388    
1389      /**
1390       * Returns <code>true</code> if this is a hierarchical URI with an
1391       * absolute or relative path; <code>false</code> otherwise.
1392       */
1393      public boolean hasPath()
1394      {
1395        // note: (absolutePath || authority == null) -> hierarchical
1396        // (authority == null && device == null && !absolutePath) -> scheme == null
1397        return hasAbsolutePath() || (authority == null && device == null);
1398      }
1399    
1400      /**
1401       * Returns <code>true</code> if this is a hierarchical URI with an
1402       * absolute path, or <code>false</code> if it is non-hierarchical, has no
1403       * path, or has a relative path.
1404       */
1405      public boolean hasAbsolutePath()
1406      {
1407        // note: absolutePath -> hierarchical
1408        return (hashCode & ABSOLUTE_PATH_FLAG) != 0;
1409      }
1410    
1411      /**
1412       * Returns <code>true</code> if this is a hierarchical URI with a relative
1413       * path, or <code>false</code> if it is non-hierarchical, has no path, or
1414       * has an absolute path.
1415       */
1416      public boolean hasRelativePath()
1417      {
1418        // note: authority == null -> hierarchical
1419        // (authority == null && device == null && !absolutePath) -> scheme == null
1420        return authority == null && device == null && !hasAbsolutePath();
1421      }
1422    
1423      /**
1424       * Returns <code>true</code> if this is a hierarchical URI with an empty
1425       * relative path; <code>false</code> otherwise.  
1426       *
1427       * <p>Note that <code>!hasEmpty()</code> does <em>not</em> imply that this
1428       * URI has any path segments; however, <code>hasRelativePath &&
1429       * !hasEmptyPath()</code> does.
1430       */
1431      public boolean hasEmptyPath()
1432      {
1433        // note: authority == null -> hierarchical
1434        // (authority == null && device == null && !absolutePath) -> scheme == null
1435        return authority == null && device == null && !hasAbsolutePath() &&
1436          segments.length == 0;
1437      }
1438    
1439      /**
1440       * Returns <code>true</code> if this is a hierarchical URI with a query
1441       * component; <code>false</code> otherwise.
1442       */
1443      public boolean hasQuery()
1444      {
1445        // note: query != null -> hierarchical
1446        return query != null;
1447      }
1448    
1449      /**
1450       * Returns <code>true</code> if this URI has a fragment component;
1451       * <code>false</code> otherwise.
1452       */
1453      public boolean hasFragment()
1454      {
1455        return fragment != null;
1456      }
1457    
1458      /**
1459       * Returns <code>true</code> if this is a current document reference; that
1460       * is, if it is a relative hierarchical URI with no authority, device or
1461       * query components, and no path segments; <code>false</code> is returned
1462       * otherwise.
1463       */
1464      public boolean isCurrentDocumentReference()
1465      {
1466        // note: authority == null -> hierarchical
1467        // (authority == null && device == null && !absolutePath) -> scheme == null
1468        return authority == null && device == null && !hasAbsolutePath() &&
1469          segments.length == 0 && query == null;
1470      }
1471    
1472      /**
1473       * Returns <code>true</code> if this is a {@link
1474       * #isCurrentDocumentReference() current document reference} with no
1475       * fragment component; <code>false</code> otherwise.
1476       *
1477       * @see #isCurrentDocumentReference()
1478       */
1479      public boolean isEmpty()
1480      {
1481        // note: authority == null -> hierarchical
1482        // (authority == null && device == null && !absolutePath) -> scheme == null
1483        return authority == null && device == null && !hasAbsolutePath() &&
1484          segments.length == 0 && query == null && fragment == null;
1485      }
1486    
1487      /**
1488       * Returns <code>true</code> if this is a hierarchical URI that may refer
1489       * directly to a locally accessible file.  This is considered to be the
1490       * case for a file-scheme absolute URI, or for a relative URI with no query;
1491       * <code>false</code> is returned otherwise.
1492       */
1493      public boolean isFile()
1494      {
1495        return isHierarchical() &&
1496          ((isRelative() && !hasQuery()) || SCHEME_FILE.equalsIgnoreCase(scheme));
1497      }
1498    
1499      /**
1500       * Returns <code>true</code> if this is a platform URI, that is, an absolute,
1501       * hierarchical URI, with "platform" scheme, no authority, and at least two
1502       * segments; <code>false</code> is returned otherwise.
1503       * @since org.eclipse.emf.common 2.3
1504       */
1505      public boolean isPlatform()
1506      {
1507        return isHierarchical() && !hasAuthority() && segmentCount() >= 2 && 
1508          SCHEME_PLATFORM.equalsIgnoreCase(scheme);
1509      }
1510    
1511      /**
1512       * Returns <code>true</code> if this is a platform resource URI, that is,
1513       * a {@link #isPlatform platform URI} whose first segment is "resource";
1514       * <code>false</code> is returned otherwise.
1515       * @see #isPlatform
1516       * @since org.eclipse.emf.common 2.3
1517       */
1518      public boolean isPlatformResource()
1519      {
1520        return isPlatform() && "resource".equals(segments[0]);
1521      }
1522    
1523      /**
1524       * Returns <code>true</code> if this is a platform plug-in URI, that is,
1525       * a {@link #isPlatform platform URI} whose first segment is "plugin";
1526       * <code>false</code> is returned otherwise.
1527       * @see #isPlatform
1528       * @since org.eclipse.emf.common 2.3
1529       */
1530      public boolean isPlatformPlugin()
1531      {
1532        return isPlatform() && "plugin".equals(segments[0]);
1533      }
1534    
1535      /**
1536       * Returns <code>true</code> if this is an archive URI.  If so, it is also
1537       * hierarchical, with an authority (consisting of an absolute URI followed
1538       * by "!"), no device, and an absolute path.
1539       */
1540      public boolean isArchive()
1541      {
1542        return isArchiveScheme(scheme);
1543      }
1544    
1545      /**
1546       * Returns <code>true</code> if the specified <code>value</code> would be
1547       * valid as the scheme of an <a
1548       * href="#archive_explanation">archive URI</a>; <code>false</code>
1549       * otherwise.
1550       */
1551      public static boolean isArchiveScheme(String value)
1552      {
1553        // Returns true if the given value is an archive scheme, as defined by
1554        // the org.eclipse.emf.common.util.URI.archiveSchemes system property.
1555        // By default, "jar", "zip", and "archive" are considered archives.
1556        return value != null && archiveSchemes.contains(value.toLowerCase());
1557      }
1558      
1559      /**
1560       * Returns the hash code.
1561       */
1562      @Override
1563      public int hashCode()
1564      {
1565        return hashCode;
1566      }
1567    
1568      /**
1569       * Returns <code>true</code> if <code>object</code> is an instance of
1570       * <code>URI</code> equal to this one; <code>false</code> otherwise.
1571       *
1572       * <p>Equality is determined strictly by comparing components, not by
1573       * attempting to interpret what resource is being identified.  The
1574       * comparison of schemes is case-insensitive.
1575       */
1576      @Override
1577      public boolean equals(Object object)
1578      {
1579        if (this == object) return true;
1580        if (!(object instanceof URI)) return false;
1581        URI uri = (URI) object;
1582    
1583        return hashCode == uri.hashCode() &&
1584          equals(scheme, uri.scheme(), true) &&
1585          equals(authority, isHierarchical() ? uri.authority() : uri.opaquePart()) &&
1586          equals(device, uri.device()) &&
1587          equals(query, uri.query()) && 
1588          equals(fragment, uri.fragment()) &&
1589          segmentsEqual(uri);
1590      }
1591    
1592      // Tests whether this URI's path segment array is equal to that of the
1593      // given uri.
1594      private boolean segmentsEqual(URI uri)
1595      {
1596        if (segments.length != uri.segmentCount()) return false;
1597        for (int i = 0, len = segments.length; i < len; i++)
1598        {
1599          if (!segments[i].equals(uri.segment(i))) return false;
1600        }
1601        return true;
1602      }
1603    
1604      // Tests two objects for equality, tolerating nulls; null is considered
1605      // to be a valid value that is only equal to itself.
1606      private static boolean equals(Object o1, Object o2)
1607      {
1608        return o1 == null ? o2 == null : o1.equals(o2);
1609      }
1610    
1611      // Tests two strings for equality, tolerating nulls and optionally
1612      // ignoring case.
1613      private static boolean equals(String s1, String s2, boolean ignoreCase)
1614      {
1615        return s1 == null ? s2 == null :
1616          ignoreCase ? s1.equalsIgnoreCase(s2) : s1.equals(s2);
1617      }
1618    
1619      /**
1620       * If this is an absolute URI, returns the scheme component;
1621       * <code>null</code> otherwise.
1622       */
1623      public String scheme()
1624      {
1625        return scheme;
1626      }
1627    
1628      /**
1629       * If this is a non-hierarchical URI, returns the opaque part component;
1630       * <code>null</code> otherwise.
1631       */
1632      public String opaquePart()
1633      {
1634        return isHierarchical() ? null : authority;
1635      }
1636    
1637      /**
1638       * If this is a hierarchical URI with an authority component, returns it;
1639       * <code>null</code> otherwise.
1640       */
1641      public String authority()
1642      {
1643        return isHierarchical() ? authority : null;
1644      }
1645    
1646      /**
1647       * If this is a hierarchical URI with an authority component that has a
1648       * user info portion, returns it; <code>null</code> otherwise.
1649       */
1650      public String userInfo()
1651      { 
1652        if (!hasAuthority()) return null;
1653       
1654        int i = authority.indexOf(USER_INFO_SEPARATOR);
1655        return i < 0 ? null : authority.substring(0, i);
1656      }
1657    
1658      /**
1659       * If this is a hierarchical URI with an authority component that has a
1660       * host portion, returns it; <code>null</code> otherwise.
1661       */
1662      public String host()
1663      {
1664        if (!hasAuthority()) return null;
1665        
1666        int i = authority.indexOf(USER_INFO_SEPARATOR);
1667        int j = authority.indexOf(PORT_SEPARATOR);
1668        return j < 0 ? authority.substring(i + 1) : authority.substring(i + 1, j);
1669      }
1670    
1671      /**
1672       * If this is a hierarchical URI with an authority component that has a
1673       * port portion, returns it; <code>null</code> otherwise.
1674       */
1675      public String port()
1676      {
1677        if (!hasAuthority()) return null;
1678    
1679        int i = authority.indexOf(PORT_SEPARATOR);
1680        return i < 0 ? null : authority.substring(i + 1);
1681      }
1682    
1683      /**
1684       * If this is a hierarchical URI with a device component, returns it;
1685       * <code>null</code> otherwise.
1686       */
1687      public String device()
1688      {
1689        return device;
1690      }
1691    
1692      /**
1693       * If this is a hierarchical URI with a path, returns an array containing
1694       * the segments of the path; an empty array otherwise.  The leading
1695       * separator in an absolute path is not represented in this array, but a
1696       * trailing separator is represented by an empty-string segment as the
1697       * final element.
1698       */
1699      public String[] segments()
1700      {
1701        return segments.clone();
1702      }
1703    
1704      /**
1705       * Returns an unmodifiable list containing the same segments as the array
1706       * returned by {@link #segments segments}.
1707       */
1708      public List<String> segmentsList()
1709      {
1710        return Collections.unmodifiableList(Arrays.asList(segments));
1711      }
1712    
1713      /**
1714       * Returns the number of elements in the segment array that would be
1715       * returned by {@link #segments segments}.
1716       */
1717      public int segmentCount()
1718      {
1719        return segments.length;
1720      }
1721    
1722      /**
1723       * Provides fast, indexed access to individual segments in the path
1724       * segment array.
1725       *
1726       * @exception java.lang.IndexOutOfBoundsException if <code>i < 0</code> or
1727       * <code>i >= segmentCount()</code>.
1728       */
1729      public String segment(int i)
1730      {
1731        return segments[i];
1732      }
1733    
1734      /**
1735       * Returns the last segment in the segment array, or <code>null</code>.
1736       */
1737      public String lastSegment()
1738      {
1739        int len = segments.length;
1740        if (len == 0) return null;
1741        return segments[len - 1];
1742      }
1743    
1744      /**
1745       * If this is a hierarchical URI with a path, returns a string
1746       * representation of the path; <code>null</code> otherwise.  The path
1747       * consists of a leading segment separator character (a slash), if the
1748       * path is absolute, followed by the slash-separated path segments.  If
1749       * this URI has a separate <a href="#device_explanation">device
1750       * component</a>, it is <em>not</em> included in the path.
1751       */
1752      public String path()
1753      {
1754        if (!hasPath()) return null;
1755    
1756        StringBuffer result = new StringBuffer();
1757        if (hasAbsolutePath()) result.append(SEGMENT_SEPARATOR);
1758    
1759        for (int i = 0, len = segments.length; i < len; i++)
1760        {
1761          if (i != 0) result.append(SEGMENT_SEPARATOR);
1762          result.append(segments[i]);
1763        }
1764        return result.toString();
1765      }
1766    
1767      /**
1768       * If this is a hierarchical URI with a path, returns a string
1769       * representation of the path, including the authority and the 
1770       * <a href="#device_explanation">device component</a>; 
1771       * <code>null</code> otherwise.  
1772       *
1773       * <p>If there is no authority, the format of this string is:
1774       * <pre>
1775       *   device/pathSegment1/pathSegment2...</pre>
1776       *
1777       * <p>If there is an authority, it is:
1778       * <pre>
1779       *   //authority/device/pathSegment1/pathSegment2...</pre>
1780       *
1781       * <p>For an <a href="#archive_explanation">archive URI</a>, it's just:
1782       * <pre>
1783       *   authority/pathSegment1/pathSegment2...</pre>
1784       */
1785      public String devicePath()
1786      {
1787        if (!hasPath()) return null;
1788    
1789        StringBuffer result = new StringBuffer();
1790    
1791        if (hasAuthority())
1792        {
1793          if (!isArchive()) result.append(AUTHORITY_SEPARATOR);
1794          result.append(authority);
1795    
1796          if (hasDevice()) result.append(SEGMENT_SEPARATOR);
1797        }
1798    
1799        if (hasDevice()) result.append(device);
1800        if (hasAbsolutePath()) result.append(SEGMENT_SEPARATOR);
1801    
1802        for (int i = 0, len = segments.length; i < len; i++)
1803        {
1804          if (i != 0) result.append(SEGMENT_SEPARATOR);
1805          result.append(segments[i]);
1806        }
1807        return result.toString();
1808      }
1809    
1810      /**
1811       * If this is a hierarchical URI with a query component, returns it;
1812       * <code>null</code> otherwise.
1813       */
1814      public String query()
1815      {
1816        return query;
1817      }
1818    
1819    
1820      /**
1821       * Returns the URI formed from this URI and the given query.
1822       *
1823       * @exception java.lang.IllegalArgumentException if
1824       * <code>query</code> is not a valid query (portion) according
1825       * to {@link #validQuery validQuery}.
1826       */
1827      public URI appendQuery(String query)
1828      {
1829        if (!validQuery(query))
1830        {
1831          throw new IllegalArgumentException(
1832            "invalid query portion: " + query);
1833        }
1834        return new URI(isHierarchical(), scheme, authority, device, hasAbsolutePath(), segments, query, fragment); 
1835      }
1836    
1837      /**
1838       * If this URI has a non-null {@link #query query}, returns the URI
1839       * formed by removing it; this URI unchanged, otherwise.
1840       */
1841      public URI trimQuery()
1842      {
1843        if (query == null)
1844        {
1845          return this;
1846        }
1847        else
1848        {
1849          return new URI(isHierarchical(), scheme, authority, device, hasAbsolutePath(), segments, null, fragment); 
1850        }
1851      }
1852    
1853      /**
1854       * If this URI has a fragment component, returns it; <code>null</code>
1855       * otherwise.
1856       */
1857      public String fragment()
1858      {
1859        return fragment;
1860      }
1861    
1862      /**
1863       * Returns the URI formed from this URI and the given fragment.
1864       *
1865       * @exception java.lang.IllegalArgumentException if
1866       * <code>fragment</code> is not a valid fragment (portion) according
1867       * to {@link #validFragment validFragment}.
1868       */
1869      public URI appendFragment(String fragment)
1870      {
1871        if (!validFragment(fragment))
1872        {
1873          throw new IllegalArgumentException(
1874            "invalid fragment portion: " + fragment);
1875        }
1876        URI result = new URI(isHierarchical(), scheme, authority, device, hasAbsolutePath(), segments, query, fragment); 
1877    
1878        if (!hasFragment())
1879        {
1880          result.cachedTrimFragment = this;
1881        }
1882        return result;
1883      }
1884    
1885      /**
1886       * If this URI has a non-null {@link #fragment fragment}, returns the URI
1887       * formed by removing it; this URI unchanged, otherwise.
1888       */
1889      public URI trimFragment()
1890      {
1891        if (fragment == null)
1892        {
1893          return this;
1894        }
1895        else if (cachedTrimFragment == null)
1896        {
1897          cachedTrimFragment = new URI(isHierarchical(), scheme, authority, device, hasAbsolutePath(), segments, query, null); 
1898        }
1899    
1900        return cachedTrimFragment;
1901      }
1902    
1903      /**
1904       * Resolves this URI reference against a <code>base</code> absolute
1905       * hierarchical URI, returning the resulting absolute URI.  If already
1906       * absolute, the URI itself is returned.  URI resolution is described in
1907       * detail in section 5.2 of <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC
1908       * 2396</a>, "Resolving Relative References to Absolute Form."
1909       *
1910       * <p>During resolution, empty segments, self references ("."), and parent
1911       * references ("..") are interpreted, so that they can be removed from the
1912       * path.  Step 6(g) gives a choice of how to handle the case where parent
1913       * references point to a path above the root: the offending segments can
1914       * be preserved or discarded.  This method preserves them.  To have them
1915       * discarded, please use the two-parameter form of {@link
1916       * #resolve(URI, boolean) resolve}.
1917       *
1918       * @exception java.lang.IllegalArgumentException if <code>base</code> is
1919       * non-hierarchical or is relative.
1920       */
1921      public URI resolve(URI base)
1922      {
1923        return resolve(base, true);
1924      }
1925    
1926      /**
1927       * Resolves this URI reference against a <code>base</code> absolute
1928       * hierarchical URI, returning the resulting absolute URI.  If already
1929       * absolute, the URI itself is returned.  URI resolution is described in
1930       * detail in section 5.2 of <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC
1931       * 2396</a>, "Resolving Relative References to Absolute Form."
1932       *
1933       * <p>During resolution, empty segments, self references ("."), and parent
1934       * references ("..") are interpreted, so that they can be removed from the
1935       * path.  Step 6(g) gives a choice of how to handle the case where parent
1936       * references point to a path above the root: the offending segments can
1937       * be preserved or discarded.  This method can do either.
1938       *
1939       * @param preserveRootParents <code>true</code> if segments referring to the
1940       * parent of the root path are to be preserved; <code>false</code> if they
1941       * are to be discarded.
1942       *
1943       * @exception java.lang.IllegalArgumentException if <code>base</code> is
1944       * non-hierarchical or is relative.
1945       */
1946      public URI resolve(URI base, boolean preserveRootParents)
1947      {
1948        if (!base.isHierarchical() || base.isRelative())
1949        {
1950          throw new IllegalArgumentException(
1951            "resolve against non-hierarchical or relative base");
1952        }
1953    
1954        // an absolute URI needs no resolving
1955        if (!isRelative()) return this;
1956    
1957        // note: isRelative() -> hierarchical
1958    
1959        String newAuthority = authority;
1960        String newDevice = device;
1961        boolean newAbsolutePath = hasAbsolutePath();
1962        String[] newSegments = segments;
1963        String newQuery = query;
1964        // note: it's okay for two URIs to share a segments array, since
1965        // neither will ever modify it
1966        
1967        if (authority == null)
1968        {
1969          // no authority: use base's
1970          newAuthority = base.authority();
1971    
1972          if (device == null)
1973          {
1974            // no device: use base's
1975            newDevice = base.device();
1976    
1977            if (hasEmptyPath() && query == null)
1978            {
1979              // current document reference: use base path and query
1980              newAbsolutePath = base.hasAbsolutePath();
1981              newSegments = base.segments();
1982              newQuery = base.query();
1983            }
1984            else if (hasRelativePath())
1985            {
1986              // relative path: merge with base and keep query (note: if the
1987              // base has no path and this a non-empty relative path, there is
1988              // an implied root in the resulting path) 
1989              newAbsolutePath = base.hasAbsolutePath() || !hasEmptyPath();
1990              newSegments = newAbsolutePath ? mergePath(base, preserveRootParents)
1991                : NO_SEGMENTS;
1992            }
1993            // else absolute path: keep it and query
1994          }
1995          // else keep device, path, and query
1996        }
1997        // else keep authority, device, path, and query
1998        
1999        // always keep fragment, even if null, and use scheme from base;
2000        // no validation needed since all components are from existing URIs
2001        return new URI(true, base.scheme(), newAuthority, newDevice,
2002                       newAbsolutePath, newSegments, newQuery, fragment);
2003      }
2004    
2005      // Merges this URI's relative path with the base non-relative path.  If
2006      // base has no path, treat it as the root absolute path, unless this has
2007      // no path either.
2008      private String[] mergePath(URI base, boolean preserveRootParents)
2009      {
2010        if (base.hasRelativePath())
2011        {
2012          throw new IllegalArgumentException("merge against relative path");
2013        }
2014        if (!hasRelativePath())
2015        {
2016          throw new IllegalStateException("merge non-relative path");
2017        }
2018    
2019        int baseSegmentCount = base.segmentCount();
2020        int segmentCount = segments.length;
2021        String[] stack = new String[baseSegmentCount + segmentCount];
2022        int sp = 0;
2023    
2024        // use a stack to accumulate segments of base, except for the last
2025        // (i.e. skip trailing separator and anything following it), and of
2026        // relative path
2027        for (int i = 0; i < baseSegmentCount - 1; i++)
2028        {
2029          sp = accumulate(stack, sp, base.segment(i), preserveRootParents);
2030        }
2031    
2032        for (int i = 0; i < segmentCount; i++)
2033        {
2034          sp = accumulate(stack, sp, segments[i], preserveRootParents);
2035        }
2036    
2037        // if the relative path is empty or ends in an empty segment, a parent 
2038        // reference, or a self reference, add a trailing separator to a
2039        // non-empty path
2040        if (sp > 0 &&  (segmentCount == 0 ||
2041                        SEGMENT_EMPTY.equals(segments[segmentCount - 1]) ||
2042                        SEGMENT_PARENT.equals(segments[segmentCount - 1]) ||
2043                        SEGMENT_SELF.equals(segments[segmentCount - 1])))
2044        {
2045          stack[sp++] = SEGMENT_EMPTY;
2046        }
2047    
2048        // return a correctly sized result
2049        String[] result = new String[sp];
2050        System.arraycopy(stack, 0, result, 0, sp);
2051        return result;
2052      }
2053    
2054      // Adds a segment to a stack, skipping empty segments and self references,
2055      // and interpreting parent references.
2056      private static int accumulate(String[] stack, int sp, String segment,
2057                                    boolean preserveRootParents)
2058      {
2059        if (SEGMENT_PARENT.equals(segment))
2060        {
2061          if (sp == 0)
2062          {
2063            // special care must be taken for a root's parent reference: it is
2064            // either ignored or the symbolic reference itself is pushed
2065            if (preserveRootParents) stack[sp++] = segment;
2066          }
2067          else
2068          {
2069            // unless we're already accumulating root parent references,
2070            // parent references simply pop the last segment descended
2071            if (SEGMENT_PARENT.equals(stack[sp - 1])) stack[sp++] = segment;
2072            else sp--;
2073          }
2074        }
2075        else if (!SEGMENT_EMPTY.equals(segment) && !SEGMENT_SELF.equals(segment))
2076        {
2077          // skip empty segments and self references; push everything else
2078          stack[sp++] = segment;
2079        }
2080        return sp;
2081      }
2082    
2083      /**
2084       * Finds the shortest relative or, if necessary, the absolute URI that,
2085       * when resolved against the given <code>base</code> absolute hierarchical
2086       * URI using {@link #resolve(URI) resolve}, will yield this absolute URI.  
2087       * If <code>base</code> is non-hierarchical or is relative,
2088       * or <code>this</code> is non-hierarchical or is relative,
2089       * <code>this</code> will be returned.
2090       */
2091      public URI deresolve(URI base)
2092      {
2093        return deresolve(base, true, false, true);
2094      }
2095    
2096      /**
2097       * Finds an absolute URI that, when resolved against the given
2098       * <code>base</code> absolute hierarchical URI using {@link
2099       * #resolve(URI, boolean) resolve}, will yield this absolute URI.
2100       * If <code>base</code> is non-hierarchical or is relative,
2101       * or <code>this</code> is non-hierarchical or is relative,
2102       * <code>this</code> will be returned.
2103       *
2104       * @param preserveRootParents the boolean argument to <code>resolve(URI,
2105       * boolean)</code> for which the returned URI should resolve to this URI.
2106       * @param anyRelPath if <code>true</code>, the returned URI's path (if
2107       * any) will be relative, if possible.  If <code>false</code>, the form of
2108       * the result's path will depend upon the next parameter.
2109       * @param shorterRelPath if <code>anyRelPath</code> is <code>false</code>
2110       * and this parameter is <code>true</code>, the returned URI's path (if
2111       * any) will be relative, if one can be found that is no longer (by number
2112       * of segments) than the absolute path.  If both <code>anyRelPath</code>
2113       * and this parameter are <code>false</code>, it will be absolute.
2114       */
2115      public URI deresolve(URI base, boolean preserveRootParents,
2116                           boolean anyRelPath, boolean shorterRelPath)
2117      {
2118        if (!base.isHierarchical() || base.isRelative()) return this;
2119        
2120        if (isRelative()) return this;
2121    
2122        // note: these assertions imply that neither this nor the base URI has a
2123        // relative path; thus, both have either an absolute path or no path
2124        
2125        // different scheme: need complete, absolute URI
2126        if (!scheme.equalsIgnoreCase(base.scheme())) return this;
2127    
2128        // since base must be hierarchical, and since a non-hierarchical URI
2129        // must have both scheme and opaque part, the complete absolute URI is
2130        // needed to resolve to a non-hierarchical URI
2131        if (!isHierarchical()) return this;
2132    
2133        String newAuthority = authority;
2134        String newDevice = device;
2135        boolean newAbsolutePath = hasAbsolutePath();
2136        String[] newSegments = segments;
2137        String newQuery = query;
2138    
2139        if (equals(authority, base.authority()) &&
2140            (hasDevice() || hasPath() || (!base.hasDevice() && !base.hasPath())))
2141        {
2142          // matching authorities and no device or path removal
2143          newAuthority = null;
2144    
2145          if (equals(device, base.device()) && (hasPath() || !base.hasPath()))
2146          {
2147            // matching devices and no path removal
2148            newDevice = null;
2149    
2150            // exception if (!hasPath() && base.hasPath())
2151    
2152            if (!anyRelPath && !shorterRelPath)
2153            {
2154              // user rejects a relative path: keep absolute or no path
2155            }
2156            else if (hasPath() == base.hasPath() && segmentsEqual(base) &&
2157                     equals(query, base.query()))
2158            {
2159              // current document reference: keep no path or query
2160              newAbsolutePath = false;
2161              newSegments = NO_SEGMENTS;
2162              newQuery = null;
2163            }
2164            else if (!hasPath() && !base.hasPath())
2165            {
2166              // no paths: keep query only
2167              newAbsolutePath = false;
2168              newSegments = NO_SEGMENTS;
2169            }
2170            // exception if (!hasAbsolutePath())
2171            else if (hasCollapsableSegments(preserveRootParents))
2172            {
2173              // path form demands an absolute path: keep it and query
2174            }
2175            else
2176            {
2177              // keep query and select relative or absolute path based on length
2178              String[] rel = findRelativePath(base, preserveRootParents);
2179              if (anyRelPath || segments.length > rel.length)
2180              {
2181                // user demands a relative path or the absolute path is longer
2182                newAbsolutePath = false;
2183                newSegments = rel;
2184              }
2185              // else keep shorter absolute path
2186            }
2187          }
2188          // else keep device, path, and query
2189        }
2190        // else keep authority, device, path, and query
2191    
2192        // always include fragment, even if null;
2193        // no validation needed since all components are from existing URIs
2194        return new URI(true, null, newAuthority, newDevice, newAbsolutePath,
2195                       newSegments, newQuery, fragment);
2196      }
2197    
2198      // Returns true if the non-relative path includes segments that would be
2199      // collapsed when resolving; false otherwise.  If preserveRootParents is
2200      // true, collapsible segments include any empty segments, except for the
2201      // last segment, as well as and parent and self references.  If
2202      // preserveRootsParents is false, parent references are not collapsible if
2203      // they are the first segment or preceded only by other parent
2204      // references.
2205      private boolean hasCollapsableSegments(boolean preserveRootParents)
2206      {
2207        if (hasRelativePath())
2208        {
2209          throw new IllegalStateException("test collapsability of relative path");
2210        }
2211    
2212        for (int i = 0, len = segments.length; i < len; i++)
2213        {
2214          String segment = segments[i];
2215          if ((i < len - 1 && SEGMENT_EMPTY.equals(segment)) ||
2216              SEGMENT_SELF.equals(segment) ||
2217              SEGMENT_PARENT.equals(segment) && (
2218                !preserveRootParents || (
2219                  i != 0 && !SEGMENT_PARENT.equals(segments[i - 1]))))
2220          {
2221            return true;
2222          }
2223        }
2224        return false;
2225      }
2226    
2227      // Returns the shortest relative path between the the non-relative path of
2228      // the given base and this absolute path.  If the base has no path, it is
2229      // treated as the root absolute path.
2230      private String[] findRelativePath(URI base, boolean preserveRootParents)
2231      {
2232        if (base.hasRelativePath())
2233        {
2234          throw new IllegalArgumentException(
2235            "find relative path against base with relative path");
2236        }
2237        if (!hasAbsolutePath())
2238        {
2239          throw new IllegalArgumentException(
2240            "find relative path of non-absolute path");
2241        }
2242    
2243        // treat an empty base path as the root absolute path
2244        String[] startPath = base.collapseSegments(preserveRootParents);
2245        String[] endPath = segments;
2246    
2247        // drop last segment from base, as in resolving
2248        int startCount = startPath.length > 0 ? startPath.length - 1 : 0;
2249        int endCount = endPath.length;
2250    
2251        // index of first segment that is different between endPath and startPath
2252        int diff = 0;
2253    
2254        // if endPath is shorter than startPath, the last segment of endPath may
2255        // not be compared: because startPath has been collapsed and had its
2256        // last segment removed, all preceding segments can be considered non-
2257        // empty and followed by a separator, while the last segment of endPath
2258        // will either be non-empty and not followed by a separator, or just empty
2259        for (int count = startCount < endCount ? startCount : endCount - 1;
2260             diff < count && startPath[diff].equals(endPath[diff]); diff++)
2261        {
2262          // Empty statement.
2263        }
2264    
2265        int upCount = startCount - diff;
2266        int downCount = endCount - diff;
2267    
2268        // a single separator, possibly preceded by some parent reference
2269        // segments, is redundant
2270        if (downCount == 1 && SEGMENT_EMPTY.equals(endPath[endCount - 1]))
2271        {
2272          downCount = 0;
2273        }
2274    
2275        // an empty path needs to be replaced by a single "." if there is no
2276        // query, to distinguish it from a current document reference
2277        if (upCount + downCount == 0)
2278        {
2279          if (query == null) return new String[] { SEGMENT_SELF };
2280          return NO_SEGMENTS;
2281        }
2282    
2283        // return a correctly sized result
2284        String[] result = new String[upCount + downCount];
2285        Arrays.fill(result, 0, upCount, SEGMENT_PARENT);
2286        System.arraycopy(endPath, diff, result, upCount, downCount);
2287        return result;
2288      }
2289    
2290      // Collapses non-ending empty segments, parent references, and self
2291      // references in a non-relative path, returning the same path that would
2292      // be produced from the base hierarchical URI as part of a resolve.
2293      String[] collapseSegments(boolean preserveRootParents)
2294      {
2295        if (hasRelativePath())
2296        {
2297          throw new IllegalStateException("collapse relative path");
2298        }
2299    
2300        if (!hasCollapsableSegments(preserveRootParents)) return segments();
2301    
2302        // use a stack to accumulate segments
2303        int segmentCount = segments.length;
2304        String[] stack = new String[segmentCount];
2305        int sp = 0;
2306    
2307        for (int i = 0; i < segmentCount; i++)
2308        {
2309          sp = accumulate(stack, sp, segments[i], preserveRootParents);
2310        }
2311    
2312        // if the path is non-empty and originally ended in an empty segment, a
2313        // parent reference, or a self reference, add a trailing separator
2314        if (sp > 0 && (SEGMENT_EMPTY.equals(segments[segmentCount - 1]) ||
2315                       SEGMENT_PARENT.equals(segments[segmentCount - 1]) ||
2316                       SEGMENT_SELF.equals(segments[segmentCount - 1])))
2317        {                   
2318          stack[sp++] = SEGMENT_EMPTY;
2319        }
2320    
2321        // return a correctly sized result
2322        String[] result = new String[sp];
2323        System.arraycopy(stack, 0, result, 0, sp);
2324        return result;
2325      }
2326    
2327      /**
2328       * Returns the string representation of this URI.  For a generic,
2329       * non-hierarchical URI, this looks like:
2330       * <pre>
2331       *   scheme:opaquePart#fragment</pre>
2332       * 
2333       * <p>For a hierarchical URI, it looks like:
2334       * <pre>
2335       *   scheme://authority/device/pathSegment1/pathSegment2...?query#fragment</pre>
2336       *
2337       * <p>For an <a href="#archive_explanation">archive URI</a>, it's just:
2338       * <pre>
2339       *   scheme:authority/pathSegment1/pathSegment2...?query#fragment</pre>
2340       * <p>Of course, absent components and their separators will be omitted.
2341       */
2342      @Override
2343      public String toString()
2344      {
2345        if (cachedToString == null)
2346        {
2347          StringBuffer result = new StringBuffer();
2348          if (!isRelative())
2349          {
2350            result.append(scheme);
2351            result.append(SCHEME_SEPARATOR);
2352          }
2353    
2354          if (isHierarchical())
2355          {
2356            if (hasAuthority())
2357            {
2358              if (!isArchive()) result.append(AUTHORITY_SEPARATOR);
2359              result.append(authority);
2360            }
2361    
2362            if (hasDevice())
2363            {
2364              result.append(SEGMENT_SEPARATOR);
2365              result.append(device);
2366            }
2367    
2368            if (hasAbsolutePath()) result.append(SEGMENT_SEPARATOR);
2369    
2370            for (int i = 0, len = segments.length; i < len; i++)
2371            {
2372              if (i != 0) result.append(SEGMENT_SEPARATOR);
2373              result.append(segments[i]);
2374            }
2375    
2376            if (hasQuery())
2377            {
2378              result.append(QUERY_SEPARATOR);
2379              result.append(query);
2380            }
2381          }
2382          else
2383          {
2384            result.append(authority);
2385          }
2386    
2387          if (hasFragment())
2388          {
2389            result.append(FRAGMENT_SEPARATOR);
2390            result.append(fragment);
2391          }
2392          cachedToString = result.toString();
2393        }
2394        return cachedToString;
2395      }
2396    
2397      // Returns a string representation of this URI for debugging, explicitly
2398      // showing each of the components.
2399      String toString(boolean includeSimpleForm)
2400      {
2401        StringBuffer result = new StringBuffer();
2402        if (includeSimpleForm) result.append(toString());
2403        result.append("\n hierarchical: ");
2404        result.append(isHierarchical());
2405        result.append("\n       scheme: ");
2406        result.append(scheme);
2407        result.append("\n    authority: ");
2408        result.append(authority);
2409        result.append("\n       device: ");
2410        result.append(device);
2411        result.append("\n absolutePath: ");
2412        result.append(hasAbsolutePath());
2413        result.append("\n     segments: ");
2414        if (segments.length == 0) result.append("<empty>");
2415        for (int i = 0, len = segments.length; i < len; i++)
2416        {
2417          if (i > 0) result.append("\n               ");
2418          result.append(segments[i]);
2419        }
2420        result.append("\n        query: ");
2421        result.append(query);
2422        result.append("\n     fragment: ");
2423        result.append(fragment);
2424        return result.toString();
2425      }
2426    
2427      /**
2428       * If this URI may refer directly to a locally accessible file, as
2429       * determined by {@link #isFile isFile}, {@link #decode decodes} and formats  
2430       * the URI as a pathname to that file; returns null otherwise.
2431       *
2432       * <p>If there is no authority, the format of this string is:
2433       * <pre>
2434       *   device/pathSegment1/pathSegment2...</pre>
2435       *
2436       * <p>If there is an authority, it is:
2437       * <pre>
2438       *   //authority/device/pathSegment1/pathSegment2...</pre>
2439       * 
2440       * <p>However, the character used as a separator is system-dependent and
2441       * obtained from {@link java.io.File#separatorChar}.
2442       */
2443      public String toFileString()
2444      {
2445        if (!isFile()) return null;
2446    
2447        StringBuffer result = new StringBuffer();
2448        char separator = File.separatorChar;
2449    
2450        if (hasAuthority())
2451        {
2452          result.append(separator);
2453          result.append(separator);
2454          result.append(authority);
2455    
2456          if (hasDevice()) result.append(separator);
2457        }
2458    
2459        if (hasDevice()) result.append(device);
2460        if (hasAbsolutePath()) result.append(separator);
2461    
2462        for (int i = 0, len = segments.length; i < len; i++)
2463        {
2464          if (i != 0) result.append(separator);
2465          result.append(segments[i]);
2466        }
2467    
2468        return decode(result.toString());
2469      }
2470      
2471      /**
2472       * If this is a platform URI, as determined by {@link #isPlatform}, returns
2473       * the workspace-relative or plug-in-based path to the resource, optionally
2474       * {@link #decode decoding} the segments in the process.
2475       * @see #createPlatformResourceURI(String, boolean)
2476       * @see #createPlatformPluginURI
2477       * @since org.eclipse.emf.common 2.3
2478       */
2479      public String toPlatformString(boolean decode)
2480      {
2481        if (isPlatform())
2482        {
2483          StringBuffer result = new StringBuffer();
2484          for (int i = 1, len = segments.length; i < len; i++)
2485          {
2486            result.append('/').append(decode ? URI.decode(segments[i]) : segments[i]);
2487          }
2488          return result.toString();
2489        }
2490        return null;
2491      }
2492    
2493      /**
2494       * Returns the URI formed by appending the specified segment on to the end
2495       * of the path of this URI, if hierarchical; this URI unchanged,
2496       * otherwise.  If this URI has an authority and/or device, but no path,
2497       * the segment becomes the first under the root in an absolute path.
2498       *
2499       * @exception java.lang.IllegalArgumentException if <code>segment</code>
2500       * is not a valid segment according to {@link #validSegment}.
2501       */
2502      public URI appendSegment(String segment)
2503      {
2504        if (!validSegment(segment))
2505        {
2506          throw new IllegalArgumentException("invalid segment: " + segment);
2507        }
2508    
2509        if (!isHierarchical()) return this;
2510    
2511        // absolute path or no path -> absolute path
2512        boolean newAbsolutePath = !hasRelativePath();
2513    
2514        int len = segments.length;
2515        String[] newSegments = new String[len + 1];
2516        System.arraycopy(segments, 0, newSegments, 0, len);
2517        newSegments[len] = segment;
2518    
2519        return new URI(true, scheme, authority, device, newAbsolutePath,
2520                       newSegments, query, fragment);
2521      }
2522    
2523      /**
2524       * Returns the URI formed by appending the specified segments on to the
2525       * end of the path of this URI, if hierarchical; this URI unchanged,
2526       * otherwise.  If this URI has an authority and/or device, but no path,
2527       * the segments are made to form an absolute path.
2528       *
2529       * @param segments an array of non-null strings, each representing one
2530       * segment of the path.  If desired, a trailing separator should be
2531       * represented by an empty-string segment as the last element of the
2532       * array.
2533       *
2534       * @exception java.lang.IllegalArgumentException if <code>segments</code>
2535       * is not a valid segment array according to {@link #validSegments}.
2536       */
2537      public URI appendSegments(String[] segments)
2538      {
2539        if (!validSegments(segments))
2540        {
2541          String s = segments == null ? "invalid segments: null" :
2542            "invalid segment: " + firstInvalidSegment(segments);
2543          throw new IllegalArgumentException(s);
2544        }
2545    
2546        if (!isHierarchical()) return this;
2547    
2548        // absolute path or no path -> absolute path
2549        boolean newAbsolutePath = !hasRelativePath(); 
2550    
2551        int len = this.segments.length;
2552        int segmentsCount = segments.length;
2553        String[] newSegments = new String[len + segmentsCount];
2554        System.arraycopy(this.segments, 0, newSegments, 0, len);
2555        System.arraycopy(segments, 0, newSegments, len, segmentsCount);
2556        
2557        return new URI(true, scheme, authority, device, newAbsolutePath,
2558                       newSegments, query, fragment);
2559      }
2560    
2561      /**
2562       * Returns the URI formed by trimming the specified number of segments
2563       * (including empty segments, such as one representing a trailing
2564       * separator) from the end of the path of this URI, if hierarchical;
2565       * otherwise, this URI is returned unchanged.
2566       *
2567       * <p>Note that if all segments are trimmed from an absolute path, the
2568       * root absolute path remains.
2569       * 
2570       * @param i the number of segments to be trimmed in the returned URI.  If
2571       * less than 1, this URI is returned unchanged; if equal to or greater
2572       * than the number of segments in this URI's path, all segments are
2573       * trimmed.  
2574       */
2575      public URI trimSegments(int i)
2576      {
2577        if (!isHierarchical() || i < 1) return this;
2578    
2579        String[] newSegments = NO_SEGMENTS;
2580        int len = segments.length - i;
2581        if (len > 0)
2582        {
2583          newSegments = new String[len];
2584          System.arraycopy(segments, 0, newSegments, 0, len);
2585        }
2586        return new URI(true, scheme, authority, device, hasAbsolutePath(),
2587                       newSegments, query, fragment);
2588      }
2589    
2590      /**
2591       * Returns <code>true</code> if this is a hierarchical URI that has a path
2592       * that ends with a trailing separator; <code>false</code> otherwise.
2593       *
2594       * <p>A trailing separator is represented as an empty segment as the
2595       * last segment in the path; note that this definition does <em>not</em>
2596       * include the lone separator in the root absolute path.
2597       */
2598      public boolean hasTrailingPathSeparator()
2599      {
2600        return segments.length > 0 && 
2601          SEGMENT_EMPTY.equals(segments[segments.length - 1]);
2602      }
2603    
2604      /**
2605       * If this is a hierarchical URI whose path includes a file extension,
2606       * that file extension is returned; null otherwise.  We define a file
2607       * extension as any string following the last period (".") in the final
2608       * path segment.  If there is no path, the path ends in a trailing
2609       * separator, or the final segment contains no period, then we consider
2610       * there to be no file extension.  If the final segment ends in a period,
2611       * then the file extension is an empty string.
2612       */
2613      public String fileExtension()
2614      {
2615        int len = segments.length;
2616        if (len == 0) return null;
2617    
2618        String lastSegment = segments[len - 1];
2619        int i = lastSegment.lastIndexOf(FILE_EXTENSION_SEPARATOR);
2620        return i < 0 ? null : lastSegment.substring(i + 1);
2621      }
2622    
2623      /**
2624       * Returns the URI formed by appending a period (".") followed by the
2625       * specified file extension to the last path segment of this URI, if it is
2626       * hierarchical with a non-empty path ending in a non-empty segment;
2627       * otherwise, this URI is returned unchanged.
2628    
2629       * <p>The extension is appended regardless of whether the segment already
2630       * contains an extension.
2631       *
2632       * @exception java.lang.IllegalArgumentException if
2633       * <code>fileExtension</code> is not a valid segment (portion) according
2634       * to {@link #validSegment}.
2635       */
2636      public URI appendFileExtension(String fileExtension)
2637      {
2638        if (!validSegment(fileExtension))
2639        {
2640          throw new IllegalArgumentException(
2641            "invalid segment portion: " + fileExtension);
2642        }
2643    
2644        int len = segments.length;
2645        if (len == 0) return this;
2646    
2647        String lastSegment = segments[len - 1];
2648        if (SEGMENT_EMPTY.equals(lastSegment)) return this;
2649        StringBuffer newLastSegment = new StringBuffer(lastSegment);
2650        newLastSegment.append(FILE_EXTENSION_SEPARATOR);
2651        newLastSegment.append(fileExtension);
2652    
2653        String[] newSegments = new String[len];
2654        System.arraycopy(segments, 0, newSegments, 0, len - 1);
2655        newSegments[len - 1] = newLastSegment.toString();
2656        
2657        // note: segments.length > 0 -> hierarchical
2658        return new URI(true, scheme, authority, device, hasAbsolutePath(),
2659                       newSegments, query, fragment); 
2660      }
2661    
2662      /**
2663       * If this URI has a non-null {@link #fileExtension fileExtension},
2664       * returns the URI formed by removing it; this URI unchanged, otherwise.
2665       */
2666      public URI trimFileExtension()
2667      {
2668        int len = segments.length;
2669        if (len == 0) return this;
2670    
2671        String lastSegment = segments[len - 1];
2672        int i = lastSegment.lastIndexOf(FILE_EXTENSION_SEPARATOR);
2673        if (i < 0) return this;
2674    
2675        String newLastSegment = lastSegment.substring(0, i);
2676        String[] newSegments = new String[len];
2677        System.arraycopy(segments, 0, newSegments, 0, len - 1);
2678        newSegments[len - 1] = newLastSegment;
2679    
2680        // note: segments.length > 0 -> hierarchical
2681        return new URI(true, scheme, authority, device, hasAbsolutePath(),
2682                       newSegments, query, fragment); 
2683      }
2684    
2685      /**
2686       * Returns <code>true</code> if this is a hierarchical URI that ends in a
2687       * slash; that is, it has a trailing path separator or is the root
2688       * absolute path, and has no query and no fragment; <code>false</code>
2689       * is returned otherwise.
2690       */
2691      public boolean isPrefix()
2692      {
2693        return isHierarchical() && query == null && fragment == null &&
2694          (hasTrailingPathSeparator() || (hasAbsolutePath() && segments.length == 0));
2695      }
2696    
2697      /**
2698       * If this is a hierarchical URI reference and <code>oldPrefix</code> is a
2699       * prefix of it, this returns the URI formed by replacing it by
2700       * <code>newPrefix</code>; <code>null</code> otherwise.
2701       *
2702       * <p>In order to be a prefix, the <code>oldPrefix</code>'s
2703       * {@link #isPrefix isPrefix} must return <code>true</code>, and it must
2704       * match this URI's scheme, authority, and device.  Also, the paths must
2705       * match, up to prefix's end.
2706       *
2707       * @exception java.lang.IllegalArgumentException if either
2708       * <code>oldPrefix</code> or <code>newPrefix</code> is not a prefix URI
2709       * according to {@link #isPrefix}.
2710       */
2711      public URI replacePrefix(URI oldPrefix, URI newPrefix)
2712      {
2713        if (!oldPrefix.isPrefix() || !newPrefix.isPrefix())
2714        {
2715          String which = oldPrefix.isPrefix() ? "new" : "old";
2716          throw new IllegalArgumentException("non-prefix " + which + " value");
2717        }
2718    
2719        // Get what's left of the segments after trimming the prefix.
2720        String[] tailSegments = getTailSegments(oldPrefix);
2721        if (tailSegments == null) return null;
2722    
2723        // If the new prefix has segments, it is not the root absolute path,
2724        // and we need to drop the trailing empty segment and append the tail
2725        // segments.
2726        String[] mergedSegments = tailSegments;
2727        if (newPrefix.segmentCount() != 0)
2728        {
2729          int segmentsToKeep = newPrefix.segmentCount() - 1;
2730          mergedSegments = new String[segmentsToKeep + tailSegments.length];
2731          System.arraycopy(newPrefix.segments(), 0, mergedSegments, 0,
2732                           segmentsToKeep);
2733    
2734          if (tailSegments.length != 0)
2735          {
2736            System.arraycopy(tailSegments, 0, mergedSegments, segmentsToKeep,
2737                             tailSegments.length);
2738          }
2739        }
2740    
2741        // no validation needed since all components are from existing URIs
2742        return new URI(true, newPrefix.scheme(), newPrefix.authority(),
2743                       newPrefix.device(), newPrefix.hasAbsolutePath(),
2744                       mergedSegments, query, fragment);
2745      }
2746    
2747      // If this is a hierarchical URI reference and prefix is a prefix of it,
2748      // returns the portion of the path remaining after that prefix has been
2749      // trimmed; null otherwise.
2750      private String[] getTailSegments(URI prefix)
2751      {
2752        if (!prefix.isPrefix())
2753        {
2754          throw new IllegalArgumentException("non-prefix trim");
2755        }
2756    
2757        // Don't even consider it unless this is hierarchical and has scheme,
2758        // authority, device and path absoluteness equal to those of the prefix.
2759        if (!isHierarchical() ||
2760            !equals(scheme, prefix.scheme(), true) ||
2761            !equals(authority, prefix.authority()) ||
2762            !equals(device, prefix.device()) ||
2763            hasAbsolutePath() != prefix.hasAbsolutePath())
2764        {
2765          return null;
2766        }
2767    
2768        // If the prefix has no segments, then it is the root absolute path, and
2769        // we know this is an absolute path, too.
2770        if (prefix.segmentCount() == 0) return segments;
2771    
2772        // This must have no fewer segments than the prefix.  Since the prefix
2773        // is not the root absolute path, its last segment is empty; all others
2774        // must match.
2775        int i = 0;
2776        int segmentsToCompare = prefix.segmentCount() - 1;
2777        if (segments.length <= segmentsToCompare) return null;
2778    
2779        for (; i < segmentsToCompare; i++)
2780        {
2781          if (!segments[i].equals(prefix.segment(i))) return null;
2782        }
2783    
2784        // The prefix really is a prefix of this.  If this has just one more,
2785        // empty segment, the paths are the same.
2786        if (i == segments.length - 1 && SEGMENT_EMPTY.equals(segments[i]))
2787        {
2788          return NO_SEGMENTS;
2789        }
2790        
2791        // Otherwise, the path needs only the remaining segments.
2792        String[] newSegments = new String[segments.length - i];
2793        System.arraycopy(segments, i, newSegments, 0, newSegments.length);
2794        return newSegments;
2795      }
2796    
2797      /**
2798       * Encodes a string so as to produce a valid opaque part value, as defined
2799       * by the RFC.  All excluded characters, such as space and <code>#</code>,
2800       * are escaped, as is <code>/</code> if it is the first character.
2801       * 
2802       * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
2803       * unescaped if they already begin a valid three-character escape sequence;
2804       * <code>false</code> to encode all <code>%</code> characters.  Note that
2805       * if a <code>%</code> is not followed by 2 hex digits, it will always be
2806       * escaped. 
2807       */
2808      public static String encodeOpaquePart(String value, boolean ignoreEscaped)
2809      {
2810        String result = encode(value, URIC_HI, URIC_LO, ignoreEscaped);
2811        return result != null && result.length() > 0 && result.charAt(0) == SEGMENT_SEPARATOR ?
2812          "%2F" + result.substring(1) :
2813          result;
2814      }
2815    
2816      /**
2817       * Encodes a string so as to produce a valid authority, as defined by the
2818       * RFC.  All excluded characters, such as space and <code>#</code>,
2819       * are escaped, as are <code>/</code> and <code>?</code>
2820       * 
2821       * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
2822       * unescaped if they already begin a valid three-character escape sequence;
2823       * <code>false</code> to encode all <code>%</code> characters.  Note that
2824       * if a <code>%</code> is not followed by 2 hex digits, it will always be
2825       * escaped. 
2826       */
2827      public static String encodeAuthority(String value, boolean ignoreEscaped)
2828      {
2829        return encode(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, ignoreEscaped);
2830      }
2831    
2832      /**
2833       * Encodes a string so as to produce a valid segment, as defined by the
2834       * RFC.  All excluded characters, such as space and <code>#</code>,
2835       * are escaped, as are <code>/</code> and <code>?</code>
2836       * 
2837       * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
2838       * unescaped if they already begin a valid three-character escape sequence;
2839       * <code>false</code> to encode all <code>%</code> characters.  Note that
2840       * if a <code>%</code> is not followed by 2 hex digits, it will always be
2841       * escaped. 
2842       */
2843      public static String encodeSegment(String value, boolean ignoreEscaped)
2844      {
2845        return encode(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, ignoreEscaped);
2846      }
2847    
2848      /**
2849       * Encodes a string so as to produce a valid query, as defined by the RFC.
2850       * Only excluded characters, such as space and <code>#</code>, are escaped.
2851       * 
2852       * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
2853       * unescaped if they already begin a valid three-character escape sequence;
2854       * <code>false</code> to encode all <code>%</code> characters.  Note that
2855       * if a <code>%</code> is not followed by 2 hex digits, it will always be
2856       * escaped. 
2857       */
2858      public static String encodeQuery(String value, boolean ignoreEscaped)
2859      {
2860        return encode(value, URIC_HI, URIC_LO, ignoreEscaped);
2861      }
2862    
2863      /**
2864       * Encodes a string so as to produce a valid fragment, as defined by the
2865       * RFC.  Only excluded characters, such as space and <code>#</code>, are
2866       * escaped.
2867       * 
2868       * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
2869       * unescaped if they already begin a valid three-character escape sequence;
2870       * <code>false</code> to encode all <code>%</code> characters.  Note that
2871       * if a <code>%</code> is not followed by 2 hex digits, it will always be
2872       * escaped. 
2873       */
2874      public static String encodeFragment(String value, boolean ignoreEscaped)
2875      {
2876        return encode(value, URIC_HI, URIC_LO, ignoreEscaped);
2877      }
2878    
2879      // Encodes a complete URI, optionally leaving % characters unescaped when
2880      // beginning a valid three-character escape sequence.  We can either treat
2881      // the first or # as a fragment separator, or encode them all.
2882      private static String encodeURI(String uri, boolean ignoreEscaped, int fragmentLocationStyle)
2883      {
2884        if (uri == null) return null;
2885    
2886        StringBuffer result = new StringBuffer();
2887    
2888        int i = uri.indexOf(SCHEME_SEPARATOR);
2889        if (i != -1)
2890        {
2891          String scheme = uri.substring(0, i);
2892          result.append(scheme);
2893          result.append(SCHEME_SEPARATOR);
2894        }
2895        
2896        int j =
2897          fragmentLocationStyle == FRAGMENT_FIRST_SEPARATOR ? uri.indexOf(FRAGMENT_SEPARATOR) :
2898            fragmentLocationStyle == FRAGMENT_LAST_SEPARATOR ? uri.lastIndexOf(FRAGMENT_SEPARATOR) : -1;
2899    
2900        if (j != -1)
2901        {
2902          String sspart = uri.substring(++i, j);
2903          result.append(encode(sspart, URIC_HI, URIC_LO, ignoreEscaped));
2904          result.append(FRAGMENT_SEPARATOR);
2905    
2906          String fragment = uri.substring(++j);
2907          result.append(encode(fragment, URIC_HI, URIC_LO, ignoreEscaped));
2908        }
2909        else
2910        {
2911          String sspart = uri.substring(++i);
2912          result.append(encode(sspart, URIC_HI, URIC_LO, ignoreEscaped));
2913        }
2914        
2915        return result.toString();
2916      }
2917    
2918      // Encodes the given string, replacing each ASCII character that is not in
2919      // the set specified by the 128-bit bitmask and each non-ASCII character
2920      // below 0xA0 by an escape sequence of % followed by two hex digits.  If
2921      // % is not in the set but ignoreEscaped is true, then % will not be encoded
2922      // iff it already begins a valid escape sequence.
2923      private static String encode(String value, long highBitmask, long lowBitmask, boolean ignoreEscaped)
2924      {
2925        if (value == null) return null;
2926    
2927        StringBuffer result = null;
2928    
2929        for (int i = 0, len = value.length(); i < len; i++)
2930        {
2931          char c = value.charAt(i);
2932    
2933          if (!matches(c, highBitmask, lowBitmask) && c < 160 &&
2934              (!ignoreEscaped || !isEscaped(value, i)))
2935          {
2936            if (result == null)
2937            {
2938              result = new StringBuffer(value.substring(0, i));
2939            }
2940            appendEscaped(result, (byte)c);
2941          }
2942          else if (result != null)
2943          {
2944            result.append(c);
2945          }
2946        }
2947        return result == null ? value : result.toString();
2948      }
2949    
2950      // Tests whether an escape occurs in the given string, starting at index i.
2951      // An escape sequence is a % followed by two hex digits.
2952      private static boolean isEscaped(String s, int i)
2953      {
2954        return s.charAt(i) == ESCAPE && s.length() > i + 2 &&
2955          matches(s.charAt(i + 1), HEX_HI, HEX_LO) &&
2956          matches(s.charAt(i + 2), HEX_HI, HEX_LO);
2957      }
2958    
2959      // Computes a three-character escape sequence for the byte, appending
2960      // it to the StringBuffer.  Only characters up to 0xFF should be escaped;
2961      // all but the least significant byte will be ignored.
2962      private static void appendEscaped(StringBuffer result, byte b)
2963      {
2964        result.append(ESCAPE);
2965    
2966        // The byte is automatically widened into an int, with sign extension,
2967        // for shifting.  This can introduce 1's to the left of the byte, which
2968        // must be cleared by masking before looking up the hex digit.
2969        //
2970        result.append(HEX_DIGITS[(b >> 4) & 0x0F]);
2971        result.append(HEX_DIGITS[b & 0x0F]);
2972      }
2973    
2974      /**
2975       * Decodes the given string by interpreting three-digit escape sequences as the bytes of a UTF-8 encoded character
2976       * and replacing them with the characters they represent.
2977       * Incomplete escape sequences are ignored and invalid UTF-8 encoded bytes are treated as extended ASCII characters.
2978       */
2979      public static String decode(String value)
2980      {
2981        if (value == null) return null;
2982    
2983        int i = value.indexOf('%');
2984        if (i < 0)
2985        {
2986          return value;
2987        }
2988        else
2989        {
2990          StringBuilder result = new StringBuilder(value.substring(0, i));
2991          byte [] bytes = new byte[4];
2992          int receivedBytes = 0;
2993          int expectedBytes = 0;
2994          for (int len = value.length(); i < len; i++)
2995          {
2996            if (isEscaped(value, i)) 
2997            {
2998              char character = unescape(value.charAt(i + 1), value.charAt(i + 2));
2999              i += 2;
3000              
3001              if (expectedBytes > 0)
3002              {
3003                if ((character & 0xC0) == 0x80)
3004                {
3005                  bytes[receivedBytes++] = (byte)character;
3006                }
3007                else
3008                {
3009                  expectedBytes = 0;
3010                }
3011              }
3012              else if (character >= 0x80)
3013              {
3014                if ((character & 0xE0) == 0xC0)
3015                {
3016                  bytes[receivedBytes++] = (byte)character;
3017                  expectedBytes = 2;
3018                }
3019                else if ((character & 0xF0) == 0xE0)
3020                {
3021                  bytes[receivedBytes++] = (byte)character;
3022                  expectedBytes = 3;
3023                }
3024                else if ((character & 0xF8) == 0xF0)
3025                {
3026                  bytes[receivedBytes++] = (byte)character;
3027                  expectedBytes = 4;
3028                }
3029              }
3030    
3031              if (expectedBytes > 0)
3032              {
3033                if (receivedBytes == expectedBytes)
3034                {
3035                  switch (receivedBytes)
3036                  {
3037                    case 2:
3038                    {
3039                      result.append((char)((bytes[0] & 0x1F) << 6 | bytes[1] & 0x3F));
3040                      break;
3041                    }
3042                    case 3:
3043                    {
3044                      result.append((char)((bytes[0] & 0xF) << 12 | (bytes[1] & 0X3F) << 6 | bytes[2] & 0x3F));
3045                      break;
3046                    }
3047                    case 4:
3048                    {
3049                      result.appendCodePoint(((bytes[0] & 0x7) << 18 | (bytes[1] & 0X3F) << 12 | (bytes[2] & 0X3F) << 6 | bytes[3] & 0x3F));
3050                      break;
3051                    }
3052                  }
3053                  receivedBytes = 0;
3054                  expectedBytes = 0;
3055                }
3056              }
3057              else
3058              {
3059                for (int j = 0; j < receivedBytes; ++j)
3060                {
3061                  result.append((char)bytes[j]);
3062                }
3063                receivedBytes = 0;
3064                result.append(character);
3065              }
3066            }
3067            else
3068            {
3069              for (int j = 0; j < receivedBytes; ++j)
3070              {
3071                result.append((char)bytes[j]);
3072              }
3073              receivedBytes = 0;
3074              result.append(value.charAt(i));
3075            }
3076          }
3077          return result.toString();
3078        }
3079      }
3080    
3081      // Returns the character encoded by % followed by the two given hex digits,
3082      // which is always 0xFF or less, so can safely be casted to a byte.  If
3083      // either character is not a hex digit, a bogus result will be returned.
3084      private static char unescape(char highHexDigit, char lowHexDigit)
3085      {
3086        return (char)((valueOf(highHexDigit) << 4) | valueOf(lowHexDigit));
3087      }
3088    
3089      // Returns the int value of the given hex digit.
3090      private static int valueOf(char hexDigit)
3091      {
3092        if (hexDigit >= 'A' && hexDigit <= 'F')
3093        {
3094          return hexDigit - 'A' + 10;
3095        }
3096        if (hexDigit >= 'a' && hexDigit <= 'f')
3097        {
3098          return hexDigit - 'a' + 10;
3099        }
3100        if (hexDigit >= '0' && hexDigit <= '9')
3101        {
3102          return hexDigit - '0';
3103        }
3104        return 0;
3105      }
3106    
3107      /*
3108       * Returns <code>true</code> if this URI contains non-ASCII characters;
3109       * <code>false</code> otherwise.
3110       *
3111       * This unused code is included for possible future use... 
3112       */
3113    /*
3114      public boolean isIRI()
3115      {
3116        return iri; 
3117      }
3118    
3119      // Returns true if the given string contains any non-ASCII characters;
3120      // false otherwise.
3121      private static boolean containsNonASCII(String value)
3122      {
3123        for (int i = 0, length = value.length(); i < length; i++)
3124        {
3125          if (value.charAt(i) > 127) return true;
3126        }
3127        return false;
3128      }
3129    */
3130    
3131      /*
3132       * If this is an {@link #isIRI IRI}, converts it to a strict ASCII URI,
3133       * using the procedure described in Section 3.1 of the
3134       * <a href="http://www.w3.org/International/iri-edit/draft-duerst-iri-09.txt">IRI
3135       * Draft RFC</a>.  Otherwise, this URI, itself, is returned.
3136       *
3137       * This unused code is included for possible future use...
3138       */
3139    /*
3140      public URI toASCIIURI()
3141      {
3142        if (!iri) return this;
3143    
3144        if (cachedASCIIURI == null)
3145        {
3146          String eAuthority = encodeAsASCII(authority);
3147          String eDevice = encodeAsASCII(device);
3148          String eQuery = encodeAsASCII(query);
3149          String eFragment = encodeAsASCII(fragment);
3150          String[] eSegments = new String[segments.length];
3151          for (int i = 0; i < segments.length; i++)
3152          {
3153            eSegments[i] = encodeAsASCII(segments[i]);
3154          }
3155          cachedASCIIURI = new URI(hierarchical, scheme, eAuthority, eDevice, absolutePath, eSegments, eQuery, eFragment); 
3156    
3157        }
3158        return cachedASCIIURI;
3159      }
3160    
3161      // Returns a strict ASCII encoding of the given value.  Each non-ASCII
3162      // character is converted to bytes using UTF-8 encoding, which are then
3163      // represented using % escaping.
3164      private String encodeAsASCII(String value)
3165      {
3166        if (value == null) return null;
3167    
3168        StringBuffer result = null;
3169    
3170        for (int i = 0, length = value.length(); i < length; i++)
3171        {
3172          char c = value.charAt(i);
3173    
3174          if (c >= 128)
3175          {
3176            if (result == null)
3177            {
3178              result = new StringBuffer(value.substring(0, i));
3179            }
3180    
3181            try
3182            {
3183              byte[] encoded = (new String(new char[] { c })).getBytes("UTF-8");
3184              for (int j = 0, encLen = encoded.length; j < encLen; j++)
3185              {
3186                appendEscaped(result, encoded[j]);
3187              }
3188            }
3189            catch (UnsupportedEncodingException e)
3190            {
3191              throw new WrappedException(e);
3192            }
3193          }
3194          else if (result != null)
3195          {
3196            result.append(c);
3197          }
3198    
3199        }
3200        return result == null ? value : result.toString();
3201      }
3202    
3203      // Returns the number of valid, consecutive, three-character escape
3204      // sequences in the given string, starting at index i.
3205      private static int countEscaped(String s, int i)
3206      {
3207        int result = 0;
3208    
3209        for (int length = s.length(); i < length; i += 3)
3210        {
3211          if (isEscaped(s, i)) result++;
3212        }
3213        return result;
3214      }
3215    */
3216    }