Author: lryan
Date: Tue May 19 00:49:41 2009
New Revision: 776142

URL: http://svn.apache.org/viewvc?rev=776142&view=rev
Log:
Implemented Uri.resolve locally, instead of using java.net.URI.resolve. Add 
explicit code to disallow opaque uris. This is a fairly substantial performance 
improvement

Modified:
    
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/uri/Uri.java

Modified: 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/uri/Uri.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/uri/Uri.java?rev=776142&r1=776141&r2=776142&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/uri/Uri.java
 (original)
+++ 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/uri/Uri.java
 Tue May 19 00:49:41 2009
@@ -18,7 +18,8 @@
  */
 package org.apache.shindig.common.uri;
 
-import com.google.common.base.Join;
+import org.apache.commons.lang.StringUtils;
+
 import com.google.common.base.Objects;
 import com.google.common.collect.Maps;
 import com.google.inject.Inject;
@@ -27,10 +28,8 @@
 import java.net.URISyntaxException;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.StringTokenizer;
 
 /**
 * Represents a Uniform Resource Identifier (URI) reference as defined by <a
@@ -98,6 +97,9 @@
    * Convert a java.net.URI to a Uri.
    */
   public static Uri fromJavaUri(URI uri) {
+    if (uri.isOpaque()) {
+      throw new IllegalArgumentException("No support for opaque Uris " + 
uri.toString());
+    }
     return new UriBuilder()
         .setScheme(uri.getScheme())
         .setAuthority(uri.getRawAuthority())
@@ -120,121 +122,153 @@
   }
 
   /**
+   * Derived from Harmony
    * Resolves a given url relative to this url. Resolution rules are the same 
as for
    * {...@code java.net.URI.resolve(URI)}
-   *
-   * @param other The url to resolve against.
-   * @return The new url.
    */
