KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > emf > common > util > URI


1 /**
2  * <copyright>
3  *
4  * Copyright (c) 2002-2005 IBM Corporation and others.
5  * All rights reserved. This program and the accompanying materials
6  * are made available under the terms of the Eclipse Public License v1.0
7  * which accompanies this distribution, and is available at
8  * http://www.eclipse.org/legal/epl-v10.html
9  *
10  * Contributors:
11  * IBM - Initial API and implementation
12  *
13  * </copyright>
14  *
15  * $Id: URI.java,v 1.18 2005/06/08 06:19:08 nickb Exp $
16  */

17 package org.eclipse.emf.common.util;
18
19 import java.io.File JavaDoc;
20 import java.util.ArrayList JavaDoc;
21 import java.util.Arrays JavaDoc;
22 import java.util.Collections JavaDoc;
23 import java.util.HashMap JavaDoc;
24 import java.util.HashSet JavaDoc;
25 import java.util.List JavaDoc;
26 import java.util.Map JavaDoc;
27 import java.util.Set JavaDoc;
28 import java.util.StringTokenizer JavaDoc;
29
30 /**
31  * A representation of a Uniform Resource Identifier (URI), as specified by
32  * <a HREF="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>, with certain
33  * enhancements. A <code>URI</code> instance can be created by specifying
34  * values for its components, or by providing a single URI string, which is
35  * parsed into its components. Static factory methods whose names begin
36  * with "create" are used for both forms of object creation. No public or
37  * protected constructors are provided; this class can not be subclassed.
38  *
39  * <p>Like <code>String</code>, <code>URI</code> is an immutable class;
40  * a <code>URI</code> instance offers several by-value methods that return a
41  * new <code>URI</code> object based on its current state. Most useful,
42  * a relative <code>URI</code> can be {@link #resolve(URI) resolve}d against
43  * a base absolute <code>URI</code> -- the latter typically identifies the
44  * document in which the former appears. The inverse to this is {@link
45  * #deresolve(URI) deresolve}, which answers the question, "what relative
46  * URI will resolve, against the given base, to this absolute URI?"
47  *
48  * <p>In the <a HREF="http://www.ietf.org/rfc/rfc2396.txt">RFC</a>, much
49  * attention is focused on a hierarchical naming system used widely to
50  * locate resources via common protocols such as HTTP, FTP, and Gopher, and
51  * to identify files on a local file system. Acordingly, most of this
52  * class's functionality is for handling such URIs, which can be identified
53  * via {@link #isHierarchical isHierarchical}.
54  *
55  * <p><a name="device_explanation">
56  * The primary enhancement beyond the RFC description is an optional
57  * device component. Instead of treating the device as just another segment
58  * in the path, it can be stored as a separate component (almost a
59  * sub-authority), with the root below it. For example, resolving
60  * <code>/bar</code> against <code>file:///c:/foo</code> would result in
61  * <code>file:///c:/bar</code> being returned. Also, you cannot take
62  * the parent of a device, so resolving <code>..</code> against
63  * <code>file:///c:/</code> would not yield <code>file:///</code>, as you
64  * might expect. This feature is useful when working with file-scheme
65  * URIs, as devices do not typically occur in protocol-based ones. A
66  * device-enabled <code>URI</code> is created by parsing a string with
67  * {@link #createURI(String) createURI}; if the first segment of the path
68  * ends with the <code>:</code> character, it is stored (including the colon)
69  * as the device, instead. Alternately, either the {@link
70  * #createHierarchicalURI(String, String, String, String, String) no-path}
71  * or the {@link #createHierarchicalURI(String, String, String, String[],
72  * String, String) absolute-path} form of <code>createHierarchicalURI()</code>
73  * can be used, in which a non-null <code>device</code> parameter can be
74  * specified.
75  *
76  * <p><a name="archive_explanation">
77  * The other enhancement provides support for the almost-hierarchical
78  * form used for files within archives, such as the JAR scheme, defined
79  * for the Java Platform in the documentation for {@link
80  * java.net.JarURLConnection}. By default, this support is enabled for
81  * absolute URIs with scheme equal to "jar", "zip", or "archive" (ignoring case), and
82  * is implemented by a hierarchical URI, whose authority includes the
83  * entire URI of the archive, up to and including the <code>!</code>
84  * character. The URI of the archive must have no fragment. The whole
85  * archive URI must have no device and an absolute path. Special handling
86  * is supported for {@link #createURI creating}, {@link
87  * #validArchiveAuthority validating}, {@link #devicePath getting the path}
88  * from, and {@link #toString displaying} archive URIs. In all other
89  * operations, including {@link #resolve(URI) resolving} and {@link
90  * #deresolve(URI) deresolving}, they are handled like any ordinary URI.
91  * The schemes that identify archive URIs can be changed from their default
92  * by setting the <code>org.eclipse.emf.common.util.URI.archiveSchemes</code>
93  * system property. Multiple schemes should be space separated, and the test
94  * of whether a URI's scheme matches is always case-insensitive.
95  *
96  * <p>This implementation does not impose the all of the restrictions on
97  * character validity that are specified in the RFC. Static methods whose
98  * names begin with "valid" are used to test whether a given string is valid
99  * value for the various URI components. Presently, these tests place no
100  * restrictions beyond what would have been required in order for {@link
101  * createURI(String) createURI} to have parsed them correctly from a single
102  * URI string. If necessary in the future, these tests may be made more
103  * strict, to better coform to the RFC.
104  *
105  * <p>Another group of static methods, whose names begin with "encode", use
106  * percent escaping to encode any characters that are not permitted in the
107  * various URI components. Another static method is provided to {@link
108  * #decode decode} encoded strings. An escaped character is represented as
109  * a percent sybol (<code>%</code>), followed by two hex digits that specify
110  * the character code. These encoding methods are more strict than the
111  * validation methods described above. They ensure validity according to the
112  * RFC, with one exception: non-ASCII characters.
113  *
114  * <p>The RFC allows only characters that can be mapped to 7-bit US-ASCII
115  * representations. Non-ASCII, single-byte characters can be used only via
116  * percent escaping, as described above. This implementation uses Java's
117  * Unicode <code>char</code> and <code>String</code> representations, and
118  * makes no attempt to encode characters 0xA0 and above. Characters in the
119  * range 0x80-0x9F are still escaped. In this respect, EMF's notion of a URI
120  * is actually more like an IRI (Internationalized Resource Identifier), for
121  * which an RFC is now in <href="http://www.w3.org/International/iri-edit/draft-duerst-iri-09.txt">draft
122  * form</a>.
123  *
124  * <p>Finally, note the difference between a <code>null</code> parameter to
125  * the static factory methods and an empty string. The former signifies the
126  * absense of a given URI component, while the latter simply makes the
127  * component blank. This can have a significant effect when resolving. For
128  * example, consider the following two URIs: <code>/bar</code> (with no
129  * authority) and <code>///bar</code> (with a blank authority). Imagine
130  * resolving them against a base with an authority, such as
131  * <code>http://www.eclipse.org/</code>. The former case will yield
132  * <code>http://www.eclipse.org/bar</code>, as the base authority will be
133  * preserved. In the latter case, the empty authority will override the
134  * base authority, resulting in <code>http:///bar</code>!
135  */

