diff options
author | Sven Gothel <[email protected]> | 2014-09-10 07:21:03 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2014-09-10 07:21:03 +0200 |
commit | c97e35377aea70cb293cabdd205bcc5da64b95c6 (patch) | |
tree | 52b4be4132fca18aff3067c199b9d6e9d54dfcac /src | |
parent | 6a466e3f1e92a1e831ea61d1bb72c32f56b2a28d (diff) |
Bug 1063: Uri: Refine API doc; Add create(Encoded ..) ; Provide common impl. for getNormalized(), getDirectory(), getParent() and getRelativeOf()
- Refine API doc
- Add notion of {@code host} and {@code port} validation
- Add create(Encoded ..), allowing creation of variants w/o re-encoding
- Provide common impl. for getNormalized(), getDirectory(), getParent() and getRelativeOf()
Above feature methods share common goals, hence use same implementation:
- If opaque, cut-off query and merge after operation
- cleanup path, i.e. /dummy/../test/ -> /test/
- cutoff file, dir - if requested
- append optional appendix and cleanup again
Return behavior various thought, i.e. null, this or allow exception.
Enhanced test of above features.
Diffstat (limited to 'src')
-rw-r--r-- | src/java/com/jogamp/common/net/Uri.java | 633 | ||||
-rw-r--r-- | src/java/com/jogamp/common/net/UriQueryProps.java | 8 | ||||
-rw-r--r-- | src/junit/com/jogamp/common/net/TestUri01.java | 168 | ||||
-rw-r--r-- | src/junit/com/jogamp/common/net/TestUri02Composing.java | 4 |
4 files changed, 630 insertions, 183 deletions
diff --git a/src/java/com/jogamp/common/net/Uri.java b/src/java/com/jogamp/common/net/Uri.java index a2c8833..6bafba2 100644 --- a/src/java/com/jogamp/common/net/Uri.java +++ b/src/java/com/jogamp/common/net/Uri.java @@ -435,6 +435,8 @@ public class Uri { /** See {@link String#lastIndexOf(String, int)}. */ public int lastIndexOf(final String str, final int fromIndex) { return s.lastIndexOf(str, fromIndex); } + /** See {@link String#startsWith(String)} */ + public boolean startsWith(final String prefix) { return s.startsWith(prefix); } /** See {@link String#startsWith(String, int)} */ public boolean startsWith(final String prefix, final int toffset) { return s.startsWith(prefix, toffset); } /** See {@link String#endsWith(String)} */ @@ -631,14 +633,17 @@ public class Uri { } /** - * Creates a new Uri instance using the given arguments. + * Creates a new Uri instance using the given unencoded arguments. * <p> - * This constructor first creates a temporary Uri string from the given components. This + * This constructor first creates a temporary Uri string from the given unencoded components. This * string will be parsed later on to create the Uri instance. * </p> * <p> * {@code [scheme:]scheme-specific-part[#fragment]} * </p> + * <p> + * {@code host} and {@code port} <i>may</i> be undefined or invalid within {@code scheme-specific-part}. + * </p> * * @param scheme the unencoded scheme part of the Uri. * @param ssp the unencoded scheme-specific-part of the Uri. @@ -669,19 +674,66 @@ public class Uri { } /** - * Creates a new Uri instance using the given arguments. + * Creates a new Uri instance using the given encoded arguments. * <p> - * This constructor first creates a temporary Uri string from the given components. This + * This constructor first creates a temporary Uri string from the given encoded components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * The given encoded components are taken as-is, i.e. no re-encoding will be performed! + * However, Uri parsing will re-evaluate encoding of the resulting components. + * </p> + * <p> + * {@code [scheme:]scheme-specific-part[#fragment]} + * </p> + * <p> + * {@code host} and {@code port} <i>may</i> be undefined or invalid within {@code scheme-specific-part}. + * </p> + * + * @param scheme the encoded scheme part of the Uri. + * @param ssp the encoded scheme-specific-part of the Uri. + * @param fragment the encoded fragment part of the Uri. + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri create(final Encoded scheme, final Encoded ssp, final Encoded fragment) throws URISyntaxException { + if ( emptyString(scheme) && emptyString(ssp) && emptyString(fragment) ) { + throw new URISyntaxException("", "all empty parts"); + } + final StringBuilder uri = new StringBuilder(); + if ( !emptyString(scheme) ) { + uri.append(scheme); + uri.append(SCHEME_SEPARATOR); + } + if ( !emptyString(ssp) ) { + uri.append(ssp.get()); + } + if ( !emptyString(fragment) ) { + uri.append(FRAGMENT_SEPARATOR); + uri.append(fragment.get()); + } + return new Uri(new Encoded(uri.toString()), false, 0); + } + + /** + * Creates a new Uri instance using the given unencoded arguments. + * <p> + * This constructor first creates a temporary Uri string from the given unencoded components. This * string will be parsed later on to create the Uri instance. * </p> * <p> * {@code [scheme:][user-info@]host[:port][path][?query][#fragment]} * </p> + * <p> + * {@code host} and {@code port} <i>must</i> be defined and valid, if any {@code authority} components are defined, + * i.e. {@code user-info}, {@code host} or {@code port}. + * </p> * * @param scheme the unencoded scheme part of the Uri. - * @param userinfo the unencoded user information of the Uri for authentication and authorization. - * @param host the unencoded host name of the Uri. - * @param port the port number of the Uri. + * @param userinfo the unencoded user information of the Uri for authentication and authorization, {@code null} for undefined. + * @param host the unencoded host name of the Uri, {@code null} for undefined. + * @param port the port number of the Uri, -1 for undefined. * @param path the unencoded path to the resource on the host. * @param query the unencoded query part of the Uri to specify parameters for the resource. * @param fragment the unencoded fragment part of the Uri. @@ -751,14 +803,97 @@ public class Uri { } /** - * Creates a new Uri instance using the given arguments. + * Creates a new Uri instance using the given encoded arguments. * <p> - * This constructor first creates a temporary Uri string from the given components. This + * This constructor first creates a temporary Uri string from the given encoded components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * The given encoded components are taken as-is, i.e. no re-encoding will be performed! + * However, Uri parsing will re-evaluate encoding of the resulting components. + * </p> + * <p> + * {@code [scheme:][user-info@]host[:port][path][?query][#fragment]} + * </p> + * <p> + * {@code host} and {@code port} <i>must</i> be defined and valid, if any {@code authority} components are defined, + * i.e. {@code user-info}, {@code host} or {@code port}. + * </p> + * + * @param scheme the encoded scheme part of the Uri. + * @param userinfo the encoded user information of the Uri for authentication and authorization, {@code null} for undefined. + * @param host the encoded host name of the Uri, {@code null} for undefined. + * @param port the port number of the Uri, -1 for undefined. + * @param path the encoded path to the resource on the host. + * @param query the encoded query part of the Uri to specify parameters for the resource. + * @param fragment the encoded fragment part of the Uri. + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri create (final Encoded scheme, final Encoded userinfo, final Encoded host, final int port, + final Encoded path, final Encoded query, final Encoded fragment) throws URISyntaxException { + if ( emptyString(scheme) && emptyString(userinfo) && emptyString(host) && emptyString(path) && + emptyString(query) && emptyString(fragment) ) { + throw new URISyntaxException("", "all empty parts"); + } + + if ( !emptyString(scheme) && !emptyString(path) && path.length() > 0 && path.charAt(0) != '/') { + throw new URISyntaxException(path.get(), "path doesn't start with '/'"); + } + + final StringBuilder uri = new StringBuilder(); + if ( !emptyString(scheme) ) { + uri.append(scheme); + uri.append(SCHEME_SEPARATOR); + } + + if ( !emptyString(userinfo) || !emptyString(host) || port != -1) { + uri.append("//"); + } + + if ( !emptyString(userinfo) ) { + uri.append(userinfo.get()); + uri.append('@'); + } + + if ( !emptyString(host) ) { + uri.append(host.get()); + } + + if ( port != -1 ) { + uri.append(SCHEME_SEPARATOR); + uri.append(port); + } + + if ( !emptyString(path) ) { + uri.append(path.get()); + } + + if ( !emptyString(query) ) { + uri.append(QUERY_SEPARATOR); + uri.append(query.get()); + } + + if ( !emptyString(fragment) ) { + uri.append(FRAGMENT_SEPARATOR); + uri.append(fragment.get()); + } + return new Uri(new Encoded(uri.toString()), true, 0); + } + + /** + * Creates a new Uri instance using the given unencoded arguments. + * <p> + * This constructor first creates a temporary Uri string from the given unencoded components. This * string will be parsed later on to create the Uri instance. * </p> * <p> * {@code [scheme:]host[path][#fragment]} * </p> + * <p> + * {@code host} <i>must</i> be valid, if defined. + * </p> * * @param scheme the unencoded scheme part of the Uri. * @param host the unencoded host name of the Uri. @@ -773,14 +908,46 @@ public class Uri { } /** - * Creates a new Uri instance using the given arguments. + * Creates a new Uri instance using the given encoded arguments. * <p> - * This constructor first creates a temporary Uri string from the given components. This + * This constructor first creates a temporary Uri string from the given encoded components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * The given encoded components are taken as-is, i.e. no re-encoding will be performed! + * However, Uri parsing will re-evaluate encoding of the resulting components. + * </p> + * <p> + * {@code [scheme:]host[path][#fragment]} + * </p> + * <p> + * {@code host} <i>must</i> be valid, if defined. + * </p> + * + * @param scheme the encoded scheme part of the Uri. + * @param host the encoded host name of the Uri. + * @param path the encoded path to the resource on the host. + * @param fragment the encoded fragment part of the Uri. + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri create(final Encoded scheme, final Encoded host, final Encoded path, final Encoded fragment) throws URISyntaxException { + return create(scheme, null, host, -1, path, null, fragment); + } + + /** + * Creates a new Uri instance using the given unencoded arguments. + * <p> + * This constructor first creates a temporary Uri string from the given unencoded components. This * string will be parsed later on to create the Uri instance. * </p> * <p> * {@code [scheme:][//authority][path][?query][#fragment]} * </p> + * <p> + * {@code host} and {@code port} <i>may</i> be undefined or invalid, in the optional {@code authority}. + * </p> * * @param scheme the unencoded scheme part of the Uri. * @param authority the unencoded authority part of the Uri. @@ -830,6 +997,66 @@ public class Uri { } /** + * Creates a new Uri instance using the given encoded arguments. + * <p> + * This constructor first creates a temporary Uri string from the given encoded encoded components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * The given encoded components are taken as-is, i.e. no re-encoding will be performed! + * However, Uri parsing will re-evaluate encoding of the resulting components. + * </p> + * <p> + * {@code [scheme:][//authority][path][?query][#fragment]} + * </p> + * <p> + * {@code host} and {@code port} <i>may</i> be undefined or invalid, in the optional {@code authority}. + * </p> + * + * @param scheme the encoded scheme part of the Uri. + * @param authority the encoded authority part of the Uri. + * @param path the encoded path to the resource on the host. + * @param query the encoded query part of the Uri to specify parameters for the resource. + * @param fragment the encoded fragment part of the Uri. + * + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri create(final Encoded scheme, final Encoded authority, final Encoded path, final Encoded query, final Encoded fragment) throws URISyntaxException { + if ( emptyString(scheme) && emptyString(authority) && emptyString(path) && + emptyString(query) && emptyString(fragment) ) { + throw new URISyntaxException("", "all empty parts"); + } + if ( !emptyString(scheme) && !emptyString(path) && path.length() > 0 && path.charAt(0) != '/') { + throw new URISyntaxException(path.get(), "path doesn't start with '/'"); + } + + final StringBuilder uri = new StringBuilder(); + if ( !emptyString(scheme) ) { + uri.append(scheme); + uri.append(SCHEME_SEPARATOR); + } + if ( !emptyString(authority) ) { + uri.append("//"); + uri.append(authority.get()); + } + + if ( !emptyString(path) ) { + uri.append(path.get()); + } + if ( !emptyString(query) ) { + uri.append(QUERY_SEPARATOR); + uri.append(query.get()); + } + if ( !emptyString(fragment) ) { + uri.append(FRAGMENT_SEPARATOR); + uri.append(fragment.get()); + } + return new Uri(new Encoded(uri.toString()), false, 0); + } + + /** * Casts the given encoded String to a {@link Encoded#cast(String) new Encoded instance} * used to create the resulting Uri instance via {@link #Uri(Encoded)}. * <p> @@ -851,7 +1078,7 @@ public class Uri { * {@code file:path} * </p> * - * @param path the path of the {@code file} {@code schema}. + * @param path the unencoded path of the {@code file} {@code schema}. * @throws URISyntaxException * if the temporary created string doesn't fit to the * specification RFC2396 or could not be parsed correctly. @@ -913,13 +1140,9 @@ public class Uri { // opaque, without host validation. // Note: This may induce encoding errors of authority and path, see {@link #PARSE_HINT_FIX_PATH} return new Uri(new Encoded( uri.toString() ), false, 0); - } else if( null != uri.getHost() ) { - // with host validation - return Uri.create(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), - uri.getPath(), uri.getQuery(), uri.getFragment()); } else { - // without host validation - return Uri.create(uri.getScheme(), uri.getAuthority(), + // with host validation if authority is defined + return Uri.create(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment()); } } @@ -1113,7 +1336,7 @@ public class Uri { * </p> */ public final File toFile() { - if( isFileScheme() ) { + if( isFileScheme() && !emptyString(path) ) { final String authorityS; if( null == authority ) { authorityS = ""; @@ -1154,10 +1377,10 @@ public class Uri { * Returned Uri: <code><i>scheme2</i>:/some/path/gluegen-rt.jar#fragment</code> * * Example 3: - * This instance: <code>scheme1:<i>scheme2</i>:/some/path/gluegen-rt.jar?lala=01#fragment</code> + * This instance: <code>scheme1:<i>scheme2</i>:/some/path/gluegen-rt.jar!/?lala=01#fragment</code> * Returned Uri: <code><i>scheme2</i>:/some/path/gluegen-rt.jar?lala=01#fragment</code> * </pre> - * @throws URISyntaxException + * @throws URISyntaxException if this Uri is a container Uri and does not comply with the container spec, i.e. a JAR Uri */ public final Uri getContainedUri() throws URISyntaxException { if( !emptyString(schemeSpecificPart) ) { @@ -1185,6 +1408,7 @@ public class Uri { } catch(final URISyntaxException e) { // OK, does not contain uri if( DEBUG ) { + System.err.println("Caught "+e.getClass().getSimpleName()+": "+e.getMessage()); e.printStackTrace(); } } @@ -1192,184 +1416,283 @@ public class Uri { return null; } + private static final boolean cutoffLastPathSegementImpl(final StringBuilder pathBuf, + final boolean cutoffFile, + final boolean cutoffDir, + final Encoded appendPath) throws URISyntaxException { + final boolean cleaned; + {// clean-up existing path + final String pathS = pathBuf.toString(); + if( 0 > pathS.indexOf("/") && emptyString(appendPath) ) { + return false; // nothing to cut-off + } + pathBuf.setLength(0); + pathBuf.append( IOUtil.cleanPathString( pathS ) ); + cleaned = pathBuf.length() != pathS.length(); + } + + {// cut-off file or last dir-segment + final String pathS = pathBuf.toString(); + final int jarSepIdx = pathS.lastIndexOf(JAR_SCHEME_SEPARATOR); + final int e = pathS.lastIndexOf("/"); + if( 0 > jarSepIdx || e - 1 > jarSepIdx ) { // stop at jar-separator '!/', if exist + if( cutoffFile && e < pathS.length() - 1 ) { + // cut-off file + pathBuf.setLength(0); + pathBuf.append( pathS.substring(0, e+1) ); + } else if( cutoffDir ) { + // cut-off dir-segment + final int p = pathS.lastIndexOf("/", e-1); + if( p >= 0 ) { + pathBuf.setLength(0); + pathBuf.append( pathS.substring(0, p+1) ); + } // else keep + } // else keep + } + final boolean cutoff = pathBuf.length() != pathS.length(); + if( !cutoff && ( cutoffDir || !cleaned ) && emptyString(appendPath) ) { + return false; // no modifications! + } + } + if( !emptyString(appendPath) ) { + pathBuf.append(appendPath.get()); + // 2nd round of cleaning! + final String pathS = pathBuf.toString(); + pathBuf.setLength(0); + pathBuf.append( IOUtil.cleanPathString( pathS ) ); + } + return true; // continue processing w/ buffer + } + private final Uri cutoffLastPathSegementImpl(final boolean cutoffFile, final boolean cutoffDir, final Encoded appendPath) throws URISyntaxException { + if( opaque ) { + if( emptyString(schemeSpecificPart) ) { + // nothing to cut-off + if( !emptyString(appendPath) ) { + return Uri.create(scheme, appendPath, fragment); + } else { + return null; + } + } + final StringBuilder sspBuf = new StringBuilder(); // without path! + + // save optional query in scheme-specific-part + final Encoded queryTemp; + final int queryI = schemeSpecificPart.lastIndexOf(QUERY_SEPARATOR); + if( queryI >= 0 ) { + queryTemp = schemeSpecificPart.substring(queryI+1); + sspBuf.append( schemeSpecificPart.substring(0, queryI).get() ); + } else { + queryTemp = null; + sspBuf.append( schemeSpecificPart.get() ); + } + + if( !cutoffLastPathSegementImpl(sspBuf, cutoffFile, cutoffDir, appendPath) ) { + return null; // no modifications + } + + if ( !emptyString(queryTemp) ) { + sspBuf.append(QUERY_SEPARATOR); + sspBuf.append( queryTemp.get() ); + } + + // without host validation if authority is defined + return Uri.create(scheme, new Encoded(sspBuf.toString()), fragment); + } else { + if( emptyString(path) ) { + return null; // nothing to cut-off + } + final StringBuilder pathBuf = new StringBuilder(); + pathBuf.append( path.get() ); + + if( !cutoffLastPathSegementImpl(pathBuf, cutoffFile, cutoffDir, appendPath) ) { + return null; // no modifications + } + + // with host validation if authority is defined + return Uri.create(scheme, userInfo, host, port, new Encoded(pathBuf.toString()), query, fragment); + } + } /** - * Return a new Uri instance representing the parent path of this Uri, - * while cutting of optional {@code query} and {@code fragment} parts. - * <p> - * Method is {@code jar-file-entry} aware, i.e. will return the parent entry if exists. - * </p> + * {@link IOUtil#cleanPathString(String) Normalizes} this Uri's path and return the + * {@link IOUtil#cleanPathString(String) normalized} form if it differs, otherwise {@code this} instance. * <p> - * If this Uri does not contain any path separator, or a parent folder Uri cannot be found, method returns {@code null}. - * </p> * <pre> * Example-1: - * This instance : <code>jar:http://some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class</code> - * Returned Uri #1: <code>jar:http://some/path/gluegen-rt.jar!/com/jogamp/common/</code> - * Returned Uri #2: <code>jar:http://some/path/gluegen-rt.jar!/com/jogamp/</code> + * This instance : <code>jar:http://some/path/../gluegen-rt.jar!/com/Test.class?arg=1#frag</code> + * Normalized : <code>jar:http://some/gluegen-rt.jar!/com/Test.class?arg=1#frag</code> * * Example-2: - * This instance : <code>http://some/path/gluegen-rt.jar</code> - * Returned Uri #1: <code>http://some/path/</code> - * Returned Uri #2: <code>http://some/</code> + * This instance : <code>http://some/path/../gluegen-rt.jar?arg=1#frag</code> + * Normalized : <code>http://some/gluegen-rt.jar?arg=1#frag</code> * </pre> + * </p> */ - public final Uri getParent() { - final int pl = null!=schemeSpecificPart? schemeSpecificPart.length() : 0; - if(pl != 0) { - final int e = schemeSpecificPart.lastIndexOf("/"); - if( e > 0 ) { // 0 == e: no path - if( e < pl - 1 ) { - // path is file or has a query - try { - return new Uri( new Encoded( scheme.get()+SCHEME_SEPARATOR+schemeSpecificPart.get().substring(0, e+1) ) ); - } catch (final URISyntaxException ue) { - // not complete, hence removed authority, or even root folder -> return null - } - } - // path is a directory .. - final int p = schemeSpecificPart.lastIndexOf("/", e-1); - if( p > 0 ) { - try { - return new Uri( new Encoded( scheme.get()+SCHEME_SEPARATOR+schemeSpecificPart.get().substring(0, p+1) ) ); - } catch (final URISyntaxException ue) { - // not complete, hence removed authority, or even root folder -> return null - } - } + public final Uri getNormalized() { + try { + final Uri res = cutoffLastPathSegementImpl(false, false, null); + return null != res ? res : this; + } catch (final URISyntaxException e) { + if( DEBUG ) { + System.err.println("Caught "+e.getClass().getSimpleName()+": "+e.getMessage()); + e.printStackTrace(); } - } - return null; - } - - /** - * Concatenates the given encoded string to the {@link #getEncoded() encoded uri} - * of this instance and returns {@link #Uri(Encoded) a new Uri instance} with the result. - * - * @throws URISyntaxException - * if the concatenated string {@code uri} doesn't fit to the - * specification RFC2396 and RFC3986 or could not be parsed correctly. - */ - public final Uri concat(final Uri.Encoded suffix) throws URISyntaxException { - if( null == suffix ) { return this; - } else { - return new Uri( input.concat(suffix) ); } } /** - * Returns a new Uri instance w/ the given new query {@code newQuery}. + * Returns this Uri's directory Uri. + * <p> + * This Uri path will be {@link IOUtil#cleanPathString(String) normalized} before returning the directory. + * </p> + * <p> + * If this Uri's directory cannot be found, or already denotes a directory, method returns {@code this} instance. + * </p> + * <p> + * <pre> + * Example-1: + * this-uri: http:/some/path/gluegen-rt.jar?arg=1#frag + * result: http:/some/path/?arg=1#frag * - * @throws URISyntaxException if this Uri is {@link #opaque} - * or if the new string {@code uri} doesn't fit to the - * specification RFC2396 and RFC3986 or could not be parsed correctly. + * Example-2: + * this-uri: file:/some/path/ + * result: file:/some/path/ + * + * Example-3: + * this-uri: file:/some/path/lala/lili/../../hello.txt + * result: file:/some/path/ + * </pre> + * </p> + * @throws URISyntaxException if the new string {@code uri} doesn't fit to the + * specification RFC2396 and RFC3986 or could not be parsed correctly. */ - public final Uri getNewQuery(final String newQuery) throws URISyntaxException { - if( opaque ) { - throw new URISyntaxException(input.decode(), "Opaque Uri cannot permute by query"); - } else if( null != host ) { - // with host validation - return Uri.create(decode(scheme), decode(userInfo), decode(host), port, - decode(path), newQuery, decode(fragment)); - } else { - // without host validation - return Uri.create(decode(scheme), decode(authority), - decode(path), newQuery, decode(fragment)); + public Uri getDirectory() { + try { + final Uri res = cutoffLastPathSegementImpl(true, false, null); + return null != res ? res : this; + } catch (final URISyntaxException e) { + if( DEBUG ) { + System.err.println("Caught "+e.getClass().getSimpleName()+": "+e.getMessage()); + e.printStackTrace(); + } + return this; } } - /// NEW START - /** - * The URI's <code><i>protocol</i>:/some/path/gluegen-rt.jar</code> - * parent dirname URI <code><i>protocol</i>:/some/path/</code> will be returned, - * or {@code null} if not applicable. + * Returns this Uri's parent directory Uri.. * <p> - * <i>protocol</i> may be "file", "http", etc.. + * This Uri path will be {@link IOUtil#cleanPathString(String) normalized} before traversing up one directory. * </p> + * <p> + * If a parent folder cannot be found, method returns {@code null}. + * </p> + * <p> + * <pre> + * Example-1: + * This instance : <code>jar:http://some/path/gluegen-rt.jar!/com/Test.class?arg=1#frag</code> + * Returned Uri #1: <code>jar:http://some/path/gluegen-rt.jar!/com/?arg=1#frag</code> + * Returned Uri #2: <code>jar:http://some/path/gluegen-rt.jar!/?arg=1#frag</code> + * Returned Uri #3: <code>null</code> * - * @return "<i>protocol</i>:/some/path/" - * @throws IllegalArgumentException if the URI doesn't match the expected formatting, or is null - * @throws URISyntaxException + * Example-2: + * This instance : <code>http://some/path/gluegen-rt.jar?arg=1#frag</code> + * Returned Uri #1: <code>http://some/path/?arg=1#frag</code> + * Returned Uri #2: <code>http://some/?arg=1#frag</code> + * Returned Uri #2: <code>null</code> + * + * Example-3: + * This instance : <code>http://some/path/../gluegen-rt.jar?arg=1#frag</code> + * Returned Uri #1: <code>http://some/?arg=1#frag</code> + * Returned Uri #2: <code>null</code> + * </pre> + * </p> */ - public Uri getDirectory() throws URISyntaxException { - final String uriS = input.get(); - - // from - // file:/some/path/gluegen-rt.jar _or_ rsrc:gluegen-rt.jar - // to - // file:/some/path/ _or_ rsrc: - int idx = uriS.lastIndexOf('/'); - if(0 > idx) { - // no abs-path, check for protocol terminator ':' - idx = uriS.lastIndexOf(':'); - if(0 > idx) { - throw new URISyntaxException(input.get(), "no scheme terminator ':'"); - } - } + public final Uri getParent() { try { - return Uri.cast(uriS.substring(0, idx+1)); // exclude jar name, include terminal '/' or ':' - } catch (final URISyntaxException ue) { + return cutoffLastPathSegementImpl(true, true, null); + } catch (final URISyntaxException e) { if( DEBUG ) { - ue.printStackTrace(); + System.err.println("Caught "+e.getClass().getSimpleName()+": "+e.getMessage()); + e.printStackTrace(); } + return null; } - return null; } /** - * Generates a URI for the <i>relativePath</i> relative to the <i>baseURI</i>, - * hence the result is a absolute location. + * Returns a new Uri appending the given {@code appendPath} + * to this instance's {@link #getDirectory() directory}. * <p> - * Impl. operates on the <i>scheme-specific-part</i>, and hence is sub-protocol savvy. + * If {@code appendPath} is empty, method behaves like {@link #getNormalized()}. * </p> * <p> - * In case <i>baseURI</i> is not a path ending w/ '/', it's a assumed to be a file and it's parent is being used. + * This resulting path will be {@link IOUtil#cleanPathString(String) normalized}. * </p> + * <p> + * <pre> + * Example-1: + * append: null + * this-uri: http:/some/path/gluegen-rt.jar + * result: http:/some/path/gluegen-rt.jar * - * @param baseURI denotes a URI to a directory ending w/ '/', or a file. In the latter case the file's directory is being used. - * @param relativePath denotes a relative file to the baseLocation's parent directory (URI encoded) - * @throws URISyntaxException if path is empty or has no parent directory available while resolving <code>../</code> + * Example-2: + * append: test.txt + * this-uri: file:/some/path/gluegen-rt.jar + * result: file:/some/path/test.txt + * + * Example-3: + * append: test.txt + * this-uri: file:/some/path/lala/lili/../../hello.txt + * result: file:/some/path/test.txt + * </pre> + * </p> + * + * @param appendPath denotes a relative path to be appended to this Uri's directory + * @throws URISyntaxException + * if the resulting {@code uri} doesn't fit to the + * specification RFC2396 and RFC3986 or could not be parsed correctly. */ - public Uri getRelativeOf(final Encoded relativePath) throws URISyntaxException { - return compose(scheme, schemeSpecificPart, relativePath, fragment); + public Uri getRelativeOf(final Encoded appendPath) throws URISyntaxException { + if( emptyString(appendPath) ) { + return getNormalized(); + } else { + return cutoffLastPathSegementImpl(true, false, appendPath); + } } - static Uri compose(final Encoded scheme, final Encoded schemeSpecificPart, final Encoded relativePath, final Encoded fragment) throws URISyntaxException { - String schemeSpecificPartS = schemeSpecificPart.get(); - - // cut off optional query in scheme-specific-part - final String query; - final int queryI = schemeSpecificPartS.lastIndexOf(QUERY_SEPARATOR); - if( queryI >= 0 ) { - query = schemeSpecificPartS.substring(queryI+1); - schemeSpecificPartS = schemeSpecificPartS.substring(0, queryI); + /** + * Concatenates the given encoded string to the {@link #getEncoded() encoded uri} + * of this instance and returns {@link #Uri(Encoded) a new Uri instance} with the result. + * + * @throws URISyntaxException + * if the concatenated string {@code uri} doesn't fit to the + * specification RFC2396 and RFC3986 or could not be parsed correctly. + */ + public final Uri concat(final Encoded suffix) throws URISyntaxException { + if( null == suffix ) { + return this; } else { - query = null; - } - if( null != relativePath ) { - if( !schemeSpecificPartS.endsWith("/") ) { - schemeSpecificPartS = IOUtil.getParentOf(schemeSpecificPartS); - } - schemeSpecificPartS = schemeSpecificPartS + relativePath.get(); - } - schemeSpecificPartS = IOUtil.cleanPathString( schemeSpecificPartS ); - final StringBuilder uri = new StringBuilder(); - uri.append(scheme.get()); - uri.append(':'); - uri.append(schemeSpecificPartS); - if ( null != query ) { - uri.append(QUERY_SEPARATOR); - uri.append(query); - } - if ( null != fragment ) { - uri.append(FRAGMENT_SEPARATOR); - uri.append(fragment.get()); + return new Uri( input.concat(suffix) ); } - return Uri.cast(uri.toString()); } - /// NEW END + /** + * Returns a new Uri instance w/ the given new query {@code newQuery}. + * + * @throws URISyntaxException if this Uri is {@link #opaque} + * or if the new string {@code uri} doesn't fit to the + * specification RFC2396 and RFC3986 or could not be parsed correctly. + */ + public final Uri getNewQuery(final Encoded newQuery) throws URISyntaxException { + if( opaque ) { + throw new URISyntaxException(input.decode(), "Opaque Uri cannot permute by query"); + } else { + // with host validation if authority is defined + return Uri.create(scheme, userInfo, host, port, path, newQuery, fragment); + } + } /** * {@inheritDoc} diff --git a/src/java/com/jogamp/common/net/UriQueryProps.java b/src/java/com/jogamp/common/net/UriQueryProps.java index 8d9bcb4..a93a1eb 100644 --- a/src/java/com/jogamp/common/net/UriQueryProps.java +++ b/src/java/com/jogamp/common/net/UriQueryProps.java @@ -63,14 +63,14 @@ public class UriQueryProps { public final Map<String, String> getProperties() { return properties; } public final char getQuerySeparator() { return query_separator.charAt(0); } - public final String appendQuery(String baseQuery) { + public final Uri.Encoded appendQuery(Uri.Encoded baseQuery) { boolean needsSep = false; final StringBuilder sb = new StringBuilder(); if ( null != baseQuery ) { if( baseQuery.startsWith(QMARK) ) { baseQuery = baseQuery.substring(1); // cut off '?' } - sb.append(baseQuery); + sb.append(baseQuery.get()); if( !baseQuery.endsWith(query_separator) ) { needsSep = true; } @@ -87,11 +87,11 @@ public class UriQueryProps { } needsSep = true; } - return sb.toString(); + return new Uri.Encoded(sb.toString(), Uri.QUERY_LEGAL); } public final Uri appendQuery(final Uri base) throws URISyntaxException { - return base.getNewQuery( appendQuery( Uri.decode(base.query) ) ); + return base.getNewQuery( appendQuery( base.query ) ); } /** diff --git a/src/junit/com/jogamp/common/net/TestUri01.java b/src/junit/com/jogamp/common/net/TestUri01.java index bcc7d27..a59409f 100644 --- a/src/junit/com/jogamp/common/net/TestUri01.java +++ b/src/junit/com/jogamp/common/net/TestUri01.java @@ -220,6 +220,8 @@ public class TestUri01 extends JunitTracer { final Uri input = Uri.cast("jar:http://localhost/test01.jar!/com/jogamp/Lala.class#tag01"); final Uri expected = Uri.cast("http://localhost/test01.jar#tag01"); final Uri contained = input.getContainedUri(); + URIDumpUtil.showUri(input); + URIDumpUtil.showUri(contained); Assert.assertEquals(expected, contained); Assert.assertEquals(expected.hashCode(), contained.hashCode()); } @@ -227,6 +229,8 @@ public class TestUri01 extends JunitTracer { final Uri input = Uri.cast("jar:file://localhost/test01.jar!/"); final Uri expected = Uri.cast("file://localhost/test01.jar"); final Uri contained = input.getContainedUri(); + URIDumpUtil.showUri(input); + URIDumpUtil.showUri(contained); Assert.assertEquals(expected, contained); Assert.assertEquals(expected.hashCode(), contained.hashCode()); } @@ -234,71 +238,191 @@ public class TestUri01 extends JunitTracer { final Uri input = Uri.cast("sftp:http://localhost/test01.jar?lala=01#tag01"); final Uri expected = Uri.cast("http://localhost/test01.jar?lala=01#tag01"); final Uri contained = input.getContainedUri(); + URIDumpUtil.showUri(input); + URIDumpUtil.showUri(contained); Assert.assertEquals(expected, contained); Assert.assertEquals(expected.hashCode(), contained.hashCode()); } } @Test - public void test06ParentAndDir() throws IOException, URISyntaxException { + public void test08NormalizedHierarchy() throws IOException, URISyntaxException { + { + final Uri input = Uri.cast("http://localhost/dummy/../"); + final Uri expected = Uri.cast("http://localhost/"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + { + final Uri input = Uri.cast("http://localhost/test/dummy/../text.txt"); + final Uri expected = Uri.cast("http://localhost/test/text.txt"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + { + final Uri input = Uri.cast("http://localhost/test/dummy/../text.txt?lala=01&lili=02#frag01"); + final Uri expected = Uri.cast("http://localhost/test/text.txt?lala=01&lili=02#frag01"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + } + + @Test + public void test09NormalizedOpaque() throws IOException, URISyntaxException { + { + final Uri input = Uri.cast("jar:http://localhost/dummy/../abc.jar!/"); + final Uri expected = Uri.cast("jar:http://localhost/abc.jar!/"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + { + final Uri input = Uri.cast("jar:http://localhost/test/dummy/../abc.jar!/"); + final Uri expected = Uri.cast("jar:http://localhost/test/abc.jar!/"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + { + final Uri input = Uri.cast("jar:http://localhost/test/dummy/../abc.jar!/a/b/C.class"); + final Uri expected = Uri.cast("jar:http://localhost/test/abc.jar!/a/b/C.class"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + { + final Uri input = Uri.cast("jar:http://localhost/test/dummy/../abc.jar!/a/b/C.class?lala=01&lili=02#frag01"); + final Uri expected = Uri.cast("jar:http://localhost/test/abc.jar!/a/b/C.class?lala=01&lili=02#frag01"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + } + + @Test + public void test10ParentAndDirHierarchy() throws IOException, URISyntaxException { { final Uri input = Uri.cast("http://localhost/"); + URIDumpUtil.showUri(input); + final Uri directory = input.getDirectory(); + Assert.assertEquals(input, directory); final Uri parent = input.getParent(); Assert.assertNull(parent); } { - final Uri input = Uri.cast("jar:http://localhost/test01.jar!/com/Lala.class"); - final Uri expParen1 = Uri.cast("jar:http://localhost/test01.jar!/com/"); + final Uri input = Uri.cast("http://localhost/dummy/../test/"); + final Uri expectedD = Uri.cast("http://localhost/test/"); + final Uri expectedP = Uri.cast("http://localhost/"); + URIDumpUtil.showUri(input); + final Uri directory = input.getDirectory(); + Assert.assertEquals(expectedD, directory); + final Uri parent = input.getParent(); + Assert.assertEquals(expectedP, parent); + } + { + final Uri input = Uri.cast("http://localhost/dummy/../test/dummy/../"); + final Uri expectedD = Uri.cast("http://localhost/test/"); + final Uri expectedP = Uri.cast("http://localhost/"); + URIDumpUtil.showUri(input); + final Uri directory = input.getDirectory(); + Assert.assertEquals(expectedD, directory); + final Uri parent = input.getParent(); + Assert.assertEquals(expectedP, parent); + } + { + final Uri input = Uri.cast("http://localhost/dir/test01.jar?lala=01#frag01"); + final Uri expParen1 = Uri.cast("http://localhost/dir/?lala=01#frag01"); final Uri expFolde1 = expParen1; - final Uri expParen2 = Uri.cast("jar:http://localhost/test01.jar!/"); + final Uri expParen2 = Uri.cast("http://localhost/?lala=01#frag01"); final Uri expFolde2 = expParen1; // is folder already - final Uri expParen3 = Uri.cast("jar:http://localhost/"); - final Uri expFolde3 = expParen2; // is folder already + final Uri expParen3 = null; + final Uri expFolde3 = expParen2; Assert.assertNotEquals(input, expParen1); Assert.assertNotEquals(expParen1, expParen2); Assert.assertNotEquals(expParen1, expParen3); + URIDumpUtil.showUri(input); final Uri parent1 = input.getParent(); - final Uri folder1 = input.getDirectory(); - final Uri parent2 = parent1.getParent(); - final Uri folder2 = parent1.getDirectory(); - final Uri parent3 = parent2.getParent(); - final Uri folder3 = parent2.getDirectory(); - Assert.assertEquals(expParen1, parent1); Assert.assertEquals(expParen1.hashCode(), parent1.hashCode()); + final Uri folder1 = input.getDirectory(); Assert.assertEquals(expFolde1, folder1); + final Uri parent2 = parent1.getParent(); Assert.assertEquals(expParen2, parent2); Assert.assertEquals(expParen2.hashCode(), parent2.hashCode()); + final Uri folder2 = parent1.getDirectory(); Assert.assertEquals(expFolde2, folder2); - Assert.assertEquals(expParen3, parent3); - Assert.assertEquals(expParen3.hashCode(), parent3.hashCode()); - Assert.assertEquals(expFolde3, folder3); + final Uri parent3 = parent2.getParent(); + Assert.assertEquals(expParen3, parent3); // NULL! + final Uri folder3 = parent2.getDirectory(); + Assert.assertEquals(expFolde3, folder3); // NULL! + } + } + @Test + public void test11ParentAndDirOpaque() throws IOException, URISyntaxException { + { + final Uri input = Uri.cast("jar:http://localhost/test.jar!/"); + URIDumpUtil.showUri(input); + final Uri directory = input.getDirectory(); + Assert.assertEquals(input, directory); + final Uri parent = input.getParent(); + Assert.assertNull(parent); } { - final Uri input = Uri.cast("http://localhost/dir/test01.jar?lala=01#frag01"); - final Uri expParen1 = Uri.cast("http://localhost/dir/"); + final Uri input = Uri.cast("jar:http://localhost/dummy/../test/test.jar!/"); + final Uri expectedD = Uri.cast("jar:http://localhost/test/test.jar!/"); + final Uri expectedP = null; + URIDumpUtil.showUri(input); + final Uri directory = input.getDirectory(); + Assert.assertEquals(expectedD, directory); + final Uri parent = input.getParent(); + Assert.assertEquals(expectedP, parent); + } + { + final Uri input = Uri.cast("jar:http://localhost/dummy/../test/dummy/../test.jar!/a/b/C.class"); + final Uri expectedD = Uri.cast("jar:http://localhost/test/test.jar!/a/b/"); + final Uri expectedP = Uri.cast("jar:http://localhost/test/test.jar!/a/b/"); + URIDumpUtil.showUri(input); + final Uri directory = input.getDirectory(); + Assert.assertEquals(expectedD, directory); + final Uri parent = input.getParent(); + Assert.assertEquals(expectedP, parent); + } + { + final Uri input = Uri.cast("jar:http://localhost/test01.jar!/com/Lala.class?lala=01#frag01"); + final Uri expParen1 = Uri.cast("jar:http://localhost/test01.jar!/com/?lala=01#frag01"); final Uri expFolde1 = expParen1; - final Uri expParen2 = Uri.cast("http://localhost/"); + final Uri expParen2 = Uri.cast("jar:http://localhost/test01.jar!/?lala=01#frag01"); final Uri expFolde2 = expParen1; // is folder already + final Uri expParen3 = null; + final Uri expFolde3 = expParen2; // is folder already Assert.assertNotEquals(input, expParen1); Assert.assertNotEquals(expParen1, expParen2); + Assert.assertNotEquals(expParen1, expParen3); + URIDumpUtil.showUri(input); final Uri parent1 = input.getParent(); - final Uri folder1 = input.getDirectory(); - final Uri parent2 = parent1.getParent(); - final Uri folder2 = parent1.getDirectory(); - Assert.assertEquals(expParen1, parent1); Assert.assertEquals(expParen1.hashCode(), parent1.hashCode()); + final Uri folder1 = input.getDirectory(); Assert.assertEquals(expFolde1, folder1); + final Uri parent2 = parent1.getParent(); Assert.assertEquals(expParen2, parent2); Assert.assertEquals(expParen2.hashCode(), parent2.hashCode()); + final Uri folder2 = parent1.getDirectory(); Assert.assertEquals(expFolde2, folder2); + + final Uri parent3 = parent2.getParent(); + Assert.assertEquals(expParen3, parent3); // NULL + final Uri folder3 = parent2.getDirectory(); + Assert.assertEquals(expFolde3, folder3); } } diff --git a/src/junit/com/jogamp/common/net/TestUri02Composing.java b/src/junit/com/jogamp/common/net/TestUri02Composing.java index 33d17b8..50e8e07 100644 --- a/src/junit/com/jogamp/common/net/TestUri02Composing.java +++ b/src/junit/com/jogamp/common/net/TestUri02Composing.java @@ -61,7 +61,7 @@ public class TestUri02Composing extends JunitTracer { } static void testUriCompositioning(final Uri refURI, final Uri uri1) throws MalformedURLException, URISyntaxException { System.err.println("scheme <"+uri1.scheme+">, ssp <"+uri1.schemeSpecificPart+">, fragment <"+uri1.fragment+">"); - final Uri uri2 = Uri.compose(uri1.scheme, uri1.schemeSpecificPart, null, uri1.fragment); + final Uri uri2 = uri1.getRelativeOf(null); System.err.println("URL-equals: "+refURI.equals(uri2)); System.err.println("URL-ref : <"+refURI+">"); @@ -76,7 +76,7 @@ public class TestUri02Composing extends JunitTracer { static void testURLCompositioning(final URL refURL, final URL url1) throws MalformedURLException, URISyntaxException { final Uri uri1 = Uri.valueOf(url1); System.err.println("scheme <"+uri1.scheme+">, ssp <"+uri1.schemeSpecificPart+">, fragment <"+uri1.fragment+">"); - final Uri uri2 = Uri.compose(uri1.scheme, uri1.schemeSpecificPart, null, uri1.fragment); + final Uri uri2 = uri1.getRelativeOf(null); System.err.println("URL-equals(1): "+refURL.toURI().equals(uri2)); System.err.println("URL-equals(2): "+refURL.equals(uri2.toURL())); |