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