136 public final class URI
137 {
138   // Common to all URI types.
139
private final int hashCode;
140   private final boolean hierarchical;
141   private final String JavaDoc scheme; // null -> relative URI reference
142
private final String JavaDoc authority;
143   private final String JavaDoc fragment;
144   private URI cachedTrimFragment;
145   private String JavaDoc cachedToString;
146   //private final boolean iri;
147
//private URI cachedASCIIURI;
148

149   // Applicable only to a hierarchical URI.
150
private final String JavaDoc device;
151   private final boolean absolutePath;
152   private final String JavaDoc[] segments; // empty last segment -> trailing separator
153
private final String JavaDoc query;
154
155   // A cache of URIs, keyed by the strings from which they were created.
156
// The fragment of any URI is removed before caching it here, to minimize
157
// the size of the cache in the usual case where most URIs only differ by
158
// the fragment.
159
private static final Map JavaDoc uriCache = Collections.synchronizedMap(new HashMap JavaDoc());
160
161   // The lower-cased schemes that will be used to identify archive URIs.
162
private static final Set JavaDoc archiveSchemes;
163
164   // Identifies a file-type absolute URI.
165
private static final String JavaDoc SCHEME_FILE = "file";
166   private static final String JavaDoc SCHEME_JAR = "jar";
167   private static final String JavaDoc SCHEME_ZIP = "zip";
168   private static final String JavaDoc SCHEME_ARCHIVE = "archive";
169
170   // Special segment values interpreted at resolve and resolve time.
171
private static final String JavaDoc SEGMENT_EMPTY = "";
172   private static final String JavaDoc SEGMENT_SELF = ".";
173   private static final String JavaDoc SEGMENT_PARENT = "..";
174   private static final String JavaDoc[] NO_SEGMENTS = new String JavaDoc[0];
175
176   // Separators for parsing a URI string.
177
private static final char SCHEME_SEPARATOR = ':';
178   private static final String JavaDoc AUTHORITY_SEPARATOR = "//";
179   private static final char DEVICE_IDENTIFIER = ':';
180   private static final char SEGMENT_SEPARATOR = '/';
181   private static final char QUERY_SEPARATOR = '?';
182   private static final char FRAGMENT_SEPARATOR = '#';
183   private static final char USER_INFO_SEPARATOR = '@';
184   private static final char PORT_SEPARATOR = ':';
185   private static final char FILE_EXTENSION_SEPARATOR = '.';
186   private static final char ARCHIVE_IDENTIFIER = '!';
187   private static final String JavaDoc ARCHIVE_SEPARATOR = "!/";
188
189   // Characters to use in escaping.
190
private static final char ESCAPE = '%';
191   private static final char[] HEX_DIGITS = {
192     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
193
194   // Some character classes, as defined in RFC 2396's BNF for URI.
195
// These are 128-bit bitmasks, stored as two longs, where the Nth bit is set
196
// iff the ASCII character with value N is included in the set. These are
197
// created with the highBitmask() and lowBitmask() methods defined below,
198
// and a character is tested against them using matches().
199
//
200
private static final long ALPHA_HI = highBitmask('a', 'z') | highBitmask('A', 'Z');
201   private static final long ALPHA_LO = lowBitmask('a', 'z') | lowBitmask('A', 'Z');
202   private static final long DIGIT_HI = highBitmask('0', '9');
203   private static final long DIGIT_LO = lowBitmask('0', '9');
204   private static final long ALPHANUM_HI = ALPHA_HI | DIGIT_HI;
205   private static final long ALPHANUM_LO = ALPHA_LO | DIGIT_LO;
206   private static final long HEX_HI = DIGIT_HI | highBitmask('A', 'F') | highBitmask('a', 'f');
207   private static final long HEX_LO = DIGIT_LO | lowBitmask('A', 'F') | lowBitmask('a', 'f');
208   private static final long UNRESERVED_HI = ALPHANUM_HI | highBitmask("-_.!~*'()");
209   private static final long UNRESERVED_LO = ALPHANUM_LO | lowBitmask("-_.!~*'()");
210   private static final long RESERVED_HI = highBitmask(";/?:@&=+$,");
211   private static final long RESERVED_LO = lowBitmask(";/?:@&=+$,");
212   private static final long URIC_HI = RESERVED_HI | UNRESERVED_HI; // | ucschar | escaped
213
private static final long URIC_LO = RESERVED_LO | UNRESERVED_LO;
214
215   // Additional useful character classes, including characters valid in certain
216
// URI components and separators used in parsing them out of a string.
217
//
218
private static final long SEGMENT_CHAR_HI = UNRESERVED_HI | highBitmask(";:@&=+$,"); // | ucschar | escaped
219
private static final long SEGMENT_CHAR_LO = UNRESERVED_LO | lowBitmask(";:@&=+$,");
220   private static final long PATH_CHAR_HI = SEGMENT_CHAR_HI | highBitmask('/'); // | ucschar | escaped
221
private static final long PATH_CHAR_LO = SEGMENT_CHAR_LO | lowBitmask('/');
222 // private static final long SCHEME_CHAR_HI = ALPHANUM_HI | highBitmask("+-.");
223
// private static final long SCHEME_CHAR_LO = ALPHANUM_LO | lowBitmask("+-.");
224
private static final long MAJOR_SEPARATOR_HI = highBitmask(":/?#");
225   private static final long MAJOR_SEPARATOR_LO = lowBitmask(":/?#");
226   private static final long SEGMENT_END_HI = highBitmask("/?#");
227   private static final long SEGMENT_END_LO = lowBitmask("/?#");
228
229   // We can't want to do encoding of platform resource URIs by default yet.
230
//
231
private static final boolean ENCODE_PLATFORM_RESOURCE_URIS =
232     System.getProperty("org.eclipse.emf.common.util.URI.encodePlatformResourceURIs") != null &&
233     !"false".equalsIgnoreCase(System.getProperty("org.eclipse.emf.common.util.URI.encodePlatformResourceURIs"));
234
235   // Static initializer for archiveSchemes.
236
static
237   {
238     Set JavaDoc set = new HashSet JavaDoc();
239     String JavaDoc propertyValue = System.getProperty("org.eclipse.emf.common.util.URI.archiveSchemes");
240
241     if (propertyValue == null)
242     {
243       set.add(SCHEME_JAR);
244       set.add(SCHEME_ZIP);
245       set.add(SCHEME_ARCHIVE);
246     }
247     else
248     {
249       for (StringTokenizer JavaDoc t = new StringTokenizer JavaDoc(propertyValue); t.hasMoreTokens(); )
250       {
251         set.add(t.nextToken().toLowerCase());
252       }
253     }
254     
255     archiveSchemes = Collections.unmodifiableSet(set);
256   }
257
258   // Returns the lower half bitmask for the given ASCII character.
259
private static long lowBitmask(char c)
260   {
261     return c < 64 ? 1L << c : 0L;
262   }
263
264   // Returns the upper half bitmask for the given ACSII character.
265
private static long highBitmask(char c)
266   {
267     return c >= 64 && c < 128 ? 1L << (c - 64) : 0L;
268   }
269
270   // Returns the lower half bitmask for all ASCII characters between the two
271
// given characters, inclusive.
272
private static long lowBitmask(char from, char to)
273   {
274     long result = 0L;
275     if (from < 64 && from <= to)
276     {
277       to = to < 64 ? to : 63;
278       for (char c = from; c <= to; c++)
279       {
280         result |= (1L << c);
281       }
282     }
283     return result;
284   }
285
286   // Returns the upper half bitmask for all AsCII characters between the two
287
// given characters, inclusive.
288
private static long highBitmask(char from, char to)
289   {
290     return to < 64 ? 0 : lowBitmask((char)(from < 64 ? 0 : from - 64), (char)(to - 64));
291   }
292
293   // Returns the lower half bitmask for all the ASCII characters in the given
294
// string.
295
private static long lowBitmask(String JavaDoc chars)
296   {
297     long result = 0L;
298     for (int i = 0, len = chars.length(); i < len; i++)
299     {
300       char c = chars.charAt(i);
301       if (c < 64) result |= (1L << c);
302     }
303     return result;
304   }
305
306   // Returns the upper half bitmask for all the ASCII characters in the given
307
// string.
308
private static long highBitmask(String JavaDoc chars)
309   {
310     long result = 0L;
311     for (int i = 0, len = chars.length(); i < len; i++)
312     {
313       char c = chars.charAt(i);
314       if (c >= 64 && c < 128) result |= (1L << (c - 64));
315     }
316     return result;
317   }
318
319   // Returns whether the given character is in the set specified by the given
320
// bitmask.
321
private static boolean matches(char c, long highBitmask, long lowBitmask)
322   {
323     if (c >= 128) return false;
324     return c < 64 ?
325       ((1L << c) & lowBitmask) != 0 :
326       ((1L << (c - 64)) & highBitmask) != 0;
327   }
328
329   // Debugging method: converts the given long to a string of binary digits.
330
/*
331   private static String toBits(long l)
332   {
333     StringBuffer result = new StringBuffer();
334     for (int i = 0; i < 64; i++)
335     {
336       boolean b = (l & 1L) != 0;
337       result.insert(0, b ? '1' : '0');
338       l >>= 1;
339     }
340     return result.toString();
341   }
342 */

343
344   /**
345    * Static factory method for a generic, non-hierarchical URI. There is no
346    * concept of a relative non-hierarchical URI; such an object cannot be
347    * created.
348    *
349    * @exception java.lang.IllegalArgumentException if <code>scheme</code> is
350    * null, if <code>scheme</code> is an <a HREF="#archive_explanation">archive
351    * URI</a> scheme, or if <code>scheme</code>, <code>opaquePart</code>, or
352    * <code>fragment</code> is not valid according to {@link #validScheme
353    * validScheme}, {@link #validOpaquePart validOpaquePart}, or {@link
354    * #validFragment validFragment}, respectively.
355    */

356   public static URI createGenericURI(String JavaDoc scheme, String JavaDoc opaquePart,
357                                      String JavaDoc fragment)
358   {
359     if (scheme == null)
360     {
361       throw new IllegalArgumentException JavaDoc("relative non-hierarchical URI");
362     }
363
364     if (isArchiveScheme(scheme))
365     {
366       throw new IllegalArgumentException JavaDoc("non-hierarchical archive URI");
367     }
368
369     validateURI(false, scheme, opaquePart, null, false, NO_SEGMENTS, null, fragment);
370     return new URI(false, scheme, opaquePart, null, false, NO_SEGMENTS, null, fragment);
371   }
372
373   /**
374    * Static factory method for a hierarchical URI with no path. The
375    * URI will be relative if <code>scheme</code> is non-null, and absolute
376    * otherwise. An absolute URI with no path requires a non-null
377    * <code>authority</code> and/or <code>device</code>.
378    *
379    * @exception java.lang.IllegalArgumentException if <code>scheme</code> is
380    * non-null while <code>authority</code> and <code>device</code> are null,
381    * if <code>scheme</code> is an <a HREF="#archive_explanation">archive
382    * URI</a> scheme, or if <code>scheme</code>, <code>authority</code>,
383    * <code>device</code>, <code>query</code>, or <code>fragment</code> is not
384    * valid according to {@link #validScheme validSheme}, {@link
385    * #validAuthority validAuthority}, {@link #validDevice validDevice},
386    * {@link #validQuery validQuery}, or {@link #validFragment validFragment},
387    * respectively.
388    */

389   public static URI createHierarchicalURI(String JavaDoc scheme, String JavaDoc authority,
390                                           String JavaDoc device, String JavaDoc query,
391                                           String JavaDoc fragment)
392   {
393     if (scheme != null && authority == null && device == null)
394     {
395       throw new IllegalArgumentException JavaDoc(
396         "absolute hierarchical URI without authority, device, path");
397     }
398
399     if (isArchiveScheme(scheme))
400     {
401       throw new IllegalArgumentException JavaDoc("archive URI with no path");
402     }
403
404     validateURI(true, scheme, authority, device, false, NO_SEGMENTS, query, fragment);
405     return new URI(true, scheme, authority, device, false, NO_SEGMENTS, query, fragment);
406   }
407
408   /**
409    * Static factory method for a hierarchical URI with absolute path.
410    * The URI will be relative if <code>scheme</code> is non-null, and
411    * absolute otherwise.
412    *
413    * @param segments an array of non-null strings, each representing one
414    * segment of the path. As an absolute path, it is automatically
415    * preceeded by a <code>/</code> separator. If desired, a trailing
416    * separator should be represented by an empty-string segment as the last
417    * element of the array.
418    *
419    * @exception java.lang.IllegalArgumentException if <code>scheme</code> is
420    * an <a HREF="#archive_explanation">archive URI</a> scheme and
421    * <code>device</code> is non-null, or if <code>scheme</code>,
422    * <code>authority</code>, <code>device</code>, <code>segments</code>,
423    * <code>query</code>, or <code>fragment</code> is not valid according to
424    * {@link #validScheme validScheme}, {@link #validAuthority validAuthority}
425    * or {@link #validArchiveAuthority validArchiveAuthority}, {@link
426    * #validDevice validDevice}, {@link #validSegments validSegments}, {@link
427    * #validQuery validQuery}, or {@link #validFragment validFragment}, as
428    * appropriate.
429    */

430   public static URI createHierarchicalURI(String JavaDoc scheme, String JavaDoc authority,
431                                           String JavaDoc device, String JavaDoc[] segments,
432                                           String JavaDoc query, String JavaDoc fragment)
433   {
434     if (isArchiveScheme(scheme) && device != null)
435     {
436       throw new IllegalArgumentException JavaDoc("archive URI with device");
437     }
438
439     segments = fix(segments);
440     validateURI(true, scheme, authority, device, true, segments, query, fragment);
441     return new URI(true, scheme, authority, device, true, segments, query, fragment);
442   }
443
444   /**
445    * Static factory method for a relative hierarchical URI with relative
446    * path.
447    *
448    * @param segments an array of non-null strings, each representing one
449    * segment of the path. A trailing separator is represented by an
450    * empty-string segment at the end of the array.
451    *
452    * @exception java.lang.IllegalArgumentException if <code>segments</code>,
453    * <code>query</code>, or <code>fragment</code> is not valid according to
454    * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
455    * {@link #validFragment validFragment}, respectively.
456    */

457   public static URI createHierarchicalURI(String JavaDoc[] segments, String JavaDoc query,
458                                           String JavaDoc fragment)
459   {
460     segments = fix(segments);
461     validateURI(true, null, null, null, false, segments, query, fragment);
462     return new URI(true, null, null, null, false, segments, query, fragment);
463   }
464
465   // Converts null to length-zero array, and clones array to ensure
466
// immutability.
467
private static String JavaDoc[] fix(String JavaDoc[] segments)
468   {
469     return segments == null ? NO_SEGMENTS : (String JavaDoc[])segments.clone();
470   }
471   
472   /**
473    * Static factory method based on parsing a URI string, with
474    * <a HREF="#device_explanation">explicit device support</a> and handling
475    * for <a HREF="#archive_explanation">archive URIs</a> enabled. The
476    * specified string is parsed as described in <a
477    * HREF="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>, and an
478    * appropriate <code>URI</code> is created and returned. Note that
479    * validity testing is not as strict as in the RFC; essentially, only
480    * separator characters are considered. So, for example, non-Latin
481    * alphabet characters appearing in the scheme would not be considered an
482    * error.
483    *
484    * @exception java.lang.IllegalArgumentException if any component parsed
485    * from <code>uri</code> is not valid according to {@link #validScheme
486    * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
487    * #validAuthority validAuthority}, {@link #validArchiveAuthority
488    * validArchiveAuthority}, {@link #validDevice validDevice}, {@link
489    * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
490    * #validFragment validFragment}, as appropriate.
491    */

492   public static URI createURI(String JavaDoc uri)
493   {
494     return createURIWithCache(uri);
495   }
496
497   /**
498    * Static factory method that encodes and parses the given URI string.
499    * Appropriate encoding is performed for each component of the URI.
500    * If more than one <code>#</code> is in the string, the last one is
501    * assumed to be the fragment's separator, and any others are encoded.
502    *
503    * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
504    * unescaped if they already begin a valid three-character escape sequence;
505    * <code>false</code> to encode all <code>%</code> characters. Note that
506    * if a <code>%</code> is not followed by 2 hex digits, it will always be
507    * escaped.
508    *
509    * @exception java.lang.IllegalArgumentException if any component parsed
510    * from <code>uri</code> is not valid according to {@link #validScheme
511    * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
512    * #validAuthority validAuthority}, {@link #validArchiveAuthority
513    * validArchiveAuthority}, {@link #validDevice validDevice}, {@link
514    * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
515    * #validFragment validFragment}, as appropriate.
516    */

517   public static URI createURI(String JavaDoc uri, boolean ignoreEscaped)
518   {
519     return createURIWithCache(encodeURI(uri, ignoreEscaped));
520   }
521
522   /**
523    * Static factory method based on parsing a URI string, with
524    * <a HREF="#device_explanation">explicit device support</a> enabled.
525    * Note that validity testing is not a strict as in the RFC; essentially,
526    * only separator characters are considered. So, for example, non-Latin
527    * alphabet characters appearing in the scheme would not be considered an
528    * error.
529    *
530    * @exception java.lang.IllegalArgumentException if any component parsed
531    * from <code>uri</code> is not valid according to {@link #validScheme
532    * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
533    * #validAuthority validAuthority}, {@link #validArchiveAuthority
534    * validArchiveAuthority}, {@link #validDevice validDevice}, {@link
535    * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
536    * #validFragment validFragment}, as appropriate.
537    *
538    * @deprecated Use {@link #createURI createURI}, which now has explicit
539    * device support enabled. The two methods now operate identically.
540    */

541   public static URI createDeviceURI(String JavaDoc uri)
542   {
543     return createURIWithCache(uri);
544   }
545
546   // Uses a cache to speed up creation of a URI from a string. The cache
547
// is consulted to see if the URI, less any fragment, has already been
548
// created. If needed, the fragment is re-appended to the cached URI,
549
// which is considerably more efficient than creating the whole URI from
550
// scratch. If the URI wasn't found in the cache, it is created using
551
// parseIntoURI() and then cached. This method should always be used
552
// by string-parsing factory methods, instead of parseIntoURI() directly.
553
/**
554    * This method was included in the public API by mistake.
555    *
556    * @deprecated Please use {@link #createURI createURI} instead.
557    */

558   public static URI createURIWithCache(String JavaDoc uri)
559   {
560     int i = uri.indexOf(FRAGMENT_SEPARATOR);
561     String JavaDoc base = i == -1 ? uri : uri.substring(0, i);
562     String JavaDoc fragment = i == -1 ? null : uri.substring(i + 1);
563
564     URI result = (URI)uriCache.get(base);
565
566     if (result == null)
567     {
568       result = parseIntoURI(base);
569       uriCache.put(base, result);
570     }
571
572     if (fragment != null)
573     {
574       result = result.appendFragment(fragment);
575     }
576     return result;
577   }
578
579   // String-parsing implementation.
580
private static URI parseIntoURI(String JavaDoc uri)
581   {
582     boolean hierarchical = true;
583     String JavaDoc scheme = null;
584     String JavaDoc authority = null;
585     String JavaDoc device = null;
586     boolean absolutePath = false;
587     String JavaDoc[] segments = NO_SEGMENTS;
588     String JavaDoc query = null;
589     String JavaDoc fragment = null;
590
591     int i = 0;
592     int j = find(uri, i, MAJOR_SEPARATOR_HI, MAJOR_SEPARATOR_LO);
593
594     if (j < uri.length() && uri.charAt(j) == SCHEME_SEPARATOR)
595     {
596       scheme = uri.substring(i, j);
597       i = j + 1;
598     }
599
600     boolean archiveScheme = isArchiveScheme(scheme);
601     if (archiveScheme)
602     {
603       j = uri.lastIndexOf(ARCHIVE_SEPARATOR);
604       if (j == -1)
605       {
606         throw new IllegalArgumentException JavaDoc("no archive separator");
607       }
608       hierarchical = true;
609       authority = uri.substring(i, ++j);
610       i = j;
611     }
612     else if (uri.startsWith(AUTHORITY_SEPARATOR, i))
613     {
614       i += AUTHORITY_SEPARATOR.length();
615       j = find(uri, i, SEGMENT_END_HI, SEGMENT_END_LO);
616       authority = uri.substring(i, j);
617       i = j;
618     }
619     else if (scheme != null &&
620              (i == uri.length() || uri.charAt(i) != SEGMENT_SEPARATOR))
621     {
622       hierarchical = false;
623       j = uri.indexOf(FRAGMENT_SEPARATOR, i);
624       if (j == -1) j = uri.length();
625       authority = uri.substring(i, j);
626       i = j;
627     }
628
629     if (!archiveScheme && i < uri.length() && uri.charAt(i) == SEGMENT_SEPARATOR)
630     {
631       j = find(uri, i + 1, SEGMENT_END_HI, SEGMENT_END_LO);
632       String JavaDoc s = uri.substring(i + 1, j);
633       
634       if (s.length() > 0 && s.charAt(s.length() - 1) == DEVICE_IDENTIFIER)
635       {
636         device = s;
637         i = j;
638       }
639     }
640
641     if (i < uri.length() && uri.charAt(i) == SEGMENT_SEPARATOR)
642     {
643       i++;
644       absolutePath = true;
645     }
646
647     if (segmentsRemain(uri, i))
648     {
649       List JavaDoc segmentList = new ArrayList JavaDoc();
650
651       while (segmentsRemain(uri, i))
652       {
653         j = find(uri, i, SEGMENT_END_HI, SEGMENT_END_LO);
654         segmentList.add(uri.substring(i, j));
655         i = j;
656
657         if (i < uri.length() && uri.charAt(i) == SEGMENT_SEPARATOR)
658         {
659           if (!segmentsRemain(uri, ++i)) segmentList.add(SEGMENT_EMPTY);
660         }
661       }
662       segments = new String JavaDoc[segmentList.size()];
663       segmentList.toArray(segments);
664     }
665
666     if (i < uri.length() && uri.charAt(i) == QUERY_SEPARATOR)
667     {
668       j = uri.indexOf(FRAGMENT_SEPARATOR, ++i);
669       if (j == -1) j = uri.length();
670       query = uri.substring(i, j);
671       i = j;
672     }
673
674     if (i < uri.length()) // && uri.charAt(i) == FRAGMENT_SEPARATOR (implied)
675
{
676       fragment = uri.substring(++i);
677     }
678
679     validateURI(hierarchical, scheme, authority, device, absolutePath, segments, query, fragment);
680     return new URI(hierarchical, scheme, authority, device, absolutePath, segments, query, fragment);
681   }
682
683   // Checks whether the string contains any more segments after the one that
684
// starts at position i.
685
private static boolean segmentsRemain(String JavaDoc uri, int i)
686   {
687     return i < uri.length() && uri.charAt(i) != QUERY_SEPARATOR &&
688       uri.charAt(i) != FRAGMENT_SEPARATOR;
689   }
690
691   // Finds the next occurance of one of the characters in the set represented
692
// by the given bitmask in the given string, beginning at index i. The index
693
// of the first found character, or s.length() if there is none, is
694
// returned. Before searching, i is limited to the range [0, s.length()].
695
//
696
private static int find(String JavaDoc s, int i, long highBitmask, long lowBitmask)
697   {
698     int len = s.length();
699     if (i >= len) return len;
700
701     for (i = i > 0 ? i : 0; i < len; i++)
702     {
703       if (matches(s.charAt(i), highBitmask, lowBitmask)) break;
704     }
705     return i;
706   }
707
708   /**
709    * Static factory method based on parsing a {@link java.io.File} path
710    * string. The <code>pathName</code> is converted into an appropriate
711    * form, as follows: platform specific path separators are converted to
712    * <code>/<code>; the path is encoded; and a "file" scheme and, if missing,
713    * a leading <code>/</code>, are added to an absolute path. The result
714    * is then parsed using {@link #createURI(String) createURI}.
715    *
716    * <p>The encoding step escapes all spaces, <code>#</code> characters, and
717    * other characters disallowed in URIs, as well as <code>?</code>, which
718    * would delimit a path from a query. Decoding is automatically performed
719    * by {@link #toFileString toFileString}, and can be applied to the values
720    * returned by other accessors by via the static {@link #decode(String)
721    * decode} method.
722    *
723    * <p>A relative path with a specified device (something like
724    * <code>C:myfile.txt</code>) cannot be expressed as a valid URI.
725    *
726    * @exception java.lang.IllegalArgumentException if <code>pathName</code>
727    * specifies a device and a relative path, or if any component of the path
728    * is not valid according to {@link #validAuthority validAuthority}, {@link
729    * #validDevice validDevice}, or {@link #validSegments validSegments},
730    * {@link #validQuery validQuery}, or {@link #validFragment validFragment}.
731    */

732   public static URI createFileURI(String JavaDoc pathName)
733   {
734     File JavaDoc file = new File JavaDoc(pathName);
735     String JavaDoc uri = File.separatorChar != '/' ? pathName.replace(File.separatorChar, SEGMENT_SEPARATOR) : pathName;
736     uri = encode(uri, PATH_CHAR_HI, PATH_CHAR_LO, false);
737     if (file.isAbsolute())
738     {
739       URI result = createURI((uri.charAt(0) == SEGMENT_SEPARATOR ? "file:" : "file:/") + uri);
740       return result;
741     }
742     else
743     {
744       URI result = createURI(uri);
745       if (result.scheme() != null)
746       {
747         throw new IllegalArgumentException JavaDoc("invalid relative pathName: " + pathName);
748       }
749       return result;
750     }
751   }
752
753   /**
754    * Static factory method based on parsing a platform-relative path string.
755    *
756    * <p>The <code>pathName</code> must be of the form:
757    * <pre>
758    * /project-name/path</pre>
759    *
760    * <p>Platform-specific path separators will be converterted to slashes.
761    * If not included, the leading path separator will be added. The
762    * result will be of this form, which is parsed using {@link #createURI
763    * createURI}:
764    * <pre>
765    * platform:/resource/project-name/path</pre>
766    *
767    * <p>This scheme supports relocatable projects in Eclipse and in
768    * stand-alone EMF.
769    *
770    * <p>Path encoding is performed only if the
771    * <code>org.eclipse.emf.common.util.URI.encodePlatformResourceURIs</code>
772    * system property is set to "true". Decoding can be performed with the
773    * static {@link #decode(String) decode} method.
774    *
775    * @exception java.lang.IllegalArgumentException if any component parsed
776    * from the path is not valid according to {@link #validDevice validDevice},
777    * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
778    * {@link #validFragment validFragment}.
779    *
780    * @see org.eclipse.core.runtime.Platform#resolve
781    * @see #createPlatformResourceURI(String, boolean)
782    */

783   public static URI createPlatformResourceURI(String JavaDoc pathName)
784   {
785     return createPlatformResourceURI(pathName, ENCODE_PLATFORM_RESOURCE_URIS);
786   }
787
788   /**
789    * Static factory method based on parsing a platform-relative path string,
790    * with an option to encode the created URI.
791    *
792    * <p>The <code>pathName</code> must be of the form:
793    * <pre>
794    * /project-name/path</pre>
795    *
796    * <p>Platform-specific path separators will be converterted to slashes.
797    * If not included, the leading path separator will be added. The
798    * result will be of this form, which is parsed using {@link #createURI
799    * createURI}:
800    * <pre>
801    * platform:/resource/project-name/path</pre>
802    *
803    * <p>This scheme supports relocatable projects in Eclipse and in
804    * stand-alone EMF.
805    *
806    * <p>Depending on the <code>encode</code> argument, the path may be
807    * automatically encoded to escape all spaces, <code>#</code> characters,
808    * and other characters disallowed in URIs, as well as <code>?</code>,
809    * which would delimit a path from a query. Decoding can be performed with
810    * the static {@link #decode(String) decode} method.
811    *
812    * @exception java.lang.IllegalArgumentException if any component parsed
813    * from the path is not valid according to {@link #validDevice validDevice},
814    * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
815    * {@link #validFragment validFragment}.
816    *
817    * @see org.eclipse.core.runtime.Platform#resolve
818    */

819   public static URI createPlatformResourceURI(String JavaDoc pathName, boolean encode)
820   {
821     if (File.separatorChar != SEGMENT_SEPARATOR)
822     {
823       pathName = pathName.replace(File.separatorChar, SEGMENT_SEPARATOR);
824     }
825
826     if (encode)
827     {
828       pathName = encode(pathName, PATH_CHAR_HI, PATH_CHAR_LO, false);
829     }
830     URI result = createURI((pathName.charAt(0) == SEGMENT_SEPARATOR ? "platform:/resource" : "platform:/resource/") + pathName);
831     return result;
832   }
833   
834   // Private constructor for use of static factory methods.
835
private URI(boolean hierarchical, String JavaDoc scheme, String JavaDoc authority,
836               String JavaDoc device, boolean absolutePath, String JavaDoc[] segments,
837               String JavaDoc query, String JavaDoc fragment)
838   {
839     int hashCode = 0;
840     //boolean iri = false;
841

842     if (hierarchical)
843     {
844       ++hashCode;
845     }
846     if (absolutePath)
847     {
848       hashCode += 2;
849     }
850     if (scheme != null)
851     {
852       hashCode ^= scheme.toLowerCase().hashCode();
853     }
854     if (authority != null)
855     {
856       hashCode ^= authority.hashCode();
857       //iri = iri || containsNonASCII(authority);
858
}
859     if (device != null)
860     {
861       hashCode ^= device.hashCode();
862       //iri = iri || containsNonASCII(device);
863
}
864     if (query != null)
865     {
866       hashCode ^= query.hashCode();
867       //iri = iri || containsNonASCII(query);
868
}
869     if (fragment != null)
870     {
871       hashCode ^= fragment.hashCode();
872       //iri = iri || containsNonASCII(fragment);
873
}
874
875     for (int i = 0, len = segments.length; i < len; i++)
876     {
877       hashCode ^= segments[i].hashCode();
878       //iri = iri || containsNonASCII(segments[i]);
879
}
880
881     this.hashCode = hashCode;
882     //this.iri = iri;
883
this.hierarchical = hierarchical;
884     this.scheme = scheme == null ? null : scheme.intern();
885     this.authority = authority;
886     this.device = device;
887     this.absolutePath = absolutePath;
888     this.segments = segments;
889     this.query = query;
890     this.fragment = fragment;
891   }
892   
893   // Validates all of the URI components. Factory methods should call this
894
// before using the constructor, though they must ensure that the
895
// inter-component requirements described in their own Javadocs are all
896
// satisfied, themselves. If a new URI is being constructed out of
897
// an existing URI, this need not be called. Instead, just the new
898
// components may be validated individually.
899
private static void validateURI(boolean hierarchical, String JavaDoc scheme,
900                                     String JavaDoc authority, String JavaDoc device,
901                                     boolean absolutePath, String JavaDoc[] segments,
902                                     String JavaDoc query, String JavaDoc fragment)
903   {
904     if (!validScheme(scheme))
905     {
906       throw new IllegalArgumentException JavaDoc("invalid scheme: " + scheme);
907     }
908     if (!hierarchical && !validOpaquePart(authority))
909     {
910       throw new IllegalArgumentException JavaDoc("invalid opaquePart: " + authority);
911     }
912     if (hierarchical && !isArchiveScheme(scheme) && !validAuthority(authority))
913     {
914       throw new IllegalArgumentException JavaDoc("invalid authority: " + authority);
915     }
916     if (hierarchical && isArchiveScheme(scheme) && !validArchiveAuthority(authority))
917     {
918       throw new IllegalArgumentException JavaDoc("invalid authority: " + authority);
919     }
920     if (!validDevice(device))
921     {
922       throw new IllegalArgumentException JavaDoc("invalid device: " + device);
923     }
924     if (!validSegments(segments))
925     {
926       String JavaDoc s = segments == null ? "invalid segments: " + segments :
927         "invalid segment: " + firstInvalidSegment(segments);
928       throw new IllegalArgumentException JavaDoc(s);
929     }
930     if (!validQuery(query))
931     {
932       throw new IllegalArgumentException JavaDoc("invalid query: " + query);
933     }
934     if (!validFragment(fragment))
935     {
936       throw new IllegalArgumentException JavaDoc("invalid fragment: " + fragment);
937     }
938   }
939
940   // Alternate, stricter implementations of the following validation methods
941
// are provided, commented out, for possible future use...
942

943   /**
944    * Returns <code>true</code> if the specified <code>value</code> would be
945    * valid as the scheme component of a URI; <code>false</code> otherwise.
946    *
947    * <p>A valid scheme may be null or contain any characters except for the
948    * following: <code>: / ? #</code>
949    */

950   public static boolean validScheme(String JavaDoc value)
951   {
952     return value == null || !contains(value, MAJOR_SEPARATOR_HI, MAJOR_SEPARATOR_LO);
953
954   // <p>A valid scheme may be null, or consist of a single letter followed
955
// by any number of letters, numbers, and the following characters:
956
// <code>+ - .</code>
957

958     //if (value == null) return true;
959
//return value.length() != 0 &&
960
// matches(value.charAt(0), ALPHA_HI, ALPHA_LO) &&
961
// validate(value, SCHEME_CHAR_HI, SCHEME_CHAR_LO, false, false);
962
}
963
964   /**
965    * Returns <code>true</code> if the specified <code>value</code> would be
966    * valid as the opaque part component of a URI; <code>false</code>
967    * otherwise.
968    *
969    * <p>A valid opaque part must be non-null, non-empty, and not contain the
970    * <code>#</code> character. In addition, its first character must not be
971    * <code>/</code>
972    */

973   public static boolean validOpaquePart(String JavaDoc value)
974   {
975     return value != null && value.indexOf(FRAGMENT_SEPARATOR) == -1 &&
976     value.length() > 0 && value.charAt(0) != SEGMENT_SEPARATOR;
977
978   // <p>A valid opaque part must be non-null and non-empty. It may contain
979
// any allowed URI characters, but its first character may not be
980
// <code>/</code>
981

982     //return value != null && value.length() != 0 &&
983
// value.charAt(0) != SEGMENT_SEPARATOR &&
984
// validate(value, URIC_HI, URIC_LO, true, true);
985
}
986
987   /**
988    * Returns <code>true</code> if the specified <code>value</code> would be
989    * valid as the authority component of a URI; <code>false</code> otherwise.
990    *
991    * <p>A valid authority may be null or contain any characters except for
992    * the following: <code>/ ? #</code>
993    */

994   public static boolean validAuthority(String JavaDoc value)
995   {
996     return value == null || !contains(value, SEGMENT_END_HI, SEGMENT_END_LO);
997
998   // A valid authority may be null or contain any allowed URI characters except
999
// for the following: <code>/ ?</code>
1000

1001    //return value == null || validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true);
1002
}
1003
1004  /**
1005   * Returns <code>true</code> if the specified <code>value</code> would be
1006   * valid as the authority component of an <a
1007   * HREF="#archive_explanation">archive URI</a>; <code>false</code>
1008   * otherwise.
1009   *
1010   * <p>To be valid, the authority, itself, must be a URI with no fragment,
1011   * followed by the character <code>!</code>.
1012   */

1013  public static boolean validArchiveAuthority(String JavaDoc value)
1014  {
1015    if (value != null && value.length() > 0 &&
1016        value.charAt(value.length() - 1) == ARCHIVE_IDENTIFIER)
1017    {
1018      try
1019      {
1020        URI archiveURI = createURI(value.substring(0, value.length() - 1));
1021        return !archiveURI.hasFragment();
1022      }
1023      catch (IllegalArgumentException JavaDoc e)
1024      {
1025      }
1026    }
1027    return false;
1028  }
1029
1030  /**
1031   * Tests whether the specificed <code>value</code> would be valid as the
1032   * authority component of an <a HREF="#archive_explanation">archive
1033   * URI</a>. This method has been replaced by {@link #validArchiveAuthority
1034   * validArchiveAuthority} since the same form of URI is now supported
1035   * for schemes other than "jar". This now simply calls that method.
1036   *
1037   * @deprecated As of EMF 2.0, replaced by {@link #validArchiveAuthority
1038   * validArchiveAuthority}.
1039   */

1040  public static boolean validJarAuthority(String JavaDoc value)
1041  {
1042    return validArchiveAuthority(value);
1043  }
1044
1045  /**
1046   * Returns <code>true</code> if the specified <code>value</code> would be
1047   * valid as the device component of a URI; <code>false</code> otherwise.
1048   *
1049   * <p>A valid device may be null or non-empty, containing any characters
1050   * except for the following: <code>/ ? #</code> In addition, its last
1051   * character must be <code>:</code>
1052   */

1053  public static boolean validDevice(String JavaDoc value)
1054  {
1055    if (value == null) return true;
1056    int len = value.length();
1057    return len > 0 && value.charAt(len - 1) == DEVICE_IDENTIFIER &&
1058      !contains(value, SEGMENT_END_HI, SEGMENT_END_LO);
1059
1060  // <p>A valid device may be null or non-empty, containing any allowed URI
1061
// characters except for the following: <code>/ ?</code> In addition, its
1062
// last character must be <code>:</code>
1063

1064    //if (value == null) return true;
1065
//int len = value.length();
1066
//return len > 0 && validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true) &&
1067
// value.charAt(len - 1) == DEVICE_IDENTIFIER;
1068
}
1069
1070  /**
1071   * Returns <code>true</code> if the specified <code>value</code> would be
1072   * a valid path segment of a URI; <code>false</code> otherwise.
1073   *
1074   * <p>A valid path segment must be non-null and not contain any of the
1075   * following characters: <code>/ ? #</code>
1076   */

1077  public static boolean validSegment(String JavaDoc value)
1078  {
1079    return value != null && !contains(value, SEGMENT_END_HI, SEGMENT_END_LO);
1080
1081  // <p>A valid path segment must be non-null and may contain any allowed URI
1082
// characters except for the following: <code>/ ?</code>
1083

1084    //return value != null && validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true);
1085
}
1086
1087  /**
1088   * Returns <code>true</code> if the specified <code>value</code> would be
1089   * a valid path segment array of a URI; <code>false</code> otherwise.
1090   *
1091   * <p>A valid path segment array must be non-null and contain only path
1092   * segements that are valid according to {@link #validSegment validSegment}.
1093   */

1094  public static boolean validSegments(String JavaDoc[] value)
1095  {
1096    if (value == null) return false;
1097    for (int i = 0, len = value.length; i < len; i++)
1098    {
1099      if (!validSegment(value[i])) return false;
1100    }
1101    return true;
1102  }
1103
1104  // Returns null if the specicied value is null or would be a valid path
1105
// segment array of a URI; otherwise, the value of the first invalid
1106
// segment.
1107
private static String JavaDoc firstInvalidSegment(String JavaDoc[] value)
1108  {
1109    if (value == null) return null;
1110    for (int i = 0, len = value.length; i < len; i++)
1111    {
1112      if (!validSegment(value[i])) return value[i];
1113    }
1114    return null;
1115  }
1116
1117  /**
1118   * Returns <code>true</code> if the specified <code>value</code> would be
1119   * valid as the query component of a URI; <code>false</code> otherwise.
1120   *
1121   * <p>A valid query may be null or contain any characters except for
1122   * <code>#</code>
1123   */

1124  public static boolean validQuery(String JavaDoc value)
1125  {
1126    return value == null || value.indexOf(FRAGMENT_SEPARATOR) == -1;
1127
1128  // <p>A valid query may be null or contain any allowed URI characters.
1129

1130    //return value == null || validate(value, URIC_HI, URIC_LO, true, true);
1131
}
1132
1133  /**
1134   * Returns <code>true</code> if the specified <code>value</code> would be
1135   * valid as the fragment component of a URI; <code>false</code> otherwise.
1136   *
1137   * <p>A fragment is taken to be unconditionally valid.
1138   */

1139  public static boolean validFragment(String JavaDoc value)
1140  {
1141    return true;
1142
1143  // <p>A valid fragment may be null or contain any allowed URI characters.
1144

1145    //return value == null || validate(value, URIC_HI, URIC_LO, true, true);
1146
}
1147
1148  // Searches the specified string for any characters in the set represnted
1149
// by the 128-bit bitmask. Returns true if any occur, or false otherwise.
1150
private static boolean contains(String JavaDoc s, long highBitmask, long lowBitmask)
1151  {
1152    for (int i = 0, len = s.length(); i < len; i++)
1153    {
1154      if (matches(s.charAt(i), highBitmask, lowBitmask)) return true;
1155    }
1156    return false;
1157  }
1158
1159  // Tests the non-null string value to see if it contains only ASCII
1160
// characters in the set represented by the specified 128-bit bitmask,
1161
// as well as, optionally, non-ASCII characters 0xA0 and above, and,
1162
// also optionally, escape sequences of % followed by two hex digits.
1163
// This method is used for the new, strict URI validation that is not
1164
// not currently in place.
1165
/*
1166  private static boolean validate(String value, long highBitmask, long lowBitmask,
1167                                     boolean allowNonASCII, boolean allowEscaped)
1168  {
1169    for (int i = 0, len = value.length(); i < len; i++)
1170    {
1171      char c = value.charAt(i);
1172
1173      if (matches(c, highBitmask, lowBitmask)) continue;
1174      if (allowNonASCII && c >= 160) continue;
1175      if (allowEscaped && isEscaped(value, i))
1176      {
1177        i += 2;
1178        continue;
1179      }
1180      return false;
1181    }
1182    return true;
1183  }
1184*/

1185
1186  /**
1187   * Returns <code>true</code> if this is a relative URI, or
1188   * <code>false</code> if it is an absolute URI.
1189   */

1190  public boolean isRelative()
1191  {
1192    return scheme == null;
1193  }
1194
1195  /**
1196   * Returns <code>true</code> if this a a hierarchical URI, or
1197   * <code>false</code> if it is of the generic form.
1198   */

1199  public boolean isHierarchical()
1200  {
1201    return hierarchical;
1202  }
1203
1204  /**
1205   * Returns <code>true</code> if this is a hierarcical URI with an authority
1206   * component; <code>false</code> otherwise.
1207   */

1208  public boolean hasAuthority()
1209  {
1210    return hierarchical && authority != null;
1211  }
1212
1213  /**
1214   * Returns <code>true</code> if this is a non-hierarchical URI with an
1215   * opaque part component; <code>false</code> otherwise.
1216   */

1217  public boolean hasOpaquePart()
1218  {
1219    // note: hierarchical -> authority != null
1220
return !hierarchical;
1221  }
1222
1223  /**
1224   * Returns <code>true</code> if this is a hierarchical URI with a device
1225   * component; <code>false</code> otherwise.
1226   */

1227  public boolean hasDevice()
1228  {
1229    // note: device != null -> hierarchical
1230
return device != null;
1231  }
1232
1233  /**
1234   * Returns <code>true</code> if this is a hierarchical URI with an
1235   * absolute or relative path; <code>false</code> otherwise.
1236   */

1237  public boolean hasPath()
1238  {
1239    // note: (absolutePath || authority == null) -> hierarchical
1240
// (authority == null && device == null && !absolutePath) -> scheme == null
1241
return absolutePath || (authority == null && device == null);
1242  }
1243
1244  /**
1245   * Returns <code>true</code> if this is a hierarchical URI with an
1246   * absolute path, or <code>false</code> if it is non-hierarchical, has no
1247   * path, or has a relative path.
1248   */

1249  public boolean hasAbsolutePath()
1250  {
1251    // note: absolutePath -> hierarchical
1252
return absolutePath;
1253  }
1254
1255  /**
1256   * Returns <code>true</code> if this is a hierarchical URI with a relative
1257   * path, or <code>false</code> if it is non-hierarchical, has no path, or
1258   * has an absolute path.
1259   */

1260  public boolean hasRelativePath()
1261  {
1262    // note: authority == null -> hierarchical
1263
// (authority == null && device == null && !absolutePath) -> scheme == null
1264
return authority == null && device == null && !absolutePath;
1265  }
1266
1267  /**
1268   * Returns <code>true</code> if this is a hierarchical URI with an empty
1269   * relative path; <code>false</code> otherwise.
1270   *
1271   * <p>Note that <code>!hasEmpty()</code> does <em>not</em> imply that this
1272   * URI has any path segments; however, <code>hasRelativePath &&
1273   * !hasEmptyPath()</code> does.
1274   */

1275  public boolean hasEmptyPath()
1276  {
1277    // note: authority == null -> hierarchical
1278
// (authority == null && device == null && !absolutePath) -> scheme == null
1279
return authority == null && device == null && !absolutePath &&
1280      segments.length == 0;
1281  }
1282
1283  /**
1284   * Returns <code>true</code> if this is a hierarchical URI with a query
1285   * component; <code>false</code> otherwise.
1286   */

1287  public boolean hasQuery()
1288  {
1289    // note: query != null -> hierarchical
1290
return query != null;
1291  }
1292
1293  /**
1294   * Returns <code>true</code> if this URI has a fragment component;
1295   * <code>false</code> otherwise.
1296   */

1297  public boolean hasFragment()
1298  {
1299    return fragment != null;
1300  }
1301
1302  /**
1303   * Returns <code>true</code> if this is a current document reference; that
1304   * is, if it is a relative hierarchical URI with no authority, device or
1305   * query components, and no path segments; <code>false</code> is returned
1306   * otherwise.
1307   */

1308  public boolean isCurrentDocumentReference()
1309  {
1310    // note: authority == null -> hierarchical
1311
// (authority == null && device == null && !absolutePath) -> scheme == null
1312
return authority == null && device == null && !absolutePath &&
1313      segments.length == 0 && query == null;
1314  }
1315
1316  /**
1317   * Returns <code>true</code> if this is a {@link
1318   * #isCurrentDocumentReference() current document reference} with no
1319   * fragment component; <code>false</code> otherwise.
1320   *
1321   * @see #isCurrentDocumentReference()
1322   */

1323  public boolean isEmpty()
1324  {
1325    // note: authority == null -> hierarchical
1326
// (authority == null && device == null && !absolutePath) -> scheme == null
1327
return authority == null && device == null && !absolutePath &&
1328      segments.length == 0 && query == null && fragment == null;
1329  }
1330
1331  /**
1332   * Returns <code>true</code> if this is a hierarchical URI that may refer
1333   * directly to a locally accessible file. This is considered to be the
1334   * case for a file-scheme absolute URI, or for a relative URI with no query;
1335   * <code>false</code> is returned otherwise.
1336   */

1337  public boolean isFile()
1338  {
1339    return isHierarchical() &&
1340      ((isRelative() && !hasQuery()) || SCHEME_FILE.equalsIgnoreCase(scheme));
1341  }
1342
1343  // Returns true if this is an archive URI. If so, we should expect that
1344
// it is also hierarchical, with an authority (consisting of an absolute
1345
// URI followed by "!"), no device, and an absolute path.
1346
private boolean isArchive()
1347  {
1348    return isArchiveScheme(scheme);
1349  }
1350
1351  /**
1352   * Returns <code>true</code> if the specified <code>value</code> would be
1353   * valid as the scheme of an <a
1354   * HREF="#archive_explanation">archive URI</a>; <code>false</code>
1355   * otherwise.
1356   */

1357  public static boolean isArchiveScheme(String JavaDoc value)
1358  {
1359    // Returns true if the given value is an archive scheme, as defined by
1360
// the org.eclipse.emf.common.util.URI.archiveSchemes system property.
1361
// By default, "jar", "zip", and "archive" are considered archives.
1362
return value != null && archiveSchemes.contains(value.toLowerCase());
1363  }
1364  
1365  /**
1366   * Returns the hash code.
1367   */

1368  public int hashCode()
1369  {
1370    return hashCode;
1371  }
1372
1373  /**
1374   * Returns <code>true</code> if <code>obj</code> is an instance of
1375   * <code>URI</code> equal to this one; <code>false</code> otherwise.
1376   *
1377   * <p>Equality is determined strictly by comparing components, not by
1378   * attempting to interpret what resource is being identified. The
1379   * comparison of schemes is case-insensitive.
1380   */

1381  public boolean equals(Object JavaDoc obj)
1382  {
1383    if (this == obj) return true;
1384    if (!(obj instanceof URI)) return false;
1385    URI uri = (URI) obj;
1386
1387    return hashCode == uri.hashCode() &&
1388      hierarchical == uri.isHierarchical() &&
1389      absolutePath == uri.hasAbsolutePath() &&
1390      equals(scheme, uri.scheme(), true) &&
1391      equals(authority, hierarchical ? uri.authority() : uri.opaquePart()) &&
1392      equals(device, uri.device()) &&
1393      equals(query, uri.query()) &&
1394      equals(fragment, uri.fragment()) &&
1395      segmentsEqual(uri);
1396  }
1397
1398  // Tests whether this URI's path segment array is equal to that of the
1399
// given uri.
1400
private boolean segmentsEqual(URI uri)
1401  {
1402    if (segments.length != uri.segmentCount()) return false;
1403    for (int i = 0, len = segments.length; i < len; i++)
1404    {
1405      if (!segments[i].equals(uri.segment(i))) return false;
1406    }
1407    return true;
1408  }
1409
1410  // Tests two objects for equality, tolerating nulls; null is considered
1411
// to be a valid value that is only equal to itself.
1412
private static boolean equals(Object JavaDoc o1, Object JavaDoc o2)
1413  {
1414    return o1 == null ? o2 == null : o1.equals(o2);
1415  }
1416
1417  // Tests two strings for equality, tolerating nulls and optionally
1418
// ignoring case.
1419
private static boolean equals(String JavaDoc s1, String JavaDoc s2, boolean ignoreCase)
1420  {
1421    return s1 == null ? s2 == null :
1422      ignoreCase ? s1.equalsIgnoreCase(s2) : s1.equals(s2);
1423  }
1424
1425  /**
1426   * If this is an absolute URI, returns the scheme component;
1427   * <code>null</code> otherwise.
1428   */

1429  public String JavaDoc scheme()
1430  {
1431    return scheme;
1432  }
1433
1434  /**
1435   * If this is a non-hierarchical URI, returns the opaque part component;
1436   * <code>null</code> otherwise.
1437   */

1438  public String JavaDoc opaquePart()
1439  {
1440    return isHierarchical() ? null : authority;
1441  }
1442
1443  /**
1444   * If this is a hierarchical URI with an authority component, returns it;
1445   * <code>null</code> otherwise.
1446   */

1447  public String JavaDoc authority()
1448  {
1449    return isHierarchical() ? authority : null;
1450  }
1451
1452  /**
1453   * If this is a hierarchical URI with an authority component that has a
1454   * user info portion, returns it; <code>null</code> otherwise.
1455   */

1456  public String JavaDoc userInfo()
1457  {
1458    if (!hasAuthority()) return null;
1459   
1460    int i = authority.indexOf(USER_INFO_SEPARATOR);
1461    return i < 0 ? null : authority.substring(0, i);
1462  }
1463
1464  /**
1465   * If this is a hierarchical URI with an authority component that has a
1466   * host portion, returns it; <code>null</code> otherwise.
1467   */

1468  public String JavaDoc host()
1469  {
1470    if (!hasAuthority()) return null;
1471    
1472    int i = authority.indexOf(USER_INFO_SEPARATOR);
1473    int j = authority.indexOf(PORT_SEPARATOR);
1474    return j < 0 ? authority.substring(i + 1) : authority.substring(i + 1, j);
1475  }
1476
1477  /**
1478   * If this is a hierarchical URI with an authority component that has a
1479   * port portion, returns it; <code>null</code> otherwise.
1480   */

1481  public String JavaDoc port()
1482  {
1483    if (!hasAuthority()) return null;
1484
1485    int i = authority.indexOf(PORT_SEPARATOR);
1486    return i < 0 ? null : authority.substring(i + 1);
1487  }
1488
1489  /**
1490   * If this is a hierarchical URI with a device component, returns it;
1491   * <code>null</code> otherwise.
1492   */

1493  public String JavaDoc device()
1494  {
1495    return device;
1496  }
1497
1498  /**
1499   * If this is a hierarchical URI with a path, returns an array containing
1500   * the segments of the path; an empty array otherwise. The leading
1501   * separator in an absolute path is not represented in this array, but a
1502   * trailing separator is represented by an empty-string segment as the
1503   * final element.
1504   */

1505  public String JavaDoc[] segments()
1506  {
1507    return (String JavaDoc[])segments.clone();
1508  }
1509
1510  /**
1511   * Returns an unmodifiable list containing the same segments as the array
1512   * returned by {@link #segments segments}.
1513   */

1514  public List JavaDoc segmentsList()
1515  {
1516    return Collections.unmodifiableList(Arrays.asList(segments));
1517  }
1518
1519  /**
1520   * Returns the number of elements in the segment array that would be
1521   * returned by {@link #segments segments}.
1522   */

1523  public int segmentCount()
1524  {
1525    return segments.length;
1526  }
1527
1528  /**
1529   * Provides fast, indexed access to individual segments in the path
1530   * segment array.
1531   *
1532   * @exception java.lang.IndexOutOfBoundsException if <code>i < 0</code> or
1533   * <code>i >= segmentCount()</code>.
1534   */

1535  public String JavaDoc segment(int i)
1536  {
1537    return segments[i];
1538  }
1539
1540  /**
1541   * Returns the last segment in the segment array, or <code>null</code>.
1542   */

1543  public String JavaDoc lastSegment()
1544  {
1545    int len = segments.length;
1546    if (len == 0) return null;
1547    return segments[len - 1];
1548  }
1549
1550  /**
1551   * If this is a hierarchical URI with a path, returns a string
1552   * representation of the path; <code>null</code> otherwise. The path
1553   * consists of a leading segment separator character (a slash), if the
1554   * path is absolute, followed by the slash-separated path segments. If
1555   * this URI has a separate <a HREF="#device_explanation">device
1556   * component</a>, it is <em>not</em> included in the path.
1557   */

1558  public String JavaDoc path()
1559  {
1560    if (!hasPath()) return null;
1561
1562    StringBuffer JavaDoc result = new StringBuffer JavaDoc();
1563    if (hasAbsolutePath()) result.append(SEGMENT_SEPARATOR);
1564
1565    for (int i = 0, len = segments.length; i < len; i++)
1566    {
1567      if (i != 0) result.append(SEGMENT_SEPARATOR);
1568      result.append(segments[i]);
1569    }
1570    return result.toString();
1571  }
1572
1573  /**
1574   * If this is a hierarchical URI with a path, returns a string
1575   * representation of the path, including the authority and the
1576   * <a HREF="#device_explanation">device component</a>;
1577   * <code>null</code> otherwise.
1578   *
1579   * <p>If there is no authority, the format of this string is:
1580   * <pre>
1581   * device/pathSegment1/pathSegment2...</pre>
1582   *
1583   * <p>If there is an authority, it is:
1584   * <pre>
1585   * //authority/device/pathSegment1/pathSegment2...</pre>
1586   *
1587   * <p>For an <a HREF="#archive_explanation">archive URI</a>, it's just:
1588   * <pre>
1589   * authority/pathSegment1/pathSegment2...</pre>
1590   */

1591  public String JavaDoc devicePath()
1592  {
1593    if (!hasPath()) return null;
1594
1595    StringBuffer JavaDoc result = new StringBuffer JavaDoc();
1596
1597    if (hasAuthority())
1598    {
1599      if (!isArchive()) result.append(AUTHORITY_SEPARATOR);
1600      result.append(authority);
1601
1602      if (hasDevice()) result.append(SEGMENT_SEPARATOR);
1603    }
1604
1605    if (hasDevice()) result.append(device);
1606    if (hasAbsolutePath()) result.append(SEGMENT_SEPARATOR);
1607
1608    for (int i = 0, len = segments.length; i < len; i++)
1609    {
1610      if (i != 0) result.append(SEGMENT_SEPARATOR);
1611      result.append(segments[i]);
1612    }
1613    return result.toString();
1614  }
1615
1616  /**
1617   * If this is a hierarchical URI with a query component, returns it;
1618   * <code>null</code> otherwise.
1619   */

1620  public String JavaDoc query()
1621  {
1622    return query;
1623  }
1624
1625
1626  /**
1627   * Returns the URI formed from this URI and the given query.
1628   *
1629   * @exception java.lang.IllegalArgumentException if
1630   * <code>query</code> is not a valid query (portion) according
1631   * to {@link #validQuery validQuery}.
1632   */

1633  public URI appendQuery(String JavaDoc query)
1634  {
1635    if (!validQuery(query))
1636    {
1637      throw new IllegalArgumentException JavaDoc(
1638        "invalid query portion: " + query);
1639    }
1640    return new URI(hierarchical, scheme, authority, device, absolutePath, segments, query, fragment);
1641  }
1642
1643  /**
1644   * If this URI has a non-null {@link #query query}, returns the URI
1645   * formed by removing it; this URI unchanged, otherwise.
1646   */

1647  public URI trimQuery()
1648  {
1649    if (query == null)
1650    {
1651      return this;
1652    }
1653    else
1654    {
1655      return new URI(hierarchical, scheme, authority, device, absolutePath, segments, null, fragment);
1656    }
1657  }
1658
1659  /**
1660   * If this URI has a fragment component, returns it; <code>null</code>
1661   * otherwise.
1662   */

1663  public String JavaDoc fragment()
1664  {
1665    return fragment;
1666  }
1667
1668  /**
1669   * Returns the URI formed from this URI and the given fragment.
1670   *
1671   * @exception java.lang.IllegalArgumentException if
1672   * <code>fragment</code> is not a valid fragment (portion) according
1673   * to {@link #validFragment validFragment}.
1674   */

1675  public URI appendFragment(String JavaDoc fragment)
1676  {
1677    if (!validFragment(fragment))
1678    {
1679      throw new IllegalArgumentException JavaDoc(
1680        "invalid fragment portion: " + fragment);
1681    }
1682    URI result = new URI(hierarchical, scheme, authority, device, absolutePath, segments, query, fragment);
1683
1684    if (!hasFragment())
1685    {
1686      result.cachedTrimFragment = this;
1687    }
1688    return result;
1689  }
1690
1691  /**
1692   * If this URI has a non-null {@link #fragment fragment}, returns the URI
1693   * formed by removing it; this URI unchanged, otherwise.
1694   */

1695  public URI trimFragment()
1696  {
1697    if (fragment == null)
1698    {
1699      return this;
1700    }
1701    else if (cachedTrimFragment == null)
1702    {
1703      cachedTrimFragment = new URI(hierarchical, scheme, authority, device, absolutePath, segments, query, null);
1704    }
1705
1706    return cachedTrimFragment;
1707  }
1708
1709  /**
1710   * Resolves this URI reference against a <code>base</code> absolute
1711   * hierarchical URI, returning the resulting absolute URI. If already
1712   * absolute, the URI itself is returned. URI resolution is described in
1713   * detail in section 5.2 of <a HREF="http://www.ietf.org/rfc/rfc2396.txt">RFC
1714   * 2396</a>, "Resolving Relative References to Absolute Form."
1715   *
1716   * <p>During resolution, empty segments, self references ("."), and parent
1717   * references ("..") are interpreted, so that they can be removed from the
1718   * path. Step 6(g) gives a choice of how to handle the case where parent
1719   * references point to a path above the root: the offending segments can
1720   * be preserved or discarded. This method preserves them. To have them
1721   * discarded, please use the two-parameter form of {@link
1722   * #resolve(URI, boolean) resolve}.
1723   *
1724   * @exception java.lang.IllegalArgumentException if <code>base</code> is
1725   * non-hierarchical or is relative.
1726   */

1727  public URI resolve(URI base)
1728  {
1729    return resolve(base, true);
1730  }
1731
1732  /**
1733   * Resolves this URI reference against a <code>base</code> absolute
1734   * hierarchical URI, returning the resulting absolute URI. If already
1735   * absolute, the URI itself is returned. URI resolution is described in
1736   * detail in section 5.2 of <a HREF="http://www.ietf.org/rfc/rfc2396.txt">RFC
1737   * 2396</a>, "Resolving Relative References to Absolute Form."
1738   *
1739   * <p>During resultion, empty segments, self references ("."), and parent
1740   * references ("..") are interpreted, so that they can be removed from the
1741   * path. Step 6(g) gives a choice of how to handle the case where parent
1742   * references point to a path above the root: the offending segments can
1743   * be preserved or discarded. This method can do either.
1744   *
1745   * @param preserveRootParents <code>true</code> if segments refering to the
1746   * parent of the root path are to be preserved; <code>false</code> if they
1747   * are to be discarded.
1748   *
1749   * @exception java.lang.IllegalArgumentException if <code>base</code> is
1750   * non-hierarchical or is relative.
1751   */

1752  public URI resolve(URI base, boolean preserveRootParents)
1753  {
1754    if (!base.isHierarchical() || base.isRelative())
1755    {
1756      throw new IllegalArgumentException JavaDoc(
1757        "resolve against non-hierarchical or relative base");
1758    }
1759
1760    // an absolute URI needs no resolving
1761
if (!isRelative()) return this;
1762
1763    // note: isRelative() -> hierarchical
1764

1765    String JavaDoc newAuthority = authority;
1766    String JavaDoc newDevice = device;
1767    boolean newAbsolutePath = absolutePath;
1768    String JavaDoc[] newSegments = segments;
1769    String JavaDoc newQuery = query;
1770    // note: it's okay for two URIs to share a segments array, since
1771
// neither will ever modify it
1772

1773    if (authority == null)
1774    {
1775      // no authority: use base's
1776
newAuthority = base.authority();
1777
1778      if (device == null)
1779      {
1780        // no device: use base's
1781
newDevice = base.device();
1782
1783        if (hasEmptyPath() && query == null)
1784        {
1785          // current document reference: use base path and query
1786
newAbsolutePath = base.hasAbsolutePath();
1787          newSegments = base.segments();
1788          newQuery = base.query();
1789        }
1790        else if (hasRelativePath())
1791        {
1792          // relative path: merge with base and keep query (note: if the
1793
// base has no path and this a non-empty relative path, there is
1794
// an implied root in the resulting path)
1795
newAbsolutePath = base.hasAbsolutePath() || !hasEmptyPath();
1796          newSegments = newAbsolutePath ? mergePath(base, preserveRootParents)
1797            : NO_SEGMENTS;
1798        }
1799        // else absolute path: keep it and query
1800
}
1801      // else keep device, path, and query
1802
}
1803    // else keep authority, device, path, and query
1804

1805    // always keep fragment, even if null, and use scheme from base;
1806
// no validation needed since all components are from existing URIs
1807
return new URI(true, base.scheme(), newAuthority, newDevice,
1808                   newAbsolutePath, newSegments, newQuery, fragment);
1809  }
1810
1811  // Merges this URI's relative path with the base non-relative path. If
1812
// base has no path, treat it as the root absolute path, unless this has
1813
// no path either.
1814
private String JavaDoc[] mergePath(URI base, boolean preserveRootParents)
1815  {
1816    if (base.hasRelativePath())
1817    {
1818      throw new IllegalArgumentException JavaDoc("merge against relative path");
1819    }
1820    if (!hasRelativePath())
1821    {
1822      throw new IllegalStateException JavaDoc("merge non-relative path");
1823    }
1824
1825    int baseSegmentCount = base.segmentCount();
1826    int segmentCount = segments.length;
1827    String JavaDoc[] stack = new String JavaDoc[baseSegmentCount + segmentCount];
1828    int sp = 0;
1829
1830    // use a stack to accumulate segments of base, except for the last
1831
// (i.e. skip trailing separator and anything following it), and of
1832
// relative path
1833
for (int i = 0; i < baseSegmentCount - 1; i++)
1834    {
1835      sp = accumulate(stack, sp, base.segment(i), preserveRootParents);
1836    }
1837
1838    for (int i = 0; i < segmentCount; i++)
1839    {
1840      sp = accumulate(stack, sp, segments[i], preserveRootParents);
1841    }
1842
1843    // if the relative path is empty or ends in an empty segment, a parent
1844
// reference, or a self referenfce, add a trailing separator to a
1845
// non-empty path
1846
if (sp > 0 && (segmentCount == 0 ||
1847                    SEGMENT_EMPTY.equals(segments[segmentCount - 1]) ||
1848                    SEGMENT_PARENT.equals(segments[segmentCount - 1]) ||
1849                    SEGMENT_SELF.equals(segments[segmentCount - 1])))
1850    {
1851      stack[sp++] = SEGMENT_EMPTY;
1852    }
1853
1854    // return a correctly sized result
1855
String JavaDoc[] result = new String JavaDoc[sp];
1856    System.arraycopy(stack, 0, result, 0, sp);
1857    return result;
1858  }
1859
1860  // Adds a segment to a stack, skipping empty segments and self references,
1861
// and interpreting parent references.
1862
private static int accumulate(String JavaDoc[] stack, int sp, String JavaDoc segment,
1863                                boolean preserveRootParents)
1864  {
1865    if (SEGMENT_PARENT.equals(segment))
1866    {
1867      if (sp == 0)
1868      {
1869        // special care must be taken for a root's parent reference: it is
1870
// either ignored or the symbolic reference itself is pushed
1871
if (preserveRootParents) stack[sp++] = segment;
1872      }
1873      else
1874      {
1875        // unless we're already accumulating root parent references,
1876
// parent references simply pop the last segment descended
1877
if (SEGMENT_PARENT.equals(stack[sp - 1])) stack[sp++] = segment;
1878        else sp--;
1879      }
1880    }
1881    else if (!SEGMENT_EMPTY.equals(segment) && !SEGMENT_SELF.equals(segment))
1882    {
1883      // skip empty segments and self references; push everything else
1884
stack[sp++] = segment;
1885    }
1886    return sp;
1887  }
1888
1889  /**
1890   * Finds the shortest relative or, if necessary, the absolute URI that,
1891   * when resolved against the given <code>base</code> absolute hierarchical
1892   * URI using {@link #resolve(URI) resolve}, will yield this absolute URI.
1893   *
1894   * @exception java.lang.IllegalArgumentException if <code>base</code> is
1895   * non-hierarchical or is relative.
1896   * @exception java.lang.IllegalStateException if <code>this</code> is
1897   * relative.
1898   */

1899  public URI deresolve(URI base)
1900  {
1901    return deresolve(base, true, false, true);
1902  }
1903
1904  /**
1905   * Finds an absolute URI that, when resolved against the given
1906   * <code>base</code> absolute hierarchical URI using {@link
1907   * #resolve(URI, boolean) resolve}, will yield this absolute URI.
1908   *
1909   * @param preserveRootParents the boolean argument to <code>resolve(URI,
1910   * boolean)</code> for which the returned URI should resolve to this URI.
1911   * @param anyRelPath if <code>true</code>, the returned URI's path (if
1912   * any) will be relative, if possible. If <code>false</code>, the form of
1913   * the result's path will depend upon the next parameter.
1914   * @param shorterRelPath if <code>anyRelPath</code> is <code>false</code>
1915   * and this parameter is <code>true</code>, the returned URI's path (if
1916   * any) will be relative, if one can be found that is no longer (by number
1917   * of segments) than the absolute path. If both <code>anyRelPath</code>
1918   * and this parameter are <code>false</code>, it will be absolute.
1919   *
1920   * @exception java.lang.IllegalArgumentException if <code>base</code> is
1921   * non-hierarchical or is relative.
1922   * @exception java.lang.IllegalStateException if <code>this</code> is
1923   * relative.
1924   */

1925  public URI deresolve(URI base, boolean preserveRootParents,
1926                       boolean anyRelPath, boolean shorterRelPath)
1927  {
1928    if (!base.isHierarchical() || base.isRelative())
1929    {
1930      throw new IllegalArgumentException JavaDoc(
1931        "deresolve against non-hierarchical or relative base");
1932    }
1933    if (isRelative())
1934    {
1935      throw new IllegalStateException JavaDoc("deresolve relative URI");
1936    }
1937
1938    // note: these assertions imply that neither this nor the base URI has a
1939
// relative path; thus, both have either an absolute path or no path
1940

1941    // different scheme: need complete, absolute URI
1942
if (!scheme.equalsIgnoreCase(base.scheme())) return this;
1943
1944    // since base must be hierarchical, and since a non-hierarchical URI
1945
// must have both scheme and opaque part, the complete absolute URI is
1946
// needed to resolve to a non-hierarchical URI
1947
if (!isHierarchical()) return this;
1948
1949    String JavaDoc newAuthority = authority;
1950    String JavaDoc newDevice = device;
1951    boolean newAbsolutePath = absolutePath;
1952    String JavaDoc[] newSegments = segments;
1953    String JavaDoc newQuery = query;
1954
1955    if (equals(authority, base.authority()) &&
1956        (hasDevice() || hasPath() || (!base.hasDevice() && !base.hasPath())))
1957    {
1958      // matching authorities and no device or path removal
1959
newAuthority = null;
1960
1961      if (equals(device, base.device()) && (hasPath() || !base.hasPath()))
1962      {
1963        // matching devices and no path removal
1964
newDevice = null;
1965
1966        // exception if (!hasPath() && base.hasPath())
1967

1968        if (!anyRelPath && !shorterRelPath)
1969        {
1970          // user rejects a relative path: keep absolute or no path
1971
}
1972        else if (hasPath() == base.hasPath() && segmentsEqual(base) &&
1973                 equals(query, base.query()))
1974        {
1975          // current document reference: keep no path or query
1976
newAbsolutePath = false;
1977          newSegments = NO_SEGMENTS;
1978          newQuery = null;
1979        }
1980        else if (!hasPath() && !base.hasPath())
1981        {
1982          // no paths: keep query only
1983
newAbsolutePath = false;
1984          newSegments = NO_SEGMENTS;
1985        }
1986        // exception if (!hasAbsolutePath())
1987
else if (hasCollapsableSegments(preserveRootParents))
1988        {
1989          // path form demands an absolute path: keep it and query
1990
}
1991        else
1992        {
1993          // keep query and select relative or absolute path based on length
1994
String JavaDoc[] rel = findRelativePath(base, preserveRootParents);
1995          if (anyRelPath || segments.length > rel.length)
1996          {
1997            // user demands a relative path or the absolute path is longer
1998
newAbsolutePath = false;
1999            newSegments = rel;
2000          }
2001          // else keep shorter absolute path
2002
}
2003      }
2004      // else keep device, path, and query
2005
}
2006    // else keep authority, device, path, and query
2007

2008    // always include fragment, even if null;
2009
// no validation needed since all components are from existing URIs
2010
return new URI(true, null, newAuthority, newDevice, newAbsolutePath,
2011                   newSegments, newQuery, fragment);
2012  }
2013
2014  // Returns true if the non-relative path includes segments that would be
2015
// collapsed when resolving; false otherwise. If preserveRootParents is
2016
// true, collapsable segments include any empty segments, except for the
2017
// last segment, as well as and parent and self references. If
2018
// preserveRootsParents is false, parent references are not collapsable if
2019
// they are the first segment or preceeded only by other parent
2020
// references.
2021
private boolean hasCollapsableSegments(boolean preserveRootParents)
2022  {
2023    if (hasRelativePath())
2024    {
2025      throw new IllegalStateException JavaDoc("test collapsability of relative path");
2026    }
2027
2028    for (int i = 0, len = segments.length; i < len; i++)
2029    {
2030      String JavaDoc segment = segments[i];
2031      if ((i < len - 1 && SEGMENT_EMPTY.equals(segment)) ||
2032          SEGMENT_SELF.equals(segment) ||
2033          SEGMENT_PARENT.equals(segment) && (
2034            !preserveRootParents || (
2035              i != 0 && !SEGMENT_PARENT.equals(segments[i - 1]))))
2036      {
2037        return true;
2038      }
2039    }
2040    return false;
2041  }
2042
2043  // Returns the shortest relative path between the the non-relative path of
2044
// the given base and this absolute path. If the base has no path, it is
2045
// treated as the root absolute path.
2046
private String JavaDoc[] findRelativePath(URI base, boolean preserveRootParents)
2047  {
2048    if (base.hasRelativePath())
2049    {
2050      throw new IllegalArgumentException JavaDoc(
2051        "find relative path against base with relative path");
2052    }
2053    if (!hasAbsolutePath())
2054    {
2055      throw new IllegalArgumentException JavaDoc(
2056        "find relative path of non-absolute path");
2057    }
2058
2059    // treat an empty base path as the root absolute path
2060
String JavaDoc[] startPath = base.collapseSegments(preserveRootParents);
2061    String JavaDoc[] endPath = segments;
2062
2063    // drop last segment from base, as in resolving
2064
int startCount = startPath.length > 0 ? startPath.length - 1 : 0;
2065    int endCount = endPath.length;
2066
2067    // index of first segment that is different between endPath and startPath
2068
int diff = 0;
2069
2070    // if endPath is shorter than startPath, the last segment of endPath may
2071
// not be compared: because startPath has been collapsed and had its
2072
// last segment removed, all preceeding segments can be considered non-
2073
// empty and followed by a separator, while the last segment of endPath
2074
// will either be non-empty and not followed by a separator, or just empty
2075
for (int count = startCount < endCount ? startCount : endCount - 1;
2076         diff < count && startPath[diff].equals(endPath[diff]); diff++);
2077
2078    int upCount = startCount - diff;
2079    int downCount = endCount - diff;
2080
2081    // a single separator, possibly preceeded by some parent reference
2082
// segments, is redundant
2083
if (downCount == 1 && SEGMENT_EMPTY.equals(endPath[endCount - 1]))
2084    {
2085      downCount = 0;
2086    }
2087
2088    // an empty path needs to be replaced by a single "." if there is no
2089
// query, to distinguish it from a current document reference
2090
if (upCount + downCount == 0)
2091    {
2092      if (query == null) return new String JavaDoc[] { SEGMENT_SELF };
2093      return NO_SEGMENTS;
2094    }
2095
2096    // return a correctly sized result
2097
String JavaDoc[] result = new String JavaDoc[upCount + downCount];
2098    Arrays.fill(result, 0, upCount, SEGMENT_PARENT);
2099    System.arraycopy(endPath, diff, result, upCount, downCount);
2100    return result;
2101  }
2102
2103  // Collapses non-ending empty segments, parent references, and self
2104
// references in a non-relative path, returning the same path that would
2105
// be produced from the base hierarchical URI as part of a resolve.
2106
String JavaDoc[] collapseSegments(boolean preserveRootParents)
2107  {
2108    if (hasRelativePath())
2109    {
2110      throw new IllegalStateException JavaDoc("collapse relative path");
2111    }
2112
2113    if (!hasCollapsableSegments(preserveRootParents)) return segments();
2114
2115    // use a stack to accumulate segments
2116
int segmentCount = segments.length;
2117    String JavaDoc[] stack = new String JavaDoc[segmentCount];
2118    int sp = 0;
2119
2120    for (int i = 0; i < segmentCount; i++)
2121    {
2122      sp = accumulate(stack, sp, segments[i], preserveRootParents);
2123    }
2124
2125    // if the path is non-empty and originally ended in an empty segment, a
2126
// parent reference, or a self reference, add a trailing separator
2127
if (sp > 0 && (SEGMENT_EMPTY.equals(segments[segmentCount - 1]) ||
2128                   SEGMENT_PARENT.equals(segments[segmentCount - 1]) ||
2129                   SEGMENT_SELF.equals(segments[segmentCount - 1])))
2130    {
2131      stack[sp++] = SEGMENT_EMPTY;
2132    }
2133
2134    // return a correctly sized result
2135
String JavaDoc[] result = new String JavaDoc[sp];
2136    System.arraycopy(stack, 0, result, 0, sp);
2137    return result;
2138  }
2139
2140  /**
2141   * Returns the string representation of this URI. For a generic,
2142   * non-hierarchical URI, this looks like:
2143   * <pre>
2144   * scheme:opaquePart#fragment</pre>
2145   *
2146   * <p>For a hierarchical URI, it looks like:
2147   * <pre>
2148   * scheme://authority/device/pathSegment1/pathSegment2...?query#fragment</pre>
2149   *
2150   * <p>For an <a HREF="#archive_explanation">archive URI</a>, it's just:
2151   * <pre>
2152   * scheme:authority/pathSegment1/pathSegment2...?query#fragment</pre>
2153   * <p>Of course, absent components and their separators will be omitted.
2154   */

2155  public String JavaDoc toString()
2156  {
2157    if (cachedToString == null)
2158    {
2159      StringBuffer JavaDoc result = new StringBuffer JavaDoc();
2160      if (!isRelative())
2161      {
2162        result.append(scheme);
2163        result.append(SCHEME_SEPARATOR);
2164      }
2165
2166      if (isHierarchical())
2167      {
2168        if (hasAuthority())
2169        {
2170          if (!isArchive()) result.append(AUTHORITY_SEPARATOR);
2171          result.append(authority);
2172        }
2173
2174        if (hasDevice())
2175        {
2176          result.append(SEGMENT_SEPARATOR);
2177          result.append(device);
2178        }
2179
2180        if (hasAbsolutePath()) result.append(SEGMENT_SEPARATOR);
2181
2182        for (int i = 0, len = segments.length; i < len; i++)
2183        {
2184          if (i != 0) result.append(SEGMENT_SEPARATOR);
2185          result.append(segments[i]);
2186        }
2187
2188        if (hasQuery())
2189        {
2190          result.append(QUERY_SEPARATOR);
2191          result.append(query);
2192        }
2193      }
2194      else
2195      {
2196        result.append(authority);
2197      }
2198
2199      if (hasFragment())
2200      {
2201        result.append(FRAGMENT_SEPARATOR);
2202        result.append(fragment);
2203      }
2204      cachedToString = result.toString();
2205    }
2206    return cachedToString;
2207  }
2208
2209  // Returns a string representation of this URI for debugging, explicitly
2210
// showing each of the components.
2211
String JavaDoc toString(boolean includeSimpleForm)
2212  {
2213    StringBuffer JavaDoc result = new StringBuffer JavaDoc();
2214    if (includeSimpleForm) result.append(toString());
2215    result.append("\n hierarchical: ");
2216    result.append(hierarchical);
2217    result.append("\n scheme: ");
2218    result.append(scheme);
2219    result.append("\n authority: ");
2220    result.append(authority);
2221    result.append("\n device: ");
2222    result.append(device);
2223    result.append("\n absolutePath: ");
2224    result.append(absolutePath);
2225    result.append("\n segments: ");
2226    if (segments.length == 0) result.append("<empty>");
2227    for (int i = 0, len = segments.length; i < len; i++)
2228    {
2229      if (i > 0) result.append("\n ");
2230      result.append(segments[i]);
2231    }
2232    result.append("\n query: ");
2233    result.append(query);
2234    result.append("\n fragment: ");
2235    result.append(fragment);
2236    return result.toString();
2237  }
2238
2239  /**
2240   * If this URI may refer directly to a locally accessible file, as
2241   * determined by {@link #isFile isFile}, {@link decode decodes} and formats
2242   * the URI as a pathname to that file; returns null otherwise.
2243   *
2244   * <p>If there is no authority, the format of this string is:
2245   * <pre>
2246   * device/pathSegment1/pathSegment2...</pre>
2247   *
2248   * <p>If there is an authority, it is:
2249   * <pre>
2250   * //authority/device/pathSegment1/pathSegment2...</pre>
2251   *
2252   * <p>However, the character used as a separator is system-dependant and
2253   * obtained from {@link java.io.File#separatorChar}.
2254   */

2255  public String JavaDoc toFileString()
2256  {
2257    if (!isFile()) return null;
2258
2259    StringBuffer JavaDoc result = new StringBuffer JavaDoc();
2260    char separator = File.separatorChar;
2261
2262    if (hasAuthority())
2263    {
2264      result.append(separator);
2265      result.append(separator);
2266      result.append(authority);
2267
2268      if (hasDevice()) result.append(separator);
2269    }
2270
2271    if (hasDevice()) result.append(device);
2272    if (hasAbsolutePath()) result.append(separator);
2273
2274    for (int i = 0, len = segments.length; i < len; i++)
2275    {
2276      if (i != 0) result.append(separator);
2277      result.append(segments[i]);
2278    }
2279
2280    return decode(result.toString());
2281  }
2282
2283  /**
2284   * Returns the URI formed by appending the specified segment on to the end
2285   * of the path of this URI, if hierarchical; this URI unchanged,
2286   * otherwise. If this URI has an authority and/or device, but no path,
2287   * the segment becomes the first under the root in an absolute path.
2288   *
2289   * @exception java.lang.IllegalArgumentException if <code>segment</code>
2290   * is not a valid segment according to {@link #validSegment}.
2291   */

2292  public URI appendSegment(String JavaDoc segment)
2293  {
2294    if (!validSegment(segment))
2295    {
2296      throw new IllegalArgumentException JavaDoc("invalid segment: " + segment);
2297    }
2298
2299    if (!isHierarchical()) return this;
2300
2301    // absolute path or no path -> absolute path
2302
boolean newAbsolutePath = !hasRelativePath();
2303
2304    int len = segments.length;
2305    String JavaDoc[] newSegments = new String JavaDoc[len + 1];
2306    System.arraycopy(segments, 0, newSegments, 0, len);
2307    newSegments[len] = segment;
2308
2309    return new URI(true, scheme, authority, device, newAbsolutePath,
2310                   newSegments, query, fragment);
2311  }
2312
2313  /**
2314   * Returns the URI formed by appending the specified segments on to the
2315   * end of the path of this URI, if hierarchical; this URI unchanged,
2316   * otherwise. If this URI has an authority and/or device, but no path,
2317   * the segments are made to form an absolute path.
2318   *
2319   * @param segments an array of non-null strings, each representing one
2320   * segment of the path. If desired, a trailing separator should be
2321   * represented by an empty-string segment as the last element of the
2322   * array.
2323   *
2324   * @exception java.lang.IllegalArgumentException if <code>segments</code>
2325   * is not a valid segment array according to {@link #validSegments}.
2326   */

2327  public URI appendSegments(String JavaDoc[] segments)
2328  {
2329    if (!validSegments(segments))
2330    {
2331      String JavaDoc s = segments == null ? "invalid segments: " + segments :
2332        "invalid segment: " + firstInvalidSegment(segments);
2333      throw new IllegalArgumentException JavaDoc(s);
2334    }
2335
2336    if (!isHierarchical()) return this;
2337
2338    // absolute path or no path -> absolute path
2339
boolean newAbsolutePath = !hasRelativePath();
2340
2341    int len = this.segments.length;
2342    int segmentsCount = segments.length;
2343    String JavaDoc[] newSegments = new String JavaDoc[len + segmentsCount];
2344    System.arraycopy(this.segments, 0, newSegments, 0, len);
2345    System.arraycopy(segments, 0, newSegments, len, segmentsCount);
2346    
2347    return new URI(true, scheme, authority, device, newAbsolutePath,
2348                   newSegments, query, fragment);
2349  }
2350
2351  /**
2352   * Returns the URI formed by trimming the specified number of segments
2353   * (including empty segments, such as one representing a trailing
2354   * separator) from the end of the path of this URI, if hierarchical;
2355   * otherwise, this URI is returned unchanged.
2356   *
2357   * <p>Note that if all segments are trimmed from an absolute path, the
2358   * root absolute path remains.
2359   *
2360   * @param i the number of segments to be trimmed in the returned URI. If
2361   * less than 1, this URI is returned unchanged; if equal to or greater
2362   * than the number of segments in this URI's path, all segments are
2363   * trimmed.
2364   */

2365  public URI trimSegments(int i)
2366  {
2367    if (!isHierarchical() || i < 1) return this;
2368
2369    String JavaDoc[] newSegments = NO_SEGMENTS;
2370    int len = segments.length - i;
2371    if (len > 0)
2372    {
2373      newSegments = new String JavaDoc[len];
2374      System.arraycopy(segments, 0, newSegments, 0, len);
2375    }
2376    return new URI(true, scheme, authority, device, absolutePath,
2377                   newSegments, query, fragment);
2378  }
2379
2380  /**
2381   * Returns <code>true</code> if this is a hierarchical URI that has a path
2382   * that ends with a trailing separator; <code>false</code> otherwise.
2383   *
2384   * <p>A trailing separator is represented as an empty segment as the
2385   * last segment in the path; note that this definition does <em>not</em>
2386   * include the lone separator in the root absolute path.
2387   */

2388  public boolean hasTrailingPathSeparator()
2389  {
2390    return segments.length > 0 &&
2391      SEGMENT_EMPTY.equals(segments[segments.length - 1]);
2392  }
2393
2394  /**
2395   * If this is a hierarchical URI whose path includes a file extension,
2396   * that file extension is returned; null otherwise. We define a file
2397   * extension as any string following the last period (".") in the final
2398   * path segment. If there is no path, the path ends in a trailing
2399   * separator, or the final segment contains no period, then we consider
2400   * there to be no file extension. If the final segment ends in a period,
2401   * then the file extension is an empty string.
2402   */

2403  public String JavaDoc fileExtension()
2404  {
2405    int len = segments.length;
2406    if (len == 0) return null;
2407
2408    String JavaDoc lastSegment = segments[len - 1];
2409    int i = lastSegment.lastIndexOf(FILE_EXTENSION_SEPARATOR);
2410    return i < 0 ? null : lastSegment.substring(i + 1);
2411  }
2412
2413  /**
2414   * Returns the URI formed by appending a period (".") followed by the
2415   * specified file extension to the last path segment of this URI, if it is
2416   * hierarchical with a non-empty path ending in a non-empty segment;
2417   * otherwise, this URI is returned unchanged.
2418
2419   * <p>The extension is appended regardless of whether the segment already
2420   * contains an extension.
2421   *
2422   * @exception java.lang.IllegalArgumentException if
2423   * <code>fileExtension</code> is not a valid segment (portion) according
2424   * to {@link #validSegment}.
2425   */

2426  public URI appendFileExtension(String JavaDoc fileExtension)
2427  {
2428    if (!validSegment(fileExtension))
2429    {
2430      throw new IllegalArgumentException JavaDoc(
2431        "invalid segment portion: " + fileExtension);
2432    }
2433
2434    int len = segments.length;
2435    if (len == 0) return this;
2436
2437    String JavaDoc lastSegment = segments[len - 1];
2438    if (SEGMENT_EMPTY.equals(lastSegment)) return this;
2439    StringBuffer JavaDoc newLastSegment = new StringBuffer JavaDoc(lastSegment);
2440    newLastSegment.append(FILE_EXTENSION_SEPARATOR);
2441    newLastSegment.append(fileExtension);
2442
2443    String JavaDoc[] newSegments = new String JavaDoc[len];
2444    System.arraycopy(segments, 0, newSegments, 0, len - 1);
2445    newSegments[len - 1] = newLastSegment.toString();
2446    
2447    // note: segments.length > 0 -> hierarchical
2448
return new URI(true, scheme, authority, device, absolutePath,
2449                   newSegments, query, fragment);
2450  }
2451
2452  /**
2453   * If this URI has a non-null {@link #fileExtension fileExtension},
2454   * returns the URI formed by removing it; this URI unchanged, otherwise.
2455   */

2456  public URI trimFileExtension()
2457  {
2458    int len = segments.length;
2459    if (len == 0) return this;
2460
2461    String JavaDoc lastSegment = segments[len - 1];
2462    int i = lastSegment.lastIndexOf(FILE_EXTENSION_SEPARATOR);
2463    if (i < 0) return this;
2464
2465    String JavaDoc newLastSegment = lastSegment.substring(0, i);
2466    String JavaDoc[] newSegments = new String JavaDoc[len];
2467    System.arraycopy(segments, 0, newSegments, 0, len - 1);
2468    newSegments[len - 1] = newLastSegment;
2469
2470    // note: segments.length > 0 -> hierarchical
2471
return new URI(true, scheme, authority, device, absolutePath,
2472                   newSegments, query, fragment);
2473  }
2474
2475  /**
2476   * Returns <code>true</code> if this is a hierarchical URI that ends in a
2477   * slash; that is, it has a trailing path separator or is the root
2478   * absolute path, and has no query and no fragment; <code>false</code>
2479   * is returned otherwise.
2480   */

2481  public boolean isPrefix()
2482  {
2483    return hierarchical && query == null && fragment == null &&
2484      (hasTrailingPathSeparator() || (absolutePath && segments.length == 0));
2485  }
2486
2487  /**
2488   * If this is a hierarchical URI reference and <code>oldPrefix</code> is a
2489   * prefix of it, this returns the URI formed by replacing it by
2490   * <code>newPrefix</code>; <code>null</code> otherwise.
2491   *
2492   * <p>In order to be a prefix, the <code>oldPrefix</code>'s
2493   * {@link #isPrefix isPrefix} must return <code>true</code>, and it must
2494   * match this URI's scheme, authority, and device. Also, the paths must
2495   * match, up to prefix's end.
2496   *
2497   * @exception java.lang.IllegalArgumentException if either
2498   * <code>oldPrefix</code> or <code>newPrefix</code> is not a prefix URI
2499   * according to {@link #isPrefix}.
2500   */

2501  public URI replacePrefix(URI oldPrefix, URI newPrefix)
2502  {
2503    if (!oldPrefix.isPrefix() || !newPrefix.isPrefix())
2504    {
2505      String JavaDoc which = oldPrefix.isPrefix() ? "new" : "old";
2506      throw new IllegalArgumentException JavaDoc("non-prefix " + which + " value");
2507    }
2508
2509    // Get what's left of the segments after trimming the prefix.
2510
String JavaDoc[] tailSegments = getTailSegments(oldPrefix);
2511    if (tailSegments == null) return null;
2512
2513    // If the new prefix has segments, it is not the root absolute path,
2514
// and we need to drop the trailing empty segment and append the tail
2515
// segments.
2516
String JavaDoc[] mergedSegments = tailSegments;
2517    if (newPrefix.segmentCount() != 0)
2518    {
2519      int segmentsToKeep = newPrefix.segmentCount() - 1;
2520      mergedSegments = new String JavaDoc[segmentsToKeep + tailSegments.length];
2521      System.arraycopy(newPrefix.segments(), 0, mergedSegments, 0,
2522                       segmentsToKeep);
2523
2524      if (tailSegments.length != 0)
2525      {
2526        System.arraycopy(tailSegments, 0, mergedSegments, segmentsToKeep,
2527                         tailSegments.length);
2528      }
2529    }
2530
2531    // no validation needed since all components are from existing URIs
2532
return new URI(true, newPrefix.scheme(), newPrefix.authority(),
2533                   newPrefix.device(), newPrefix.hasAbsolutePath(),
2534                   mergedSegments, query, fragment);
2535  }
2536
2537  // If this is a hierarchical URI reference and prefix is a prefix of it,
2538
// returns the portion of the path remaining after that prefix has been
2539
// trimmed; null otherwise.
2540
private String JavaDoc[] getTailSegments(URI prefix)
2541  {
2542    if (!prefix.isPrefix())
2543    {
2544      throw new IllegalArgumentException JavaDoc("non-prefix trim");
2545    }
2546
2547    // Don't even consider it unless this is hierarchical and has scheme,
2548
// authority, device and path absoluteness equal to those of the prefix.
2549
if (!hierarchical ||
2550        !equals(scheme, prefix.scheme(), true) ||
2551        !equals(authority, prefix.authority()) ||
2552        !equals(device, prefix.device()) ||
2553        absolutePath != prefix.hasAbsolutePath())
2554    {
2555      return null;
2556    }
2557
2558    // If the prefix has no segments, then it is the root absolute path, and
2559
// we know this is an absolute path, too.
2560
if (prefix.segmentCount() == 0) return segments;
2561
2562    // This must have no fewer segments than the prefix. Since the prefix
2563
// is not the root absolute path, its last segment is empty; all others
2564
// must match.
2565
int i = 0;
2566    int segmentsToCompare = prefix.segmentCount() - 1;
2567    if (segments.length <= segmentsToCompare) return null;
2568
2569    for (; i < segmentsToCompare; i++)
2570    {
2571      if (!segments[i].equals(prefix.segment(i))) return null;
2572    }
2573
2574    // The prefix really is a prefix of this. If this has just one more,
2575
// empty segment, the paths are the same.
2576
if (i == segments.length - 1 && SEGMENT_EMPTY.equals(segments[i]))
2577    {
2578      return NO_SEGMENTS;
2579    }
2580    
2581    // Otherwise, the path needs only the remaining segments.
2582
String JavaDoc[] newSegments = new String JavaDoc[segments.length - i];
2583    System.arraycopy(segments, i, newSegments, 0, newSegments.length);
2584    return newSegments;
2585  }
2586
2587  /**
2588   * Encodes a string so as to produce a valid opaque part value, as defined
2589   * by the RFC. All excluded characters, such as space and <code>#</code>,
2590   * are escaped, as is <code>/</code> if it is the first character.
2591   *
2592   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
2593   * unescaped if they already begin a valid three-character escape sequence;
2594   * <code>false</code> to encode all <code>%</code> characters. Note that
2595   * if a <code>%</code> is not followed by 2 hex digits, it will always be
2596   * escaped.
2597   */

2598  public static String JavaDoc encodeOpaquePart(String JavaDoc value, boolean ignoreEscaped)
2599  {
2600    String JavaDoc result = encode(value, URIC_HI, URIC_LO, ignoreEscaped);
2601    return result != null && result.length() > 0 && result.charAt(0) == SEGMENT_SEPARATOR ?
2602      "%2F" + result.substring(1) :
2603      result;
2604  }
2605
2606  /**
2607   * Encodes a string so as to produce a valid authority, as defined by the
2608   * RFC. All excluded characters, such as space and <code>#</code>,
2609   * are escaped, as are <code>/</code> and <code>?</code>
2610   *
2611   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
2612   * unescaped if they already begin a valid three-character escape sequence;
2613   * <code>false</code> to encode all <code>%</code> characters. Note that
2614   * if a <code>%</code> is not followed by 2 hex digits, it will always be
2615   * escaped.
2616   */

2617  public static String JavaDoc encodeAuthority(String JavaDoc value, boolean ignoreEscaped)
2618  {
2619    return encode(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, ignoreEscaped);
2620  }
2621
2622  /**
2623   * Encodes a string so as to produce a valid segment, as defined by the
2624   * RFC. All excluded characters, such as space and <code>#</code>,
2625   * are escaped, as are <code>/</code> and <code>?</code>
2626   *
2627   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
2628   * unescaped if they already begin a valid three-character escape sequence;
2629   * <code>false</code> to encode all <code>%</code> characters. Note that
2630   * if a <code>%</code> is not followed by 2 hex digits, it will always be
2631   * escaped.
2632   */

2633  public static String JavaDoc encodeSegment(String JavaDoc value, boolean ignoreEscaped)
2634  {
2635    return encode(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, ignoreEscaped);
2636  }
2637
2638  /**
2639   * Encodes a string so as to produce a valid query, as defined by the RFC.
2640   * Only excluded characters, such as space and <code>#</code>, are escaped.
2641   *
2642   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
2643   * unescaped if they already begin a valid three-character escape sequence;
2644   * <code>false</code> to encode all <code>%</code> characters. Note that
2645   * if a <code>%</code> is not followed by 2 hex digits, it will always be
2646   * escaped.
2647   */

2648  public static String JavaDoc encodeQuery(String JavaDoc value, boolean ignoreEscaped)
2649  {
2650    return encode(value, URIC_HI, URIC_LO, ignoreEscaped);
2651  }
2652
2653  /**
2654   * Encodes a string so as to produce a valid fragment, as defined by the
2655   * RFC. Only excluded characters, such as space and <code>#</code>, are
2656   * escaped.
2657   *
2658   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
2659   * unescaped if they already begin a valid three-character escape sequence;
2660   * <code>false</code> to encode all <code>%</code> characters. Note that
2661   * if a <code>%</code> is not followed by 2 hex digits, it will always be
2662   * escaped.
2663   */

2664  public static String JavaDoc encodeFragment(String JavaDoc value, boolean ignoreEscaped)
2665  {
2666    return encode(value, URIC_HI, URIC_LO, ignoreEscaped);
2667  }
2668
2669  // Encodes a complete URI, optionally leaving % characters unescaped when
2670
// beginning a valid three-character escape sequence. We assume that the
2671
// last # begins the fragment.
2672
private static String JavaDoc encodeURI(String JavaDoc uri, boolean ignoreEscaped)
2673  {
2674    if (uri == null) return null;
2675
2676    StringBuffer JavaDoc result = new StringBuffer JavaDoc();
2677
2678    int i = uri.indexOf(SCHEME_SEPARATOR);
2679    if (i != -1)
2680    {
2681      String JavaDoc scheme = uri.substring(0, i);
2682      result.append(scheme);
2683      result.append(SCHEME_SEPARATOR);
2684    }
2685    
2686    int j = uri.lastIndexOf(FRAGMENT_SEPARATOR);
2687    if (j != -1)
2688    {
2689      String JavaDoc sspart = uri.substring(++i, j);
2690      result.append(encode(sspart, URIC_HI, URIC_LO, ignoreEscaped));
2691      result.append(FRAGMENT_SEPARATOR);
2692
2693      String JavaDoc fragment = uri.substring(++j);
2694      result.append(encode(fragment, URIC_HI, URIC_LO, ignoreEscaped));
2695    }
2696    else
2697    {
2698      String JavaDoc sspart = uri.substring(++i);
2699      result.append(encode(sspart, URIC_HI, URIC_LO, ignoreEscaped));
2700    }
2701    
2702    return result.toString();
2703  }
2704
2705  // Encodes the given string, replacing each ASCII character that is not in
2706
// the set specified by the 128-bit bitmask and each non-ASCII character
2707
// below 0xA0 by an escape sequence of % followed by two hex digits. If
2708
// % is not in the set but ignoreEscaped is true, then % will not be encoded
2709
// iff it already begins a valid escape sequence.
2710
private static String JavaDoc encode(String JavaDoc value, long highBitmask, long lowBitmask, boolean ignoreEscaped)
2711  {
2712    if (value == null) return null;
2713
2714    StringBuffer JavaDoc result = null;
2715
2716    for (int i = 0, len = value.length(); i < len; i++)
2717    {
2718      char c = value.charAt(i);
2719
2720      if (!matches(c, highBitmask, lowBitmask) && c < 160 &&
2721          (!ignoreEscaped || !isEscaped(value, i)))
2722      {
2723        if (result == null)
2724        {
2725          result = new StringBuffer JavaDoc(value.substring(0, i));
2726        }
2727        appendEscaped(result, (byte)c);
2728      }
2729      else if (result != null)
2730      {
2731        result.append(c);
2732      }
2733    }
2734    return result == null ? value : result.toString();
2735  }
2736
2737  // Tests whether an escape occurs in the given string, starting at index i.
2738
// An escape sequence is a % followed by two hex digits.
2739
private static boolean isEscaped(String JavaDoc s, int i)
2740  {
2741    return s.charAt(i) == ESCAPE && s.length() > i + 2 &&
2742      matches(s.charAt(i + 1), HEX_HI, HEX_LO) &&
2743      matches(s.charAt(i + 2), HEX_HI, HEX_LO);
2744  }
2745
2746  // Computes a three-character escape sequence for the byte, appending
2747
// it to the StringBuffer. Only characters up to 0xFF should be escaped;
2748
// all but the least significant byte will be ignored.
2749
private static void appendEscaped(StringBuffer JavaDoc result, byte b)
2750  {
2751    result.append(ESCAPE);
2752
2753    // The byte is automatically widened into an int, with sign extension,
2754
// for shifting. This can introduce 1's to the left of the byte, which
2755
// must be cleared by masking before looking up the hex digit.
2756
//
2757
result.append(HEX_DIGITS[(b >> 4) & 0x0F]);
2758    result.append(HEX_DIGITS[b & 0x0F]);
2759  }
2760
2761  /**
2762   * Decodes the given string, replacing each three-digit escape sequence by
2763   * the character that it represents. Incomplete escape sequences are
2764   * ignored.
2765   */

2766  public static String JavaDoc decode(String JavaDoc value)
2767  {
2768    if (value == null) return null;
2769
2770    StringBuffer JavaDoc result = null;
2771
2772    for (int i = 0, len = value.length(); i < len; i++)
2773    {
2774      if (isEscaped(value, i))
2775      {
2776        if (result == null)
2777        {
2778          result = new StringBuffer JavaDoc(value.substring(0, i));
2779        }
2780        result.append(unescape(value.charAt(i + 1), value.charAt(i + 2)));
2781        i += 2;
2782      }
2783      else if (result != null)
2784      {
2785        result.append(value.charAt(i));
2786      }
2787    }
2788    return result == null ? value : result.toString();
2789  }
2790
2791  // Returns the character encoded by % followed by the two given hex digits,
2792
// which is always 0xFF or less, so can safely be casted to a byte. If
2793
// either character is not a hex digit, a bogus result will be returned.
2794
private static char unescape(char highHexDigit, char lowHexDigit)
2795  {
2796    return (char)((valueOf(highHexDigit) << 4) | valueOf(lowHexDigit));
2797  }
2798
2799  // Returns the int value of the given hex digit.
2800
private static int valueOf(char hexDigit)
2801  {
2802    if (hexDigit >= 'A' && hexDigit <= 'F')
2803    {
2804      return hexDigit - 'A' + 10;
2805    }
2806    if (hexDigit >= 'a' && hexDigit <= 'f')
2807    {
2808      return hexDigit - 'a' + 10;
2809    }
2810    if (hexDigit >= '0' && hexDigit <= '9')
2811    {
2812      return hexDigit - '0';
2813    }
2814    return 0;
2815  }
2816
2817  /*
2818   * Returns <code>true</code> if this URI contains non-ASCII characters;
2819   * <code>false</code> otherwise.
2820   *
2821   * This unused code is included for possible future use...
2822   */

2823/*
2824  public boolean isIRI()
2825  {
2826    return iri;
2827  }
2828
2829  // Returns true if the given string contains any non-ASCII characters;
2830  // false otherwise.
2831  private static boolean containsNonASCII(String value)
2832  {
2833    for (int i = 0, len = value.length(); i < len; i++)
2834    {
2835      if (value.charAt(i) > 127) return true;
2836    }
2837    return false;
2838  }
2839*/

2840
2841  /*
2842   * If this is an {@link #isIRI IRI}, converts it to a strict ASCII URI,
2843   * using the procedure described in Section 3.1 of the
2844   * <a HREF="http://www.w3.org/International/iri-edit/draft-duerst-iri-09.txt">IRI
2845   * Draft RFC</a>. Otherwise, this URI, itself, is returned.
2846   *
2847   * This unused code is included for possible future use...
2848   */

2849/*
2850  public URI toASCIIURI()
2851  {
2852    if (!iri) return this;
2853
2854    if (cachedASCIIURI == null)
2855    {
2856      String eAuthority = encodeAsASCII(authority);
2857      String eDevice = encodeAsASCII(device);
2858      String eQuery = encodeAsASCII(query);
2859      String eFragment = encodeAsASCII(fragment);
2860      String[] eSegments = new String[segments.length];
2861      for (int i = 0; i < segments.length; i++)
2862      {
2863        eSegments[i] = encodeAsASCII(segments[i]);
2864      }
2865      cachedASCIIURI = new URI(hierarchical, scheme, eAuthority, eDevice, absolutePath, eSegments, eQuery, eFragment);
2866
2867    }
2868    return cachedASCIIURI;
2869  }
2870
2871  // Returns a strict ASCII encoding of the given value. Each non-ASCII
2872  // character is converted to bytes using UTF-8 encoding, which are then
2873  // represnted using % escaping.
2874  private String encodeAsASCII(String value)
2875  {
2876    if (value == null) return null;
2877
2878    StringBuffer result = null;
2879
2880    for (int i = 0, len = value.length(); i < len; i++)
2881    {
2882      char c = value.charAt(i);
2883
2884      if (c >= 128)
2885      {
2886        if (result == null)
2887        {
2888          result = new StringBuffer(value.substring(0, i));
2889        }
2890
2891        try
2892        {
2893          byte[] encoded = (new String(new char[] { c })).getBytes("UTF-8");
2894          for (int j = 0, encLen = encoded.length; j < encLen; j++)
2895          {
2896            appendEscaped(result, encoded[j]);
2897          }
2898        }
2899        catch (UnsupportedEncodingException e)
2900        {
2901          throw new WrappedException(e);
2902        }
2903      }
2904      else if (result != null)
2905      {
2906        result.append(c);
2907      }
2908
2909    }
2910    return result == null ? value : result.toString();
2911  }
2912
2913  // Returns the number of valid, consecutive, three-character escape
2914  // sequences in the given string, starting at index i.
2915  private static int countEscaped(String s, int i)
2916  {
2917    int result = 0;
2918
2919    for (int len = s.length(); i < len; i += 3)
2920    {
2921      if (isEscaped(s, i)) result++;
2922    }
2923    return result;
2924  }
2925*/

2926}
2927
Popular Tags