[103768] update resolver to distinguish between physical and logical resolutin results
diff --git a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/ExtensibleURIResolver.java b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/ExtensibleURIResolver.java
index dff7bba..d50c258 100644
--- a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/ExtensibleURIResolver.java
+++ b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/ExtensibleURIResolver.java
@@ -94,6 +94,34 @@
 
 		return result;
 	}
+    
+    public String resolvePhysicalLocation(String baseLocation, String publicId, String logicalLocation)
+    {
+      String result = logicalLocation;
+      URIResolverExtensionRegistry resolverRegistry = URIResolverExtensionRegistry.getIntance();
+      IFile file = computeFile(baseLocation);
+      
+      // compute the project that holds the resource
+      //      
+      IProject project =  file != null ? file.getProject() : null;            
+      List list = resolverRegistry.getExtensionDescriptors(project);      
+      for (Iterator i = resolverRegistry.getMatchingURIResolvers(list, URIResolverExtensionRegistry.STAGE_PHYSICAL).iterator(); i.hasNext(); )
+      {        
+        // get the list of applicable physical resolvers from the extension registry
+        //
+        while (i.hasNext())
+        {
+          URIResolverExtension resolver = (URIResolverExtension) i.next();
+          String tempresult = resolver.resolve(file, baseLocation, publicId, result);
+          if(tempresult != null)
+          {
+            result = tempresult;
+          }
+        }
+      }        
+      return result;
+    }
+    
 
 	protected String normalize(String baseLocation, String systemId)
 	{
diff --git a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URI.java b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URI.java
index 3aac823..6003437 100644
--- a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URI.java
+++ b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URI.java
@@ -1,23 +1,26 @@
 /*
-* Copyright (c) 2002 IBM Corporation and others.
-* All rights reserved.   This program and the accompanying materials
-* are made available under the terms of the Common Public License v1.0
-* which accompanies this distribution, and is available at
-* http://www.eclipse.org/legal/cpl-v10.html
-* 
-* Contributors:
-*   IBM - Initial API and implementation
-*   Jens Lukowski/Innoopract - initial renaming/restructuring
-* 
-*/
+ * Copyright (c) 2002 IBM Corporation and others.
+ * All rights reserved.   This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * Contributors:
+ *   IBM - Initial API and implementation
+ *   Jens Lukowski/Innoopract - initial renaming/restructuring
+ * 
+ */
 package org.eclipse.wst.common.uriresolver.internal;
 
 import java.io.File;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * A representation of a Uniform Resource Identifier (URI), as specified by
@@ -42,9 +45,9 @@
  * locate resources via common protocols such as HTTP, FTP, and Gopher, and
  * to identify files on a local file system.  Acordingly, most of this
  * class's functionality is for handling such URIs, which can be identified
- * via {@link #isHierarchical()}.
+ * via {@link #isHierarchical isHierarchical}.
  *
- * <p><a name="device_explaination">
+ * <p><a name="device_explanation">
  * The primary enhancement beyond the RFC description is an optional
  * device component.  Instead of treating the device as just another segment
  * in the path, it can be stored as a separate component (almost a
@@ -55,22 +58,59 @@
  * <code>file:///c:/</code> would not yield <code>file:///</code>, as you
  * might expect.  This feature is useful when working with file-scheme
  * URIs, as devices do not typically occur in protocol-based ones.  A
- * device-enabled <code>URI</code> can be created by parsing a string with
- * {@link #createURI} or by specifying a non-null <code>device</code>
- * paramter for either the {@link #createHierarchicalURI(String, String,
- * String, String, String) no-path} or the {@link
- * #createHierarchicalURI(String, String, String, String[], String, String)
- * absolute-path} form of <code>createHierarchicalURI()</code>.
+ * device-enabled <code>URI</code> is created by parsing a string with
+ * {@link #createURI(String) createURI}; if the first segment of the path
+ * ends with the <code>:</code> character, it is stored (including the colon)
+ * as the device, instead.  Alternately, either the {@link
+ * #createHierarchicalURI(String, String, String, String, String) no-path}
+ * or the {@link #createHierarchicalURI(String, String, String, String[],
+ * String, String) absolute-path} form of <code>createHierarchicalURI()</code>
+ * can be used, in which a non-null <code>device</code> parameter can be
+ * specified.
  *
- * <p>Compared to the RFC description, this implementation is quite relaxed
- * about validity.  Static methods whose names begin with "valid" test
- * whether a given string is a valid value for the various URI components.
- * Presently, these tests place no restrictions beyond what would have been
- * required in order for {@link #createURI} to
- * have parsed them correctly from a single URI string.  Note that all of
- * the static factory methods invoke the appropriate validation methods and
- * throw exceptions in response to a negative result, ensuring that
- * invalid URIs are never created.
+ * <p><a name="archive_explanation"> 
+ * The other enhancement provides support for the almost-hierarchical
+ * form used for files within archives, such as the JAR scheme, defined
+ * for the Java Platform in the documentation for {@link
+ * java.net.JarURLConnection}. By default, this support is enabled for
+ * absolute URIs with scheme equal to "jar", "zip", or "archive" (ignoring case), and
+ * is implemented by a hierarchical URI, whose authority includes the
+ * entire URI of the archive, up to and including the <code>!</code>
+ * character.  The URI of the archive must have no fragment.  The whole
+ * archive URI must have no device and an absolute path.  Special handling
+ * is supported for {@link #createURI creating}, {@link
+ * #validArchiveAuthority validating}, {@link #devicePath getting the path}
+ * from, and {@link #toString displaying} archive URIs. In all other
+ * operations, including {@link #resolve(URI) resolving} and {@link
+ * #deresolve(URI) deresolving}, they are handled like any ordinary URI.
+ *
+ * <p>This implementation does not impose the all of the restrictions on
+ * character validity that are specified in the RFC.  Static methods whose
+ * names begin with "valid" are used to test whether a given string is valid
+ * value for the various URI components.  Presently, these tests place no
+ * restrictions beyond what would have been required in order for {@link
+ * createURI(String) createURI} to have parsed them correctly from a single
+ * URI string.  If necessary in the future, these tests may be made more
+ * strict, to better coform to the RFC.
+ * 
+ * <p>Another group of static methods, whose names begin with "encode", use
+ * percent escaping to encode any characters that are not permitted in the
+ * various URI components. Another static method is provided to {@link
+ * #decode decode} encoded strings.  An escaped character is represented as
+ * a percent sybol (<code>%</code>), followed by two hex digits that specify
+ * the character code.  These encoding methods are more strict than the
+ * validation methods described above.  They ensure validity according to the
+ * RFC, with one exception: non-ASCII characters.
+ *
+ * <p>The RFC allows only characters that can be mapped to 7-bit US-ASCII
+ * representations.  Non-ASCII, single-byte characters can be used only via
+ * percent escaping, as described above.  This implementation uses Java's
+ * Unicode <code>char</code> and <code>String</code> representations, and
+ * makes no attempt to encode characters 0xA0 and above.  Characters in the
+ * range 0x80-0x9F are still escaped.  In this respect, this notion of a URI
+ * is actually more like an IRI (Internationalized Resource Identifier), for
+ * which an RFC is now in <href="http://www.w3.org/International/iri-edit/draft-duerst-iri-09.txt">draft
+ * form</a>.
  *
  * <p>Finally, note the difference between a <code>null</code> parameter to
  * the static factory methods and an empty string.  The former signifies the
@@ -94,6 +134,8 @@
   private final String fragment;
   private URI cachedTrimFragment;
   private String cachedToString;
+  //private final boolean iri;
+  //private URI cachedASCIIURI;
 
   // Applicable only to a hierarchical URI.
   private final String device;
@@ -101,9 +143,20 @@
   private final String[] segments; // empty last segment -> trailing separator
   private final String query;
 
+  // A cache of URIs, keyed by the strings from which they were created.
+  // The fragment of any URI is removed before caching it here, to minimize
+  // the size of the cache in the usual case where most URIs only differ by
+  // the fragment.
+  private static final Map uriCache = Collections.synchronizedMap(new HashMap());
+
+  // The lower-cased schemes that will be used to identify archive URIs.
+  private static final Set archiveSchemes;
+
   // Identifies a file-type absolute URI.
   private static final String SCHEME_FILE = "file";
   private static final String SCHEME_JAR = "jar";
+  private static final String SCHEME_ZIP = "zip";
+  private static final String SCHEME_ARCHIVE = "archive";
 
   // Special segment values interpreted at resolve and resolve time.
   private static final String SEGMENT_EMPTY = "";
@@ -111,7 +164,7 @@
   private static final String SEGMENT_PARENT = "..";
   private static final String[] NO_SEGMENTS = new String[0];
 
-  // Separators for parsing a URI string
+  // Separators for parsing a URI string.
   private static final char SCHEME_SEPARATOR = ':';
   private static final String AUTHORITY_SEPARATOR = "//";
   private static final char DEVICE_IDENTIFIER = ':';
@@ -121,10 +174,146 @@
   private static final char USER_INFO_SEPARATOR = '@';
   private static final char PORT_SEPARATOR = ':';
   private static final char FILE_EXTENSION_SEPARATOR = '.';
-  private static final char[] MAJOR_SEPARATORS = {
-    SCHEME_SEPARATOR, SEGMENT_SEPARATOR, QUERY_SEPARATOR, FRAGMENT_SEPARATOR };
-  private static final char[] SEGMENT_END = {
-    SEGMENT_SEPARATOR, QUERY_SEPARATOR, FRAGMENT_SEPARATOR };
+  private static final char ARCHIVE_IDENTIFIER = '!';
+  private static final String ARCHIVE_SEPARATOR = "!/";
+
+  // Characters to use in escaping.
+  private static final char ESCAPE = '%';
+  private static final char[] HEX_DIGITS = {
+    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+  // Some character classes, as defined in RFC 2396's BNF for URI.
+  // These are 128-bit bitmasks, stored as two longs, where the Nth bit is set
+  // iff the ASCII character with value N is included in the set.  These are
+  // created with the highBitmask() and lowBitmask() methods defined below,
+  // and a character is tested against them using matches().
+  //
+  private static final long ALPHA_HI = highBitmask('a', 'z') | highBitmask('A', 'Z');
+  private static final long ALPHA_LO = lowBitmask('a', 'z')  | lowBitmask('A', 'Z');
+  private static final long DIGIT_HI = highBitmask('0', '9');
+  private static final long DIGIT_LO = lowBitmask('0', '9');
+  private static final long ALPHANUM_HI = ALPHA_HI | DIGIT_HI;
+  private static final long ALPHANUM_LO = ALPHA_LO | DIGIT_LO;
+  private static final long HEX_HI = DIGIT_HI | highBitmask('A', 'F') | highBitmask('a', 'f');
+  private static final long HEX_LO = DIGIT_LO | lowBitmask('A', 'F')  | lowBitmask('a', 'f');
+  private static final long UNRESERVED_HI = ALPHANUM_HI | highBitmask("-_.!~*'()"); 
+  private static final long UNRESERVED_LO = ALPHANUM_LO | lowBitmask("-_.!~*'()");
+  private static final long RESERVED_HI = highBitmask(";/?:@&=+$,");
+  private static final long RESERVED_LO = lowBitmask(";/?:@&=+$,");
+  private static final long URIC_HI = RESERVED_HI | UNRESERVED_HI;  // | ucschar | escaped
+  private static final long URIC_LO = RESERVED_LO | UNRESERVED_LO;
+
+  // Additional useful character classes, including characters valid in certain
+  // URI components and separators used in parsing them out of a string. 
+  //
+  private static final long SEGMENT_CHAR_HI = UNRESERVED_HI | highBitmask(";:@&=+$,");  // | ucschar | escaped
+  private static final long SEGMENT_CHAR_LO = UNRESERVED_LO | lowBitmask(";:@&=+$,");
+  private static final long PATH_CHAR_HI = SEGMENT_CHAR_HI | highBitmask('/');  // | ucschar | escaped
+  private static final long PATH_CHAR_LO = SEGMENT_CHAR_LO | lowBitmask('/');
+//  private static final long SCHEME_CHAR_HI = ALPHANUM_HI | highBitmask("+-.");
+//  private static final long SCHEME_CHAR_LO = ALPHANUM_LO | lowBitmask("+-.");
+  private static final long MAJOR_SEPARATOR_HI = highBitmask(":/?#");
+  private static final long MAJOR_SEPARATOR_LO = lowBitmask(":/?#");
+  private static final long SEGMENT_END_HI = highBitmask("/?#");
+  private static final long SEGMENT_END_LO = lowBitmask("/?#");
+
+  // Static initializer for archiveSchemes.
+  static
+  {
+    Set set = new HashSet();
+    set.add(SCHEME_JAR);
+    set.add(SCHEME_ZIP);
+    set.add(SCHEME_ARCHIVE);
+    
+    
+    archiveSchemes = Collections.unmodifiableSet(set);
+  }
+
+  // Returns the lower half bitmask for the given ASCII character.
+  private static long lowBitmask(char c)
+  {
+    return c < 64 ? 1L << c : 0L;
+  }
+
+  // Returns the upper half bitmask for the given ACSII character.
+  private static long highBitmask(char c)
+  {
+    return c >= 64 && c < 128 ? 1L << (c - 64) : 0L;
+  }
+
+  // Returns the lower half bitmask for all ASCII characters between the two
+  // given characters, inclusive.
+  private static long lowBitmask(char from, char to)
+  {
+    long result = 0L;
+    if (from < 64 && from <= to)
+    {
+      to = to < 64 ? to : 63;
+      for (char c = from; c <= to; c++)
+      {
+        result |= (1L << c);
+      }
+    }
+    return result;
+  }
+
+  // Returns the upper half bitmask for all AsCII characters between the two
+  // given characters, inclusive.
+  private static long highBitmask(char from, char to)
+  {
+    return to < 64 ? 0 : lowBitmask((char)(from < 64 ? 0 : from - 64), (char)(to - 64));
+  }
+
+  // Returns the lower half bitmask for all the ASCII characters in the given
+  // string.
+  private static long lowBitmask(String chars)
+  {
+    long result = 0L;
+    for (int i = 0, len = chars.length(); i < len; i++)
+    {
+      char c = chars.charAt(i);
+      if (c < 64) result |= (1L << c);
+    }
+    return result;
+  }
+
+  // Returns the upper half bitmask for all the ASCII characters in the given
+  // string.
+  private static long highBitmask(String chars)
+  {
+    long result = 0L;
+    for (int i = 0, len = chars.length(); i < len; i++)
+    {
+      char c = chars.charAt(i);
+      if (c >= 64 && c < 128) result |= (1L << (c - 64));
+    }
+    return result;
+  }
+
+  // Returns whether the given character is in the set specified by the given
+  // bitmask.
+  private static boolean matches(char c, long highBitmask, long lowBitmask)
+  {
+    if (c >= 128) return false;
+    return c < 64 ?
+      ((1L << c) & lowBitmask) != 0 :
+      ((1L << (c - 64)) & highBitmask) != 0;
+  }
+
+  // Debugging method: converts the given long to a string of binary digits.
+/*
+  private static String toBits(long l)
+  {
+    StringBuffer result = new StringBuffer();
+    for (int i = 0; i < 64; i++)
+    {
+      boolean b = (l & 1L) != 0;
+      result.insert(0, b ? '1' : '0');
+      l >>= 1;
+    }
+    return result.toString();
+  }
+*/
 
   /**
    * Static factory method for a generic, non-hierarchical URI.  There is no
@@ -132,9 +321,11 @@
    * created.
    *
    * @exception java.lang.IllegalArgumentException if <code>scheme</code> is
-   * null, or if <code>scheme</code>, <code>opaquePart</code>, or
-   * <code>fragment</code> is not valid according to {@link #validScheme},
-   * {@link #validOpaquePart}, or {@link #validFragment}, respectively.
+   * null, if <code>scheme</code> is an <a href="#archive_explanation">archive
+   * URI</a> scheme, or if <code>scheme</code>, <code>opaquePart</code>, or
+   * <code>fragment</code> is not valid according to {@link #validScheme
+   * validScheme}, {@link #validOpaquePart validOpaquePart}, or {@link
+   * #validFragment validFragment}, respectively.
    */
   public static URI createGenericURI(String scheme, String opaquePart,
                                      String fragment)
@@ -144,8 +335,13 @@
       throw new IllegalArgumentException("relative non-hierarchical URI");
     }
 
-    return new URI(false, scheme, opaquePart, null, false, NO_SEGMENTS,
-                   null, fragment);
+    if (isArchiveScheme(scheme))
+    {
+      throw new IllegalArgumentException("non-hierarchical archive URI");
+    }
+
+    validateURI(false, scheme, opaquePart, null, false, NO_SEGMENTS, null, fragment);
+    return new URI(false, scheme, opaquePart, null, false, NO_SEGMENTS, null, fragment);
   }
 
   /**
@@ -156,10 +352,13 @@
    *
    * @exception java.lang.IllegalArgumentException if <code>scheme</code> is
    * non-null while <code>authority</code> and <code>device</code> are null,
-   * or if <code>scheme</code>, <code>authority</code>, <code>device</code>,
-   * <code>query</code>, or <code>fragment</code> is not valid according to
-   * {@link #validScheme}, {@link #validAuthority}, {@link #validDevice},
-   * {@link #validQuery}, or {@link #validFragment}, respectively.
+   * if <code>scheme</code> is an <a href="#archive_explanation">archive
+   * URI</a> scheme, or if <code>scheme</code>, <code>authority</code>,
+   * <code>device</code>, <code>query</code>, or <code>fragment</code> is not
+   * valid according to {@link #validScheme validSheme}, {@link
+   * #validAuthority validAuthority}, {@link #validDevice validDevice},
+   * {@link #validQuery validQuery}, or {@link #validFragment validFragment},
+   * respectively.
    */
   public static URI createHierarchicalURI(String scheme, String authority,
                                           String device, String query,
@@ -171,8 +370,13 @@
         "absolute hierarchical URI without authority, device, path");
     }
 
-    return new URI(true, scheme, authority, device, false, NO_SEGMENTS,
-                   query, fragment);
+    if (isArchiveScheme(scheme))
+    {
+      throw new IllegalArgumentException("archive URI with no path");
+    }
+
+    validateURI(true, scheme, authority, device, false, NO_SEGMENTS, query, fragment);
+    return new URI(true, scheme, authority, device, false, NO_SEGMENTS, query, fragment);
   }
 
   /**
@@ -186,19 +390,29 @@
    * separator should be represented by an empty-string segment as the last
    * element of the array. 
    *
-   * @exception java.lang.IllegalArgumentException if <code>scheme</code>,
+   * @exception java.lang.IllegalArgumentException if <code>scheme</code> is
+   * an <a href="#archive_explanation">archive URI</a> scheme and 
+   * <code>device</code> is non-null, or if <code>scheme</code>,
    * <code>authority</code>, <code>device</code>, <code>segments</code>,
    * <code>query</code>, or <code>fragment</code> is not valid according to
-   * {@link #validScheme}, {@link #validAuthority}, {@link #validDevice},
-   * {@link #validSegments}, {@link #validQuery}, or {@link #validFragment},
-   * respectively.
+   * {@link #validScheme validScheme}, {@link #validAuthority validAuthority}
+   * or {@link #validArchiveAuthority validArchiveAuthority}, {@link
+   * #validDevice validDevice}, {@link #validSegments validSegments}, {@link
+   * #validQuery validQuery}, or {@link #validFragment validFragment}, as
+   * appropriate.
    */
   public static URI createHierarchicalURI(String scheme, String authority,
                                           String device, String[] segments,
                                           String query, String fragment)
   {
-    return new URI(true, scheme, authority, device, true, fix(segments),
-                   query, fragment);
+    if (isArchiveScheme(scheme) && device != null)
+    {
+      throw new IllegalArgumentException("archive URI with device");
+    }
+
+    segments = fix(segments);
+    validateURI(true, scheme, authority, device, true, segments, query, fragment);
+    return new URI(true, scheme, authority, device, true, segments, query, fragment);
   }
 
   /**
@@ -211,14 +425,15 @@
    *
    * @exception java.lang.IllegalArgumentException if <code>segments</code>,
    * <code>query</code>, or <code>fragment</code> is not valid according to 
-   * {@link #validSegments}, {@link #validQuery}, or {@link #validFragment},
-   * respectively.
+   * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
+   * {@link #validFragment validFragment}, respectively.
    */
   public static URI createHierarchicalURI(String[] segments, String query,
                                           String fragment)
   {
-    return new URI(true, null, null, null, false, fix(segments), query,
-                   fragment);
+    segments = fix(segments);
+    validateURI(true, null, null, null, false, segments, query, fragment);
+    return new URI(true, null, null, null, false, segments, query, fragment);
   }
 
   // Converts null to length-zero array, and clones array to ensure
@@ -230,8 +445,9 @@
   
   /**
    * Static factory method based on parsing a URI string, with 
-   * <a href="#device_explaination">explicit device support</a> enabled.  
-   * The specified string is parsed as described in <a
+   * <a href="#device_explanation">explicit device support</a> and handling
+   * for <a href="#archive_explanation">archive URIs</a> enabled. The
+   * specified string is parsed as described in <a
    * href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>, and an
    * appropriate <code>URI</code> is created and returned.  Note that
    * validity testing is not as strict as in the RFC; essentially, only
@@ -240,33 +456,98 @@
    * error.
    *
    * @exception java.lang.IllegalArgumentException if any component parsed
-   * from <code>uri</code> is not valid according to {@link #validScheme},
-   * {@link #validOpaquePart}, {@link #validAuthority}, {@link
-   * #validDevice}, {@link #validSegments}, {@link #validQuery}, or {@link
-   * #validFragment}, as appropriate.
+   * from <code>uri</code> is not valid according to {@link #validScheme
+   * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
+   * #validAuthority validAuthority}, {@link #validArchiveAuthority
+   * validArchiveAuthority}, {@link #validDevice validDevice}, {@link
+   * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
+   * #validFragment validFragment}, as appropriate.
    */
   public static URI createURI(String uri)
   {
-    return parseIntoURI(uri);
+    return createURIWithCache(uri); 
+  }
+
+  /**
+   * Static factory method that encodes and parses the given URI string.
+   * Appropriate encoding is performed for each component of the URI.
+   * If more than one <code>#</code> is in the string, the last one is
+   * assumed to be the fragment's separator, and any others are encoded.
+   *  
+   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
+   * unescaped if they already begin a valid three-character escape sequence;
+   * <code>false</code> to encode all <code>%</code> characters.  Note that
+   * if a <code>%</code> is not followed by 2 hex digits, it will always be
+   * escaped. 
+   *
+   * @exception java.lang.IllegalArgumentException if any component parsed
+   * from <code>uri</code> is not valid according to {@link #validScheme
+   * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
+   * #validAuthority validAuthority}, {@link #validArchiveAuthority
+   * validArchiveAuthority}, {@link #validDevice validDevice}, {@link
+   * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
+   * #validFragment validFragment}, as appropriate.
+   */
+  public static URI createURI(String uri, boolean ignoreEscaped)
+  {
+    return createURIWithCache(encodeURI(uri, ignoreEscaped));
   }
 
   /**
    * Static factory method based on parsing a URI string, with 
-   * <a href="#device_explaination">explicit device support</a> enabled.  
+   * <a href="#device_explanation">explicit device support</a> enabled.  
    * Note that validity testing is not a strict as in the RFC; essentially,
    * only separator characters are considered.  So, for example, non-Latin
    * alphabet characters appearing in the scheme would not be considered an
    * error.
+   *
    * @exception java.lang.IllegalArgumentException if any component parsed
-   * from <code>uri</code> is not valid according to {@link #validScheme},
-   * {@link #validOpaquePart}, {@link #validAuthority}, {@link
-   * #validDevice}, {@link #validSegments}, {@link #validQuery}, or {@link
-   * #validFragment}, as appropriate.
-   * @deprecated
+   * from <code>uri</code> is not valid according to {@link #validScheme
+   * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
+   * #validAuthority validAuthority}, {@link #validArchiveAuthority
+   * validArchiveAuthority}, {@link #validDevice validDevice}, {@link
+   * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
+   * #validFragment validFragment}, as appropriate.
+   *
+   * @deprecated Use {@link #createURI createURI}, which now has explicit
+   * device support enabled. The two methods now operate identically.
    */
   public static URI createDeviceURI(String uri)
   {
-    return parseIntoURI(uri);
+    return createURIWithCache(uri);
+  }
+
+  // Uses a cache to speed up creation of a URI from a string.  The cache
+  // is consulted to see if the URI, less any fragment, has already been
+  // created.  If needed, the fragment is re-appended to the cached URI,
+  // which is considerably more efficient than creating the whole URI from
+  // scratch.  If the URI wasn't found in the cache, it is created using
+  // parseIntoURI() and then cached.  This method should always be used
+  // by string-parsing factory methods, instead of parseIntoURI() directly.
+  /**
+   * This method was included in the public API by mistake.
+   * 
+   * @deprecated Please use {@link #createURI createURI} instead.
+   */
+  public static URI createURIWithCache(String uri)
+  {
+    int i = uri.indexOf(FRAGMENT_SEPARATOR);
+    String base = i == -1 ? uri : uri.substring(0, i);
+    String fragment = i == -1 ? null : uri.substring(i + 1);
+
+    URI result = (URI)uriCache.get(base);
+
+    if (result == null)
+    {
+      result = parseIntoURI(base);
+      uriCache.put(base, result);
+    }
+
+    if (fragment != null)
+    {
+      result = result.appendFragment(fragment);
+    }
+    return result;
   }
 
   // String-parsing implementation.
@@ -282,7 +563,7 @@
     String fragment = null;
 
     int i = 0;
-    int j = findSeparator(uri, i, MAJOR_SEPARATORS);
+    int j = find(uri, i, MAJOR_SEPARATOR_HI, MAJOR_SEPARATOR_LO);
 
     if (j < uri.length() && uri.charAt(j) == SCHEME_SEPARATOR)
     {
@@ -290,28 +571,38 @@
       i = j + 1;
     }
 
-    if (uri.startsWith(AUTHORITY_SEPARATOR, i))
+    boolean archiveScheme = isArchiveScheme(scheme);
+    if (archiveScheme)
+    {
+      j = uri.lastIndexOf(ARCHIVE_SEPARATOR);
+      if (j == -1)
+      {
+        throw new IllegalArgumentException("no archive separator");
+      }
+      hierarchical = true;
+      authority = uri.substring(i, ++j);
+      i = j;
+    }
+    else if (uri.startsWith(AUTHORITY_SEPARATOR, i))
     {
       i += AUTHORITY_SEPARATOR.length();
-      j = findSeparator(uri, i, SEGMENT_END);
+      j = find(uri, i, SEGMENT_END_HI, SEGMENT_END_LO);
       authority = uri.substring(i, j);
       i = j;
     }
     else if (scheme != null &&
-             (i == uri.length() || 
-               uri.charAt(i) != SEGMENT_SEPARATOR && 
-                 !(scheme.equalsIgnoreCase(SCHEME_FILE) || scheme.equalsIgnoreCase(SCHEME_JAR))))
+             (i == uri.length() || uri.charAt(i) != SEGMENT_SEPARATOR))
     {
       hierarchical = false;
-      j = findSeparator(uri, i, new char[] { FRAGMENT_SEPARATOR });
+      j = uri.indexOf(FRAGMENT_SEPARATOR, i);
+      if (j == -1) j = uri.length();
       authority = uri.substring(i, j);
       i = j;
     }
 
-    if (i < uri.length() &&
-        uri.charAt(i) == SEGMENT_SEPARATOR)
+    if (!archiveScheme && i < uri.length() && uri.charAt(i) == SEGMENT_SEPARATOR)
     {
-      j = findSeparator(uri, i + 1, SEGMENT_END);
+      j = find(uri, i + 1, SEGMENT_END_HI, SEGMENT_END_LO);
       String s = uri.substring(i + 1, j);
       
       if (s.length() > 0 && s.charAt(s.length() - 1) == DEVICE_IDENTIFIER)
@@ -333,7 +624,7 @@
 
       while (segmentsRemain(uri, i))
       {
-        j = findSeparator(uri, i, SEGMENT_END);
+        j = find(uri, i, SEGMENT_END_HI, SEGMENT_END_LO);
         segmentList.add(uri.substring(i, j));
         i = j;
 
@@ -348,7 +639,8 @@
 
     if (i < uri.length() && uri.charAt(i) == QUERY_SEPARATOR)
     {
-      j = findSeparator(uri, ++i, new char[] { FRAGMENT_SEPARATOR });
+      j = uri.indexOf(FRAGMENT_SEPARATOR, ++i);
+      if (j == -1) j = uri.length();
       query = uri.substring(i, j);
       i = j;
     }
@@ -358,66 +650,8 @@
       fragment = uri.substring(++i);
     }
 
-    return new URI(hierarchical, scheme, authority, device, absolutePath,
-                   segments, query, fragment);
-  }
-
-  /**
-   * Static factory method based on parsing File path string, with 
-   * <a href="#device_explaination">explicit device support</a> enabled.  
-   * Note that validity testing is not a strict as in the RFC; essentially,
-   * only separator characters are considered.  So, for example, non-Latin
-   * alphabet characters appearing in a path segment would not be considered an
-   * error.
-   * @exception java.lang.IllegalArgumentException if any component parsed
-   * from <code>uri</code> is not valid according to {@link #validScheme},
-   * {@link #validOpaquePart}, {@link #validAuthority}, {@link
-   * #validDevice}, {@link #validSegments}, {@link #validQuery}, or {@link
-   * #validFragment}, as appropriate.
-   */
-  public static URI createFileURI(String pathName)
-  {
-    File file = new File(pathName);
-    String uri = File.separatorChar != '/' ? pathName.replace(File.separatorChar, SEGMENT_SEPARATOR) : pathName;
-    if (file.isAbsolute())
-    {
-      URI result = parseIntoURI((uri.charAt(0) == SEGMENT_SEPARATOR ? "file:" : "file:/") + uri);
-      return result;
-    }
-    else
-    {
-      URI result = parseIntoURI(uri);
-      if (result.scheme() != null)
-      {
-        throw new IllegalArgumentException("invalid relative pathName: " + pathName);
-      }
-      return result;
-    }
-  }
-
-  /**
-   * Static factory method based on parsing a platform-relative path string.
-   * The <code>pathName</code> must be of the form 
-   *<pre>
-   *  /project-name/path
-   *</pre>
-   * and the result will be of the form 
-   *<pre>
-   *  platform:/resource/project-name/path
-   *</pre>
-   * The leading separator of the path will be provided if not present.
-   * This scheme supports relocatable projects in Eclipse and in stand-alone EMF.
-   * @exception java.lang.IllegalArgumentException if any component parsed
-   * from <code>uri</code> is not valid according to {@link #validScheme},
-   * {@link #validOpaquePart}, {@link #validAuthority}, {@link
-   * #validDevice}, {@link #validSegments}, {@link #validQuery}, or {@link
-   * #validFragment}, as appropriate.
-   * @see org.eclipse.core.runtime.Platform#resolve
-   */
-  public static URI createPlatformResourceURI(String pathName)
-  {
-    URI result = parseIntoURI((pathName.charAt(0) == SEGMENT_SEPARATOR ? "platform:/resource" : "platform:/resource/") + pathName);
-    return result;
+    validateURI(hierarchical, scheme, authority, device, absolutePath, segments, query, fragment);
+    return new URI(hierarchical, scheme, authority, device, absolutePath, segments, query, fragment);
   }
 
   // Checks whether the string contains any more segments after the one that
@@ -428,36 +662,212 @@
       uri.charAt(i) != FRAGMENT_SEPARATOR;
   }
 
-  // Finds the next occurance of one of the characters specified in
-  // separators in the given URI, beginning at index i.  The index of the
-  // first separator, or uri.length() if there is no such character, is
-  // returned.  Before searching, i is limited to be in the range 
-  // [0, uri.length()].
-  private static int findSeparator(String uri, int i, char[] separators)
+  // Finds the next occurance of one of the characters in the set represented
+  // by the given bitmask in the given string, beginning at index i. The index
+  // of the first found character, or s.length() if there is none, is
+  // returned.  Before searching, i is limited to the range [0, s.length()].
+  //
+  private static int find(String s, int i, long highBitmask, long lowBitmask)
   {
-    int len = uri.length();
+    int len = s.length();
     if (i >= len) return len;
 
-    outerLoop: for (i = i > 0 ? i : 0; i < len; i++)
+    for (i = i > 0 ? i : 0; i < len; i++)
     {
-      for (int j = 0, slen = separators.length; j < slen; j++)
-      {
-        if (uri.charAt(i) == separators[j]) break outerLoop;
-      }
+      if (matches(s.charAt(i), highBitmask, lowBitmask)) break;
     }
     return i;
   }
 
-  // Private constructor for use of static factory methods.  Does validation
-  // of each component, but assumes that the inter-component requirements
-  // described in the factory doc-comments are all satisfied.
+  /**
+   * Static factory method based on parsing a {@link java.io.File} path
+   * string.  The <code>pathName</code> is converted into an appropriate
+   * form, as follows: platform specific path separators are converted to
+   * <code>/<code>; the path is encoded; and a "file" scheme and, if missing,
+   * a leading <code>/</code>, are added to an absolute path.  The result
+   * is then parsed using {@link #createURI(String) createURI}.
+   *
+   * <p>The encoding step escapes all spaces, <code>#</code> characters, and
+   * other characters disallowed in URIs, as well as <code>?</code>, which
+   * would delimit a path from a query.  Decoding is automatically performed
+   * by {@link #toFileString toFileString}, and can be applied to the values
+   * returned by other accessors by via the static {@link #decode(String)
+   * decode} method.
+   *
+   * <p>A relative path with a specified device (something like
+   * <code>C:myfile.txt</code>) cannot be expressed as a valid URI.
+   * 
+   * @exception java.lang.IllegalArgumentException if <code>pathName</code>
+   * specifies a device and a relative path, or if any component of the path
+   * is not valid according to {@link #validAuthority validAuthority}, {@link
+   * #validDevice validDevice}, or {@link #validSegments validSegments},
+   * {@link #validQuery validQuery}, or {@link #validFragment validFragment}.
+   */
+  public static URI createFileURI(String pathName)
+  {
+    File file = new File(pathName);
+    String uri = File.separatorChar != '/' ? pathName.replace(File.separatorChar, SEGMENT_SEPARATOR) : pathName;
+    uri = encode(uri, PATH_CHAR_HI, PATH_CHAR_LO, false);
+    if (file.isAbsolute())
+    {
+      URI result = createURI((uri.charAt(0) == SEGMENT_SEPARATOR ? "file:" : "file:/") + uri);
+      return result;
+    }
+    else
+    {
+      URI result = createURI(uri);
+      if (result.scheme() != null)
+      {
+        throw new IllegalArgumentException("invalid relative pathName: " + pathName);
+      }
+      return result;
+    }
+  }
+
+  /**
+   * Static factory method based on parsing a platform-relative path string.
+   *
+   * <p>The <code>pathName</code> must be of the form:
+   * <pre>
+   *   /project-name/path</pre>
+   *
+   * <p>Platform-specific path separators will be converterted to slashes.
+   * If not included, the leading path separator will be added.  The
+   * result will be of this form, which is parsed using {@link #createURI
+   * createURI}:
+   * <pre>
+   *   platform:/resource/project-name/path</pre>
+   *
+   * 
+   * @exception java.lang.IllegalArgumentException if any component parsed
+   * from the path is not valid according to {@link #validDevice validDevice},
+   * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
+   * {@link #validFragment validFragment}.
+   *
+   * @see org.eclipse.core.runtime.Platform#resolve
+   * @see #createPlatformResourceURI(String, boolean)
+   */
+  public static URI createPlatformResourceURI(String pathName)
+  {
+    return createPlatformResourceURI(pathName, false);
+  }
+
+  /**
+   * Static factory method based on parsing a platform-relative path string,
+   * with an option to encode the created URI.
+   *
+   * <p>The <code>pathName</code> must be of the form:
+   * <pre>
+   *   /project-name/path</pre>
+   *
+   * <p>Platform-specific path separators will be converterted to slashes.
+   * If not included, the leading path separator will be added.  The
+   * result will be of this form, which is parsed using {@link #createURI
+   * createURI}:
+   * <pre>
+   *   platform:/resource/project-name/path</pre>
+   *
+   * <p>This scheme supports relocatable projects in Eclipse and in
+   * stand-alone .
+   *
+   * <p>Depending on the <code>encode</code> argument, the path may be
+   * automatically encoded to escape all spaces, <code>#</code> characters,
+   * and other characters disallowed in URIs, as well as <code>?</code>,
+   * which would delimit a path from a query.  Decoding can be performed with
+   * the static {@link #decode(String) decode} method.
+   * 
+   * @exception java.lang.IllegalArgumentException if any component parsed
+   * from the path is not valid according to {@link #validDevice validDevice},
+   * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
+   * {@link #validFragment validFragment}.
+   *
+   * @see org.eclipse.core.runtime.Platform#resolve
+   */
+  public static URI createPlatformResourceURI(String pathName, boolean encode)
+  {
+    if (File.separatorChar != SEGMENT_SEPARATOR)
+    {
+      pathName = pathName.replace(File.separatorChar, SEGMENT_SEPARATOR);
+    }
+
+    if (encode)
+    {
+      pathName = encode(pathName, PATH_CHAR_HI, PATH_CHAR_LO, false);
+    }
+    URI result = createURI((pathName.charAt(0) == SEGMENT_SEPARATOR ? "platform:/resource" : "platform:/resource/") + pathName);
+    return result;
+  }
+  
+  // Private constructor for use of static factory methods.
   private URI(boolean hierarchical, String scheme, String authority,
               String device, boolean absolutePath, String[] segments,
               String query, String fragment)
   {
-    String name = null;
-    String value = null;
+    int hashCode = 0;
+    //boolean iri = false;
 
+    if (hierarchical)
+    {
+      ++hashCode;
+    }
+    if (absolutePath)
+    {
+      hashCode += 2;
+    }
+    if (scheme != null)
+    {
+      hashCode ^= scheme.toLowerCase().hashCode();
+    }
+    if (authority != null)
+    {
+      hashCode ^= authority.hashCode();
+      //iri = iri || containsNonASCII(authority);
+    }
+    if (device != null)
+    {
+      hashCode ^= device.hashCode();
+      //iri = iri || containsNonASCII(device);
+    }
+    if (query != null)
+    {
+      hashCode ^= query.hashCode();
+      //iri = iri || containsNonASCII(query);
+    }
+    if (fragment != null)
+    {
+      hashCode ^= fragment.hashCode();
+      //iri = iri || containsNonASCII(fragment);
+    }
+
+    for (int i = 0, len = segments.length; i < len; i++)
+    {
+      hashCode ^= segments[i].hashCode();
+      //iri = iri || containsNonASCII(segments[i]);
+    }
+
+    this.hashCode = hashCode;
+    //this.iri = iri;
+    this.hierarchical = hierarchical;
+    this.scheme = scheme == null ? null : scheme.intern();
+    this.authority = authority;
+    this.device = device;
+    this.absolutePath = absolutePath;
+    this.segments = segments;
+    this.query = query;
+    this.fragment = fragment;
+  }
+  
+  // Validates all of the URI components.  Factory methods should call this
+  // before using the constructor, though they must ensure that the
+  // inter-component requirements described in their own Javadocs are all
+  // satisfied, themselves.  If a new URI is being constructed out of
+  // an existing URI, this need not be called.  Instead, just the new
+  // components may be validated individually.
+  private static void validateURI(boolean hierarchical, String scheme,
+                                    String authority, String device,
+                                    boolean absolutePath, String[] segments,
+                                    String query, String fragment)
+  {
     if (!validScheme(scheme))
     {
       throw new IllegalArgumentException("invalid scheme: " + scheme);
@@ -466,7 +876,11 @@
     {
       throw new IllegalArgumentException("invalid opaquePart: " + authority);
     }
-    if (hierarchical && !validAuthority(authority))
+    if (hierarchical && !isArchiveScheme(scheme) && !validAuthority(authority))
+    {
+      throw new IllegalArgumentException("invalid authority: " + authority);
+    }
+    if (hierarchical && isArchiveScheme(scheme) && !validArchiveAuthority(authority))
     {
       throw new IllegalArgumentException("invalid authority: " + authority);
     }
@@ -488,66 +902,10 @@
     {
       throw new IllegalArgumentException("invalid fragment: " + fragment);
     }
-
-    int hashCode = 0;
-    if (hierarchical)
-    {
-      ++hashCode;
-    }
-    if (absolutePath)
-    {
-      hashCode += 2;
-    }
-    if (scheme != null)
-    {
-      hashCode ^= scheme.hashCode();
-    }
-    if (authority != null)
-    {
-      hashCode ^= authority.hashCode();
-    }
-    if (device != null)
-    {
-      hashCode ^= device.hashCode();
-    }
-    if (query != null)
-    {
-      hashCode ^= query.hashCode();
-    }
-    if (fragment != null)
-    {
-      hashCode ^= fragment.hashCode();
-    }
-
-    for (int i = 0, len = segments.length; i < len; i++)
-    {
-      hashCode ^= segments[i].hashCode();
-    }
-
-    this.hashCode = hashCode;
-    this.hierarchical = hierarchical;
-    this.scheme = scheme;
-    this.authority = authority;
-    this.device = device;
-    this.absolutePath = absolutePath;
-    this.segments = segments;
-    this.query = query;
-    this.fragment = fragment;
   }
 
-  // Searches the specified string for any of the specified target
-  // characters, and if any occur, returns true; false otherwise.
-  private static boolean contains(String s, char[] targets)
-  {
-    for (int i = 0, len = s.length(); i < len; i++)
-    {
-      for (int j = 0, tlen = targets.length; j < tlen; j++)
-      {
-        if (s.charAt(i) == targets[j]) return true;
-      }
-    }
-    return false;
-  }
+  // Alternate, stricter implementations of the following validation methods
+  // are provided, commented out, for possible future use...
 
   /**
    * Returns <code>true</code> if the specified <code>value</code> would be
@@ -558,7 +916,16 @@
    */
   public static boolean validScheme(String value)
   {
-    return value == null || !contains(value, MAJOR_SEPARATORS);
+    return value == null || !contains(value, MAJOR_SEPARATOR_HI, MAJOR_SEPARATOR_LO);  
+
+  // <p>A valid scheme may be null, or consist of a single letter followed
+  // by any number of letters, numbers, and the following characters:
+  // <code>+ - .</code>
+
+    //if (value == null) return true;
+    //return value.length() != 0 &&
+    //  matches(value.charAt(0), ALPHA_HI, ALPHA_LO) &&
+    //  validate(value, SCHEME_CHAR_HI, SCHEME_CHAR_LO, false, false);
   }
 
   /**
@@ -573,7 +940,15 @@
   public static boolean validOpaquePart(String value)
   {
     return value != null && value.indexOf(FRAGMENT_SEPARATOR) == -1 &&
-      value.length() > 0 && value.charAt(0) != SEGMENT_SEPARATOR;
+    value.length() > 0 && value.charAt(0) != SEGMENT_SEPARATOR;
+
+  // <p>A valid opaque part must be non-null and non-empty. It may contain
+  // any allowed URI characters, but its first character may not be
+  // <code>/</code> 
+
+    //return value != null && value.length() != 0 &&
+    //  value.charAt(0) != SEGMENT_SEPARATOR &&
+    //  validate(value, URIC_HI, URIC_LO, true, true);
   }
 
   /**
@@ -585,11 +960,43 @@
    */
   public static boolean validAuthority(String value)
   {
-    return value == null || !contains(value, SEGMENT_END);
+    return value == null || !contains(value, SEGMENT_END_HI, SEGMENT_END_LO);
+
+  // A valid authority may be null or contain any allowed URI characters except
+  // for the following: <code>/ ?</code>
+
+    //return value == null || validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true);
   }
 
   /**
    * Returns <code>true</code> if the specified <code>value</code> would be
+   * valid as the authority component of an <a
+   * href="#archive_explanation">archive URI</a>; <code>false</code>
+   * otherwise.
+   *
+   * <p>To be valid, the authority, itself, must be a URI with no fragment,
+   * followed by the character <code>!</code>.
+   */
+  public static boolean validArchiveAuthority(String value)
+  {
+    if (value != null && value.length() > 0 &&
+        value.charAt(value.length() - 1) == ARCHIVE_IDENTIFIER)
+    {
+      try
+      {
+        URI archiveURI = createURI(value.substring(0, value.length() - 1));
+        return !archiveURI.hasFragment();
+      }
+      catch (IllegalArgumentException e)
+      {
+      }
+    }
+    return false;
+  }
+
+
+  /**
+   * Returns <code>true</code> if the specified <code>value</code> would be
    * valid as the device component of a URI; <code>false</code> otherwise.
    *
    * <p>A valid device may be null or non-empty, containing any characters
@@ -597,11 +1004,20 @@
    * character must be <code>:</code>
    */
   public static boolean validDevice(String value)
-  {
+  {    
     if (value == null) return true;
     int len = value.length();
     return len > 0 && value.charAt(len - 1) == DEVICE_IDENTIFIER &&
-      !contains(value, SEGMENT_END);
+      !contains(value, SEGMENT_END_HI, SEGMENT_END_LO);
+
+  // <p>A valid device may be null or non-empty, containing any allowed URI
+  // characters except for the following: <code>/ ?</code>  In addition, its
+  // last character must be <code>:</code>
+
+    //if (value == null) return true;
+    //int len = value.length();
+    //return len > 0 && validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true) &&
+    //  value.charAt(len - 1) == DEVICE_IDENTIFIER;
   }
 
   /**
@@ -613,7 +1029,12 @@
    */
   public static boolean validSegment(String value)
   {
-    return value != null && !contains(value, SEGMENT_END);
+    return value != null && !contains(value, SEGMENT_END_HI, SEGMENT_END_LO);
+
+  // <p>A valid path segment must be non-null and may contain any allowed URI
+  // characters except for the following: <code>/ ?</code> 
+
+    //return value != null && validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true);
   }
 
   /**
@@ -621,7 +1042,7 @@
    * a valid path segment array of a URI; <code>false</code> otherwise.
    *
    * <p>A valid path segment array must be non-null and contain only path
-   * segements that are valid, according to {@link #validSegment}.
+   * segements that are valid according to {@link #validSegment validSegment}.
    */
   public static boolean validSegments(String[] value)
   {
@@ -656,7 +1077,11 @@
   public static boolean validQuery(String value)
   {
     return value == null || value.indexOf(FRAGMENT_SEPARATOR) == -1;
-  }
+
+  // <p>A valid query may be null or contain any allowed URI characters.
+
+    //return value == null || validate(value, URIC_HI, URIC_LO, true, true);
+}
 
   /**
    * Returns <code>true</code> if the specified <code>value</code> would be
@@ -667,8 +1092,50 @@
   public static boolean validFragment(String value)
   {
     return true;
+
+  // <p>A valid fragment may be null or contain any allowed URI characters.
+
+    //return value == null || validate(value, URIC_HI, URIC_LO, true, true);
   }
 
+  // Searches the specified string for any characters in the set represnted
+  // by the 128-bit bitmask.  Returns true if any occur, or false otherwise.
+  private static boolean contains(String s, long highBitmask, long lowBitmask)
+  {
+    for (int i = 0, len = s.length(); i < len; i++)
+    {
+      if (matches(s.charAt(i), highBitmask, lowBitmask)) return true;
+    }
+    return false;
+  }
+
+  // Tests the non-null string value to see if it contains only ASCII
+  // characters in the set represented by the specified 128-bit bitmask,
+  // as well as, optionally, non-ASCII characters 0xA0 and above, and,
+  // also optionally, escape sequences of % followed by two hex digits.
+  // This method is used for the new, strict URI validation that is not
+  // not currently in place.
+/*
+  private static boolean validate(String value, long highBitmask, long lowBitmask,
+                                     boolean allowNonASCII, boolean allowEscaped)
+  {
+    for (int i = 0, len = value.length(); i < len; i++)
+    { 
+      char c = value.charAt(i);
+
+      if (matches(c, highBitmask, lowBitmask)) continue;
+      if (allowNonASCII && c >= 160) continue;
+      if (allowEscaped && isEscaped(value, i))
+      {
+        i += 2;
+        continue;
+      }
+      return false;
+    }
+    return true;
+  }
+*/
+
   /**
    * Returns <code>true</code> if this is a relative URI, or
    * <code>false</code> if it is an absolute URI.
@@ -826,6 +1293,25 @@
       ((isRelative() && !hasQuery()) || SCHEME_FILE.equalsIgnoreCase(scheme));
   }
 
+  // Returns true if this is an archive URI.  If so, we should expect that
+  // it is also hierarchical, with an authority (consisting of an absolute
+  // URI followed by "!"), no device, and an absolute path.
+  private boolean isArchive()
+  {
+    return isArchiveScheme(scheme);
+  }
+
+  /**
+   * Returns <code>true</code> if the specified <code>value</code> would be
+   * valid as the scheme of an <a
+   * href="#archive_explanation">archive URI</a>; <code>false</code>
+   * otherwise.
+   */
+  public static boolean isArchiveScheme(String value)
+  {
+    return value != null && archiveSchemes.contains(value.toLowerCase());
+  }
+  
   /**
    * Returns the hash code.
    */
@@ -839,7 +1325,8 @@
    * <code>URI</code> equal to this one; <code>false</code> otherwise.
    *
    * <p>Equality is determined strictly by comparing components, not by
-   * attempting to interpret what resource is being identified.
+   * attempting to interpret what resource is being identified.  The
+   * comparison of schemes is case-insensitive.
    */
   public boolean equals(Object obj)
   {
@@ -850,7 +1337,7 @@
     return hashCode == uri.hashCode() &&
       hierarchical == uri.isHierarchical() &&
       absolutePath == uri.hasAbsolutePath() &&
-      equals(scheme, uri.scheme()) &&
+      equals(scheme, uri.scheme(), true) &&
       equals(authority, hierarchical ? uri.authority() : uri.opaquePart()) &&
       equals(device, uri.device()) &&
       equals(query, uri.query()) && 
@@ -877,6 +1364,14 @@
     return o1 == null ? o2 == null : o1.equals(o2);
   }
 
+  // Tests two strings for equality, tolerating nulls and optionally
+  // ignoring case.
+  private static boolean equals(String s1, String s2, boolean ignoreCase)
+  {
+    return s1 == null ? s2 == null :
+      ignoreCase ? s1.equalsIgnoreCase(s2) : s1.equals(s2);
+  }
+
   /**
    * If this is an absolute URI, returns the scheme component;
    * <code>null</code> otherwise.
@@ -964,7 +1459,7 @@
 
   /**
    * Returns an unmodifiable list containing the same segments as the array
-   * returned by {@link #segments()}.
+   * returned by {@link #segments segments}.
    */
   public List segmentsList()
   {
@@ -973,7 +1468,7 @@
 
   /**
    * Returns the number of elements in the segment array that would be
-   * returned by {@link #segments()}.
+   * returned by {@link #segments segments}.
    */
   public int segmentCount()
   {
@@ -1007,9 +1502,8 @@
    * representation of the path; <code>null</code> otherwise.  The path
    * consists of a leading segment separator character (a slash), if the
    * path is absolute, followed by the slash-separated path segments.  If
-   * this URI has a separate <a href="#device_explaination">device
-   * component</a>, it is <em>not</em> included in the path.  If it has a
-   * device stored as a path segment, it is included.
+   * this URI has a separate <a href="#device_explanation">device
+   * component</a>, it is <em>not</em> included in the path.
    */
   public String path()
   {
@@ -1029,22 +1523,30 @@
   /**
    * If this is a hierarchical URI with a path, returns a string
    * representation of the path, including the authority and the 
-   * <a href="#device_explaination">device component</a>; 
+   * <a href="#device_explanation">device component</a>; 
    * <code>null</code> otherwise.  
-   * <p>The format of this string is
+   *
+   * <p>If there is no authority, the format of this string is:
    * <pre>
-   *   //authority/device/pathSegment1/pathSegment2...
-   *</pre>
+   *   device/pathSegment1/pathSegment2...</pre>
+   *
+   * <p>If there is an authority, it is:
+   * <pre>
+   *   //authority/device/pathSegment1/pathSegment2...</pre>
+   *
+   * <p>For an <a href="#archive_explanation">archive URI</a>, it's just:
+   * <pre>
+   *   authority/pathSegment1/pathSegment2...</pre>
    */
   public String devicePath()
   {
     if (!hasPath()) return null;
 
     StringBuffer result = new StringBuffer();
+
     if (hasAuthority())
     {
-      result.append(SEGMENT_SEPARATOR);
-      result.append(SEGMENT_SEPARATOR);
+      if (!isArchive()) result.append(AUTHORITY_SEPARATOR);
       result.append(authority);
 
       if (hasDevice()) result.append(SEGMENT_SEPARATOR);
@@ -1076,7 +1578,7 @@
    *
    * @exception java.lang.IllegalArgumentException if
    * <code>query</code> is not a valid query (portion) according
-   * to {@link #validQuery}.
+   * to {@link #validQuery validQuery}.
    */
   public URI appendQuery(String query)
   {
@@ -1089,7 +1591,7 @@
   }
 
   /**
-   * If this URI has a non-null {@link #query}, returns the URI
+   * If this URI has a non-null {@link #query query}, returns the URI
    * formed by removing it; this URI unchanged, otherwise.
    */
   public URI trimQuery()
@@ -1118,7 +1620,7 @@
    *
    * @exception java.lang.IllegalArgumentException if
    * <code>fragment</code> is not a valid fragment (portion) according
-   * to {@link #validFragment}.
+   * to {@link #validFragment validFragment}.
    */
   public URI appendFragment(String fragment)
   {
@@ -1128,12 +1630,16 @@
         "invalid fragment portion: " + fragment);
     }
     URI result = new URI(hierarchical, scheme, authority, device, absolutePath, segments, query, fragment); 
-    result.cachedTrimFragment = this;
+
+    if (!hasFragment())
+    {
+      result.cachedTrimFragment = this;
+    }
     return result;
   }
 
   /**
-   * If this URI has a non-null {@link #fragment}, returns the URI
+   * If this URI has a non-null {@link #fragment fragment}, returns the URI
    * formed by removing it; this URI unchanged, otherwise.
    */
   public URI trimFragment()
@@ -1162,7 +1668,8 @@
    * path.  Step 6(g) gives a choice of how to handle the case where parent
    * references point to a path above the root: the offending segments can
    * be preserved or discarded.  This method preserves them.  To have them
-   * discarded, please use the {@link #resolve(URI, boolean)} method.
+   * discarded, please use the two-parameter form of {@link
+   * #resolve(URI, boolean) resolve}.
    *
    * @exception java.lang.IllegalArgumentException if <code>base</code> is
    * non-hierarchical or is relative.
@@ -1185,7 +1692,7 @@
    * references point to a path above the root: the offending segments can
    * be preserved or discarded.  This method can do either.
    *
-   * @param preserveRootParent <code>true</code> if segments refering to the
+   * @param preserveRootParents <code>true</code> if segments refering to the
    * parent of the root path are to be preserved; <code>false</code> if they
    * are to be discarded.
    *
@@ -1245,7 +1752,8 @@
     }
     // else keep authority, device, path, and query
     
-    // always keep fragment, even if null, and use scheme from base
+    // always keep fragment, even if null, and use scheme from base;
+    // no validation needed since all components are from existing URIs
     return new URI(true, base.scheme(), newAuthority, newDevice,
                    newAbsolutePath, newSegments, newQuery, fragment);
   }
@@ -1331,7 +1839,7 @@
   /**
    * Finds the shortest relative or, if necessary, the absolute URI that,
    * when resolved against the given <code>base</code> absolute hierarchical
-   * URI using {@link #resolve(URI)}, will yield this absolute URI.  
+   * URI using {@link #resolve(URI) resolve}, will yield this absolute URI.  
    *
    * @exception java.lang.IllegalArgumentException if <code>base</code> is
    * non-hierarchical or is relative.
@@ -1345,8 +1853,8 @@
 
   /**
    * Finds an absolute URI that, when resolved against the given
-   * <code>base</code> absolute hierarchical URI using {@link #resolve(URI,
-   * boolean)}, will yield this absolute URI.
+   * <code>base</code> absolute hierarchical URI using {@link
+   * #resolve(URI, boolean) resolve}, will yield this absolute URI.
    *
    * @param preserveRootParents the boolean argument to <code>resolve(URI,
    * boolean)</code> for which the returned URI should resolve to this URI.
@@ -1381,7 +1889,7 @@
     // relative path; thus, both have either an absolute path or no path
     
     // different scheme: need complete, absolute URI
-    if (!scheme.equals(base.scheme())) return this;
+    if (!scheme.equalsIgnoreCase(base.scheme())) return this;
 
     // since base must be hierarchical, and since a non-hierarchical URI
     // must have both scheme and opaque part, the complete absolute URI is
@@ -1447,7 +1955,8 @@
     }
     // else keep authority, device, path, and query
 
-    // always include fragment, even if null
+    // always include fragment, even if null;
+    // no validation needed since all components are from existing URIs
     return new URI(true, null, newAuthority, newDevice, newAbsolutePath,
                    newSegments, newQuery, fragment);
   }
@@ -1581,14 +2090,16 @@
   /**
    * Returns the string representation of this URI.  For a generic,
    * non-hierarchical URI, this looks like:
-   *
    * <pre>
    *   scheme:opaquePart#fragment</pre>
    * 
    * <p>For a hierarchical URI, it looks like:
    * <pre>
    *   scheme://authority/device/pathSegment1/pathSegment2...?query#fragment</pre>
-   * 
+   *
+   * <p>For an <a href="#archive_explanation">archive URI</a>, it's just:
+   * <pre>
+   *   scheme:authority/pathSegment1/pathSegment2...?query#fragment</pre>
    * <p>Of course, absent components and their separators will be omitted.
    */
   public String toString()
@@ -1606,7 +2117,7 @@
       {
         if (hasAuthority())
         {
-          result.append(AUTHORITY_SEPARATOR);
+          if (!isArchive()) result.append(AUTHORITY_SEPARATOR);
           result.append(authority);
         }
 
@@ -1677,10 +2188,14 @@
 
   /**
    * If this URI may refer directly to a locally accessible file, as
-   * determined by {@link #isFile()}, returns the URI formatted as a
-   * pathname to that file; null otherwise.
+   * determined by {@link #isFile isFile}, {@link decode decodes} and formats  
+   * the URI as a pathname to that file; returns null otherwise.
    *
-   * <p>The format of this string is 
+   * <p>If there is no authority, the format of this string is:
+   * <pre>
+   *   device/pathSegment1/pathSegment2...</pre>
+   *
+   * <p>If there is an authority, it is:
    * <pre>
    *   //authority/device/pathSegment1/pathSegment2...</pre>
    * 
@@ -1711,7 +2226,8 @@
       if (i != 0) result.append(separator);
       result.append(segments[i]);
     }
-    return result.toString();
+
+    return decode(result.toString());
   }
 
   /**
@@ -1884,8 +2400,8 @@
   }
 
   /**
-   * If this URI has a non-null {@link #fileExtension}, returns the URI
-   * formed by removing it; this URI unchanged, otherwise.
+   * If this URI has a non-null {@link #fileExtension fileExtension},
+   * returns the URI formed by removing it; this URI unchanged, otherwise.
    */
   public URI trimFileExtension()
   {
@@ -1924,9 +2440,9 @@
    * <code>newPrefix</code>; <code>null</code> otherwise.
    *
    * <p>In order to be a prefix, the <code>oldPrefix</code>'s
-   * {@link #isPrefix} must return <code>true</code>, and it must match this
-   * URI's scheme, authority, and device.  Also, the paths must match, up to
-   * prefix's end.
+   * {@link #isPrefix isPrefix} must return <code>true</code>, and it must
+   * match this URI's scheme, authority, and device.  Also, the paths must
+   * match, up to prefix's end.
    *
    * @exception java.lang.IllegalArgumentException if either
    * <code>oldPrefix</code> or <code>newPrefix</code> is not a prefix URI
@@ -1962,6 +2478,7 @@
       }
     }
 
+    // no validation needed since all components are from existing URIs
     return new URI(true, newPrefix.scheme(), newPrefix.authority(),
                    newPrefix.device(), newPrefix.hasAbsolutePath(),
                    mergedSegments, query, fragment);
@@ -1980,7 +2497,7 @@
     // Don't even consider it unless this is hierarchical and has scheme,
     // authority, device and path absoluteness equal to those of the prefix.
     if (!hierarchical ||
-        !equals(scheme, prefix.scheme()) ||
+        !equals(scheme, prefix.scheme(), true) ||
         !equals(authority, prefix.authority()) ||
         !equals(device, prefix.device()) ||
         absolutePath != prefix.hasAbsolutePath())
@@ -2016,4 +2533,344 @@
     System.arraycopy(segments, i, newSegments, 0, newSegments.length);
     return newSegments;
   }
+
+  /**
+   * Encodes a string so as to produce a valid opaque part value, as defined
+   * by the RFC.  All excluded characters, such as space and <code>#</code>,
+   * are escaped, as is <code>/</code> if it is the first character.
+   * 
+   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
+   * unescaped if they already begin a valid three-character escape sequence;
+   * <code>false</code> to encode all <code>%</code> characters.  Note that
+   * if a <code>%</code> is not followed by 2 hex digits, it will always be
+   * escaped. 
+   */
+  public static String encodeOpaquePart(String value, boolean ignoreEscaped)
+  {
+    String result = encode(value, URIC_HI, URIC_LO, ignoreEscaped);
+    return result != null && result.length() > 0 && result.charAt(0) == SEGMENT_SEPARATOR ?
+      "%2F" + result.substring(1) :
+      result;
+  }
+
+  /**
+   * Encodes a string so as to produce a valid authority, as defined by the
+   * RFC.  All excluded characters, such as space and <code>#</code>,
+   * are escaped, as are <code>/</code> and <code>?</code>
+   * 
+   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
+   * unescaped if they already begin a valid three-character escape sequence;
+   * <code>false</code> to encode all <code>%</code> characters.  Note that
+   * if a <code>%</code> is not followed by 2 hex digits, it will always be
+   * escaped. 
+   */
+  public static String encodeAuthority(String value, boolean ignoreEscaped)
+  {
+    return encode(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, ignoreEscaped);
+  }
+
+  /**
+   * Encodes a string so as to produce a valid segment, as defined by the
+   * RFC.  All excluded characters, such as space and <code>#</code>,
+   * are escaped, as are <code>/</code> and <code>?</code>
+   * 
+   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
+   * unescaped if they already begin a valid three-character escape sequence;
+   * <code>false</code> to encode all <code>%</code> characters.  Note that
+   * if a <code>%</code> is not followed by 2 hex digits, it will always be
+   * escaped. 
+   */
+  public static String encodeSegment(String value, boolean ignoreEscaped)
+  {
+    return encode(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, ignoreEscaped);
+  }
+
+  /**
+   * Encodes a string so as to produce a valid query, as defined by the RFC.
+   * Only excluded characters, such as space and <code>#</code>, are escaped.
+   * 
+   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
+   * unescaped if they already begin a valid three-character escape sequence;
+   * <code>false</code> to encode all <code>%</code> characters.  Note that
+   * if a <code>%</code> is not followed by 2 hex digits, it will always be
+   * escaped. 
+   */
+  public static String encodeQuery(String value, boolean ignoreEscaped)
+  {
+    return encode(value, URIC_HI, URIC_LO, ignoreEscaped);
+  }
+
+  /**
+   * Encodes a string so as to produce a valid fragment, as defined by the
+   * RFC.  Only excluded characters, such as space and <code>#</code>, are
+   * escaped.
+   * 
+   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
+   * unescaped if they already begin a valid three-character escape sequence;
+   * <code>false</code> to encode all <code>%</code> characters.  Note that
+   * if a <code>%</code> is not followed by 2 hex digits, it will always be
+   * escaped. 
+   */
+  public static String encodeFragment(String value, boolean ignoreEscaped)
+  {
+    return encode(value, URIC_HI, URIC_LO, ignoreEscaped);
+  }
+
+  // Encodes a complete URI, optionally leaving % characters unescaped when
+  // beginning a valid three-character escape sequence.  We assume that the
+  // last # begins the fragment.
+  private static String encodeURI(String uri, boolean ignoreEscaped)
+  {
+    if (uri == null) return null;
+
+    StringBuffer result = new StringBuffer();
+
+    int i = uri.indexOf(SCHEME_SEPARATOR);
+    if (i != -1)
+    {
+      String scheme = uri.substring(0, i);
+      result.append(scheme);
+      result.append(SCHEME_SEPARATOR);
+    }
+    
+    int j = uri.lastIndexOf(FRAGMENT_SEPARATOR);
+    if (j != -1)
+    {
+      String sspart = uri.substring(++i, j);
+      result.append(encode(sspart, URIC_HI, URIC_LO, ignoreEscaped));
+      result.append(FRAGMENT_SEPARATOR);
+
+      String fragment = uri.substring(++j);
+      result.append(encode(fragment, URIC_HI, URIC_LO, ignoreEscaped));
+    }
+    else
+    {
+      String sspart = uri.substring(++i);
+      result.append(encode(sspart, URIC_HI, URIC_LO, ignoreEscaped));
+    }
+    
+    return result.toString();
+  }
+
+  // Encodes the given string, replacing each ASCII character that is not in
+  // the set specified by the 128-bit bitmask and each non-ASCII character
+  // below 0xA0 by an escape sequence of % followed by two hex digits.  If
+  // % is not in the set but ignoreEscaped is true, then % will not be encoded
+  // iff it already begins a valid escape sequence.
+  private static String encode(String value, long highBitmask, long lowBitmask, boolean ignoreEscaped)
+  {
+    if (value == null) return null;
+
+    StringBuffer result = null;
+
+    for (int i = 0, len = value.length(); i < len; i++)
+    {
+      char c = value.charAt(i);
+
+      if (!matches(c, highBitmask, lowBitmask) && c < 160 &&
+          (!ignoreEscaped || !isEscaped(value, i)))
+      {
+        if (result == null)
+        {
+          result = new StringBuffer(value.substring(0, i));
+        }
+        appendEscaped(result, (byte)c);
+      }
+      else if (result != null)
+      {
+        result.append(c);
+      }
+    }
+    return result == null ? value : result.toString();
+  }
+
+  // Tests whether an escape occurs in the given string, starting at index i.
+  // An escape sequence is a % followed by two hex digits.
+  private static boolean isEscaped(String s, int i)
+  {
+    return s.charAt(i) == ESCAPE && s.length() > i + 2 &&
+      matches(s.charAt(i + 1), HEX_HI, HEX_LO) &&
+      matches(s.charAt(i + 2), HEX_HI, HEX_LO);
+  }
+
+  // Computes a three-character escape sequence for the byte, appending
+  // it to the StringBuffer.  Only characters up to 0xFF should be escaped;
+  // all but the least significant byte will be ignored.
+  private static void appendEscaped(StringBuffer result, byte b)
+  {
+    result.append(ESCAPE);
+
+    // The byte is automatically widened into an int, with sign extension,
+    // for shifting.  This can introduce 1's to the left of the byte, which
+    // must be cleared by masking before looking up the hex digit.
+    //
+    result.append(HEX_DIGITS[(b >> 4) & 0x0F]);
+    result.append(HEX_DIGITS[b & 0x0F]);
+  }
+
+  /**
+   * Decodes the given string, replacing each three-digit escape sequence by
+   * the character that it represents.  Incomplete escape sequences are
+   * ignored.
+   */
+  public static String decode(String value)
+  {
+    if (value == null) return null;
+
+    StringBuffer result = null;
+
+    for (int i = 0, len = value.length(); i < len; i++)
+    {
+      if (isEscaped(value, i)) 
+      {
+        if (result == null)
+        {
+          result = new StringBuffer(value.substring(0, i));
+        }
+        result.append(unescape(value.charAt(i + 1), value.charAt(i + 2)));
+        i += 2;
+      }
+      else if (result != null)
+      {
+        result.append(value.charAt(i));
+      }
+    }
+    return result == null ? value : result.toString();
+  }
+
+  // Returns the character encoded by % followed by the two given hex digits,
+  // which is always 0xFF or less, so can safely be casted to a byte.  If
+  // either character is not a hex digit, a bogus result will be returned.
+  private static char unescape(char highHexDigit, char lowHexDigit)
+  {
+    return (char)((valueOf(highHexDigit) << 4) | valueOf(lowHexDigit));
+  }
+
+  // Returns the int value of the given hex digit.
+  private static int valueOf(char hexDigit)
+  {
+    if (hexDigit >= 'A' && hexDigit <= 'F')
+    {
+      return hexDigit - 'A' + 10;
+    }
+    if (hexDigit >= 'a' && hexDigit <= 'f')
+    {
+      return hexDigit - 'a' + 10;
+    }
+    if (hexDigit >= '0' && hexDigit <= '9')
+    {
+      return hexDigit - '0';
+    }
+    return 0;
+  }
+
+  /*
+   * Returns <code>true</code> if this URI contains non-ASCII characters;
+   * <code>false</code> otherwise.
+   *
+   * This unused code is included for possible future use... 
+   */
+/*
+  public boolean isIRI()
+  {
+    return iri; 
+  }
+
+  // Returns true if the given string contains any non-ASCII characters;
+  // false otherwise.
+  private static boolean containsNonASCII(String value)
+  {
+    for (int i = 0, len = value.length(); i < len; i++)
+    {
+      if (value.charAt(i) > 127) return true;
+    }
+    return false;
+  }
+*/
+
+  /*
+   * If this is an {@link #isIRI IRI}, converts it to a strict ASCII URI,
+   * using the procedure described in Section 3.1 of the
+   * <a href="http://www.w3.org/International/iri-edit/draft-duerst-iri-09.txt">IRI
+   * Draft RFC</a>.  Otherwise, this URI, itself, is returned.
+   *
+   * This unused code is included for possible future use...
+   */
+/*
+  public URI toASCIIURI()
+  {
+    if (!iri) return this;
+
+    if (cachedASCIIURI == null)
+    {
+      String eAuthority = encodeAsASCII(authority);
+      String eDevice = encodeAsASCII(device);
+      String eQuery = encodeAsASCII(query);
+      String eFragment = encodeAsASCII(fragment);
+      String[] eSegments = new String[segments.length];
+      for (int i = 0; i < segments.length; i++)
+      {
+        eSegments[i] = encodeAsASCII(segments[i]);
+      }
+      cachedASCIIURI = new URI(hierarchical, scheme, eAuthority, eDevice, absolutePath, eSegments, eQuery, eFragment); 
+
+    }
+    return cachedASCIIURI;
+  }
+
+  // Returns a strict ASCII encoding of the given value.  Each non-ASCII
+  // character is converted to bytes using UTF-8 encoding, which are then
+  // represnted using % escaping.
+  private String encodeAsASCII(String value)
+  {
+    if (value == null) return null;
+
+    StringBuffer result = null;
+
+    for (int i = 0, len = value.length(); i < len; i++)
+    {
+      char c = value.charAt(i);
+
+      if (c >= 128)
+      {
+        if (result == null)
+        {
+          result = new StringBuffer(value.substring(0, i));
+        }
+
+        try
+        {
+          byte[] encoded = (new String(new char[] { c })).getBytes("UTF-8");
+          for (int j = 0, encLen = encoded.length; j < encLen; j++)
+          {
+            appendEscaped(result, encoded[j]);
+          }
+        }
+        catch (UnsupportedEncodingException e)
+        {
+          throw new WrappedException(e);
+        }
+      }
+      else if (result != null)
+      {
+        result.append(c);
+      }
+
+    }
+    return result == null ? value : result.toString();
+  }
+
+  // Returns the number of valid, consecutive, three-character escape
+  // sequences in the given string, starting at index i.
+  private static int countEscaped(String s, int i)
+  {
+    int result = 0;
+
+    for (int len = s.length(); i < len; i += 3)
+    {
+      if (isEscaped(s, i)) result++;
+    }
+    return result;
+  }
+*/
 }
diff --git a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionRegistry.java b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionRegistry.java
index 48c0827..926229e 100644
--- a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionRegistry.java
+++ b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionRegistry.java
@@ -26,6 +26,7 @@
 	protected HashMap map = new HashMap();
 	public static final int STAGE_PRENORMALIZATION = 1;
 	public static final int STAGE_POSTNORMALIZATION = 2;	
+    public static final int STAGE_PHYSICAL = 3;    
   public static final String PRIORITY_LOW = "low";
   public static final String PRIORITY_MEDIUM = "medium";
   public static final String PRIORITY_HIGH = "high";
diff --git a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionRegistryReader.java b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionRegistryReader.java
index e7de355..35b3847 100644
--- a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionRegistryReader.java
+++ b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionRegistryReader.java
@@ -36,6 +36,7 @@
 	    protected static final String ATT_STAGE = "stage";		
 	    protected static final String VAL_STAGE_PRE = "prenormalization";
 	    protected static final String VAL_STAGE_POST = "postnormalization";
+        protected static final String VAL_STAGE_PHYSICAL = "physical";
 	    protected static final String ATT_VALUE = "value";
        protected static final String ATT_PRIORITY = "priority";
 	   
@@ -99,6 +100,10 @@
 						{
 						  stageint = URIResolverExtensionRegistry.STAGE_PRENORMALIZATION;
 						}
+                        else if (stage.equalsIgnoreCase(VAL_STAGE_PHYSICAL))
+                        {
+                          stageint = URIResolverExtensionRegistry.STAGE_PHYSICAL;  
+                        }  
 						registry.put(className, classLoader, projectNatureIds, resourceType, stageint, priority);
 					} catch (Exception e) {
 					}
diff --git a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/provisional/URIResolver.java b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/provisional/URIResolver.java
index ed7dab9..dff7f90 100644
--- a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/provisional/URIResolver.java
+++ b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/provisional/URIResolver.java
@@ -14,7 +14,6 @@
 
 /**
  * A URIResolver is used to resolve URI references to resources.
- *  
  */
 public interface URIResolver {
 	
@@ -22,7 +21,15 @@
 	 * @param baseLocation - the location of the resource that contains the uri 
 	 * @param publicId - an optional public identifier (i.e. namespace name), or null if none
 	 * @param systemId - an absolute or relative URI, or null if none 
-	 * @return an absolute URI
+	 * @return an absolute URI represention the 'logical' location of the resource
 	 */
 	public String resolve(String baseLocation, String publicId, String systemId);
+    
+    /**
+     * @param baseLocation - the location of the resource that contains the uri 
+     * @param publicId - an optional public identifier (i.e. namespace name), or null if none
+     * @param systemId - an absolute or relative URI, or null if none 
+     * @return an absolute URI represention the 'physical' location of the resource
+     */
+    public String resolvePhysicalLocation(String baseLocation, String publicId, String logicalLocation);    
 }