-  public Uri resolve(Uri other) {
-    // return  this.resolveNew(other);
-    if (other == null) {
+  public Uri resolve(Uri relative) {
+    if (relative == null) {
       return null;
     }
-    
-    return fromJavaUri(toJavaUri().resolve(other.toJavaUri()));
-  }
-
-  public Uri resolveNew(Uri other) {
-    if (other == null) {
-      return null;
+    if (relative.isAbsolute()) {
+      return relative;
     }
 
-
-    String scheme = other.getScheme();
-    String authority = other.getAuthority();
-    String path = other.getPath();
-    String query = other.getQuery();
-    String fragment = other.getFragment();
-
-    if (scheme != null && scheme.length() > 0) {
-      // Do nothing - this will accept other's fields verbatim.
-    } else if (authority != null) {
-      // Schema-relative ie. "//newhost.com/foo?q=s". Take base scheme.
-      scheme = getScheme();
-    } else if (path != null && path.length() > 0) {
-      // Resolve other path against current. Keep prerequisites.
-      scheme = getScheme();
-      authority = getAuthority();
-      path = resolvePath(path);
-    } else if (query != null && query.length() > 0) {
-      // Accept query + fragment verbatim. Use base scheme/authority/path.
-      scheme = getScheme();
-      authority = getAuthority();
-      // Treat query-relative as ""-path with query.
-      path = resolvePath("");
-    } else if (fragment != null && fragment.length() > 0) {
-      // Accept fragment verbatim. Use base scheme/authority/path/query.
-      scheme = getScheme();
-      authority = getAuthority();
-      path = getPath();
-      query = getQuery();
+    UriBuilder result;
+    if (StringUtils.isEmpty(relative.path) && relative.scheme == null
+        && relative.authority == null && relative.query == null
+        && relative.fragment != null) {
+      // if the relative URI only consists of fragment,
+      // the resolved URI is very similar to this URI,
+      // except that it has the fragement from the relative URI.
+      result = new UriBuilder(this);
+      result.setFragment(relative.fragment);
+    } else if (relative.scheme != null) {
+      result = new UriBuilder(relative);
+    } else if (relative.authority != null) {
+      // if the relative URI has authority,
+      // the resolved URI is almost the same as the relative URI,
+      // except that it has the scheme of this URI.
+      result = new UriBuilder(relative);
+      result.setScheme(scheme);
+    } else {
+      // since relative URI has no authority,
+      // the resolved URI is very similar to this URI,
+      // except that it has the query and fragment of the relative URI,
+      // and the path is different.
+      result = new UriBuilder(this);
+      result.setFragment(relative.fragment);
+      result.setQuery(relative.query);
+      String relativePath = (relative.path == null) ? "" : relative.path;
+      if (relativePath.startsWith("/")) { //$NON-NLS-1$
+        result.setPath(relativePath);
+      } else {
+        // resolve a relative reference
+        int endindex = path.lastIndexOf('/') + 1;
+        result.setPath(normalizePath(path.substring(0, endindex) + 
relativePath));
+      }
     }
+    Uri resolved = result.toUri();
+    validate(resolved);
+    return resolved;
+  }
 
-    return new UriBuilder()
-        .setScheme(scheme)
-        .setAuthority(authority)
-        .setPath(path)
-        .setQuery(query)
-        .setFragment(fragment)
-        .toUri();
+  private static void validate(Uri uri) {
+    if (StringUtils.isEmpty(uri.authority) &&
+        StringUtils.isEmpty(uri.path) &&
+        StringUtils.isEmpty(uri.query)) {
+      throw new IllegalArgumentException("Invalid scheme-specific part");
+    }
   }
 
   /**
-   * Resolves {...@code otherPath} against the current path, returning the 
result.
-   * Implements RFC 2396 resolution rules.
+   * Dervived from harmony
+   * normalize path, and return the resulting string
    */
-  private String resolvePath(String otherPath) {
-    if (otherPath.startsWith("/")) {
-      // Optimization: just accept other.
-      return otherPath;
-    }
-    
-    // Relative path. Treat current path as a stack, otherPath as a List
-    // in order to merge.
-    LinkedList<String> pathStack = new LinkedList<String>();
-    String curPath = getPath() != null ? getPath() : "/";  // Just in case.
-    StringTokenizer tok = new StringTokenizer(curPath, "/");
-
-    while (tok.hasMoreTokens()) {
-      pathStack.add(tok.nextToken());
-    }
-    if (!curPath.endsWith("/")) {
-      // The first entry in mergePath overwrites the last in the pathStack.
-      // eg. curPath = "/foo/bar", otherPath = "baz" --> "/foo/baz".
-      pathStack.removeLast();
-    }
-
-    LinkedList<String> mergePath = new LinkedList<String>();
-    StringTokenizer tok2 = new StringTokenizer(otherPath, "/");
-    while (tok2.hasMoreTokens()) {
-      mergePath.add(tok2.nextToken());
-    }
-    if (otherPath.endsWith("/") || otherPath.equals("")) {
-      // Retains the ending slash in the final join.
-      mergePath.add("");
-    }
-
-    // Merge mergePath into pathStack.
-    for (String mergeComponent : mergePath) {
-      if (mergeComponent.equals(".")) {
-        // Retain current position in the path. Continue.
-        continue;
-      } else if (mergeComponent.equals("..")) {
-        // Pop one off the path stack if available. If not do nothing.
-        if (!pathStack.isEmpty()) {
-          pathStack.removeLast();
+  private static String normalizePath(String path) {
+    // count the number of '/'s, to determine number of segments
+    int index = -1;
+    int pathlen = path.length();
+    int size = 0;
+    if (pathlen > 0 && path.charAt(0) != '/') {
+      size++;
+    }
+    while ((index = path.indexOf('/', index + 1)) != -1) {
+      if (index + 1 < pathlen && path.charAt(index + 1) != '/') {
+        size++;
+      }
+    }
+
+    String[] seglist = new String[size];
+    boolean[] include = new boolean[size];
+
+    // break the path into segments and store in the list
+    int current = 0;
+    int index2 = 0;
+    index = (pathlen > 0 && path.charAt(0) == '/') ? 1 : 0;
+    while ((index2 = path.indexOf('/', index + 1)) != -1) {
+      seglist[current++] = path.substring(index, index2);
+      index = index2 + 1;
+    }
+
+    // if current==size, then the last character was a slash
+    // and there are no more segments
+    if (current < size) {
+      seglist[current] = path.substring(index);
+    }
+
+    // determine which segments get included in the normalized path
+    for (int i = 0; i < size; i++) {
+      include[i] = true;
+      if (seglist[i].equals("..")) { //$NON-NLS-1$
+        int remove = i - 1;
+        // search back to find a segment to remove, if possible
+        while (remove > -1 && !include[remove]) {
+          remove--;
         }
-      } else {
-        // Append latest to the path.
-        pathStack.add(mergeComponent);
+        // if we find a segment to remove, remove it and the ".."
+        // segment
+        if (remove > -1 && !seglist[remove].equals("..")) { //$NON-NLS-1$
+          include[remove] = false;
+          include[i] = false;
+        }
+      } else if (seglist[i].equals(".")) { //$NON-NLS-1$
+        include[i] = false;
+      }
+    }
+
+    // put the path back together
+    StringBuilder newpath = new StringBuilder();
+    if (path.startsWith("/")) { //$NON-NLS-1$
+      newpath.append('/');
+    }
+
+    for (int i = 0; i < seglist.length; i++) {
+      if (include[i]) {
+        newpath.append(seglist[i]);
+        newpath.append('/');
       }
     }
 
-    if (getAuthority() != null) {
-      pathStack.addFirst(""); // get an initial / on the front..
+    // if we used at least one segment and the path previously ended with
+    // a slash and the last segment is still used, then delete the extra
+    // trailing '/'
+    if (!path.endsWith("/") && seglist.length > 0 //$NON-NLS-1$
+        && include[seglist.length - 1]) {
+      newpath.deleteCharAt(newpath.length() - 1);
+    }
+
+    String result = newpath.toString();
+
+    // check for a ':' in the first segment if one exists,
+    // prepend "./" to normalize
+    index = result.indexOf(':');
+    index2 = result.indexOf('/');
+    if (index != -1 && (index < index2 || index2 == -1)) {
+      newpath.insert(0, "./"); //$NON-NLS-1$
+      result = newpath.toString();
     }
-    return Join.join("/", pathStack);
+    return result;
   }
 
   /**


Reply via email to