diff options
author | Sven Gothel <[email protected]> | 2014-09-07 10:20:12 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2014-09-07 10:20:12 +0200 |
commit | 5205e47e8a2e84e793b26305391b1c4f8648597c (patch) | |
tree | 2487d359e99ce72f3dfe29f795da6d09f44fe54a | |
parent | 0b07f9107c5b033913f5c4cbeb906ae6dafc2d77 (diff) |
Bug 1063 - Utilize own Uri and Uri.Encoded class w/ proper encoding and differentiating encoded/decoded variants by type [Part 1]
- Add immutable com.jogamp.common.net.Uri class impl. RFC 2396 w/ encoding of RFC 3986
- Class Uri.Encoded is used to represent encoded parts
- IOUtil: Remove unused methods (residing in Uri) and mark others deprecated (will move to Uri)
- Adopt usage of Uri: This must be completet in follow-up commits!
18 files changed, 2636 insertions, 318 deletions
diff --git a/LICENSE.txt b/LICENSE.txt index 8be18df..ba830c7 100755 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,7 +1,13 @@ - The GlueGen source code is mostly licensed under the New BSD 2-clause license, however it contains other licensed material as well. +Other licensed material is compatible with the 'New BSD 2-Clause License', +if not stated otherwise. + +'New BSD 2-Clause License' incompatible materials are optional, they are: + + NONE + Below you find a detailed list of licenses used in this project. +++ @@ -131,3 +137,23 @@ L.3) The GlueGen source tree contains CGRAM http://www.antlr.org/grammar/cgram/, ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +A.1) The GlueGen source tree contains code from The Apache Software Foundation + which is covered by the Apache License Version 2.0 + + Apache Harmony - Open Source Java SE + ===================================== + + <http://harmony.apache.org/> + + Author: The Apache Software Foundation (http://www.apache.org/). + + Copyright 2006, 2010 The Apache Software Foundation. + + Apache License Version 2.0, January 2004 + http://www.apache.org/licenses/LICENSE-2.0 + Or within this repository: doc/licenses/Apache.LICENSE-2.0 + + Files: + src/java/com/jogamp/common/net/Uri.java + + diff --git a/make/scripts/runtest-x64.bat b/make/scripts/runtest-x64.bat index 6a5d79c..f88e795 100755 --- a/make/scripts/runtest-x64.bat +++ b/make/scripts/runtest-x64.bat @@ -6,10 +6,10 @@ REM scripts\java-win64.bat com.jogamp.common.util.TestTempJarCache REM scripts\java-win64.bat com.jogamp.common.os.TestElfReader01 REM scripts\java-win64.bat com.jogamp.common.util.TestIOUtil01 -REM scripts\java-win64.bat com.jogamp.common.util.TestIOUtilURICompose -REM scripts\java-win64.bat com.jogamp.common.util.TestIOUtilURIHandling REM scripts\java-win64.bat com.jogamp.common.net.TestUrisWithAssetHandler REM scripts\java-win64.bat com.jogamp.common.net.TestURIQueryProps -scripts\java-win64.bat com.jogamp.common.net.TestNetIOURIReservedCharsBug908 -REM scripts\java-win64.bat com.jogamp.common.util.TestIOUtilURIHandling +REM scripts\java-win64.bat com.jogamp.common.net.TestUri01 +REM scripts\java-win64.bat com.jogamp.common.net.TestUri02Composing +REM scripts\java-win64.bat com.jogamp.common.net.TestUri03Resolving +scripts\java-win64.bat com.jogamp.common.net.TestUri99LaunchOnReservedCharPathBug908 diff --git a/make/scripts/runtest.sh b/make/scripts/runtest.sh index 4bec5e7..c996305 100755 --- a/make/scripts/runtest.sh +++ b/make/scripts/runtest.sh @@ -98,8 +98,6 @@ function onetest() { #onetest com.jogamp.common.util.TestPlatform01 2>&1 | tee -a $LOG #onetest com.jogamp.common.util.TestRunnableTask01 2>&1 | tee -a $LOG #onetest com.jogamp.common.util.TestIOUtil01 2>&1 | tee -a $LOG -#onetest com.jogamp.common.util.TestIOUtilURICompose 2>&1 | tee -a $LOG -#onetest com.jogamp.common.util.TestIOUtilURIHandling 2>&1 | tee -a $LOG #onetest com.jogamp.common.util.TestTempJarCache 2>&1 | tee -a $LOG #onetest com.jogamp.common.util.TestJarUtil 2>&1 | tee -a $LOG #onetest com.jogamp.common.util.TestValueConversion 2>&1 | tee -a $LOG @@ -112,9 +110,12 @@ function onetest() { #onetest com.jogamp.common.util.TestBitstream04 2>&1 | tee -a $LOG #onetest com.jogamp.common.net.TestUrisWithAssetHandler 2>&1 | tee -a $LOG #onetest com.jogamp.common.net.TestURIQueryProps 2>&1 | tee -a $LOG +#onetest com.jogamp.common.net.TestUri01 2>&1 | tee -a $LOG +#onetest com.jogamp.common.net.TestUri02Composing 2>&1 | tee -a $LOG +onetest com.jogamp.common.net.TestUri03Resolving 2>&1 | tee -a $LOG +#onetest com.jogamp.common.net.TestUri99LaunchOnReservedCharPathBug908 2>&1 | tee -a $LOG #onetest com.jogamp.common.net.AssetURLConnectionUnregisteredTest 2>&1 | tee -a $LOG #onetest com.jogamp.common.net.AssetURLConnectionRegisteredTest 2>&1 | tee -a $LOG -onetest com.jogamp.common.net.TestNetIOURIReservedCharsBug908 2>&1 | tee -a $LOG #onetest com.jogamp.junit.sec.TestSecIOUtil01 2>&1 | tee -a $LOG #onetest com.jogamp.common.nio.TestBuffersFloatDoubleConversion 2>&1 | tee -a $LOG #onetest com.jogamp.common.nio.TestPointerBufferEndian 2>&1 | tee -a $LOG diff --git a/src/java/com/jogamp/common/net/AssetURLContext.java b/src/java/com/jogamp/common/net/AssetURLContext.java index 2ada3c6..af90c01 100644 --- a/src/java/com/jogamp/common/net/AssetURLContext.java +++ b/src/java/com/jogamp/common/net/AssetURLContext.java @@ -185,7 +185,7 @@ public abstract class AssetURLContext implements PiggybackURLContext { try { final File file = new File(path); if(file.exists()) { - url = IOUtil.toURISimple(file).toURL(); + url = Uri.valueOf(file).toURL(); conn = open(url); type = null != conn ? 3 : -1; } diff --git a/src/java/com/jogamp/common/net/Uri.java b/src/java/com/jogamp/common/net/Uri.java new file mode 100644 index 0000000..b116cb4 --- /dev/null +++ b/src/java/com/jogamp/common/net/Uri.java @@ -0,0 +1,2085 @@ +/** + * Copyright 2013 JogAmp Community. All rights reserved. + * + * This code is derived from the Apache Harmony project's {@code class java.net.URI.Helper}, + * copyright 2006, 2010 The Apache Software Foundation (http://www.apache.org/). + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the LICENSE.txt file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jogamp.common.net; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.StringTokenizer; +import java.util.regex.Pattern; + +import com.jogamp.common.util.IOUtil; + +/** + * This class implements an immutable Uri as defined by <a href="https://tools.ietf.org/html/rfc2396">RFC 2396</a>. + * <p> + * Character encoding is employed as defined by <a href="https://tools.ietf.org/html/rfc3986">RFC 3986</a>, + * see <a href="https://tools.ietf.org/html/rfc3986#section-2.1">RFC 3986 section 2.1</a>, + * while multibyte unicode characters are preserved in encoded parts. + * </p> + * + * <pre> + 1 [scheme:]scheme-specific-part[#fragment] + 2 [scheme:][//authority]path[?query][#fragment] + 3 [scheme:][//[user-info@]host[:port]]path[?query][#fragment] + + scheme-specific-part: [//authority]path[?query] + authority: [user-info@]host[:port] + * </pre> + * <p> + * <a href="https://tools.ietf.org/html/rfc3986#section-2.2">RFC 3986 section 2.2</a> <i>Reserved Characters</i> (January 2005) + * <table border="1"> + <tr> + <td><code>!</code></td> + <td><code>*</code></td> + <td><code>'</code></td> + <td><code>(</code></td> + <td><code>)</code></td> + <td><code>;</code></td> + <td><code>:</code></td> + <td><code>@</code></td> + <td><code>&</code></td> + <td><code>=</code></td> + <td><code>+</code></td> + <td><code>$</code></td> + <td><code>,</code></td> + <td><code>/</code></td> + <td><code>?</code></td> + <td><code>#</code></td> + <td><code>[</code></td> + <td><code>]</code></td> + </tr> + * </table> + * </p> + * <p> + * <a href="https://tools.ietf.org/html/rfc3986#section-2.3">RFC 3986 section 2.3</a> <i>Unreserved Characters</i> (January 2005) + * <table border="1"> + <tr> + <td><code>A</code></td> + <td><code>B</code></td> + <td><code>C</code></td> + <td><code>D</code></td> + <td><code>E</code></td> + <td><code>F</code></td> + <td><code>G</code></td> + <td><code>H</code></td> + <td><code>I</code></td> + <td><code>J</code></td> + <td><code>K</code></td> + <td><code>L</code></td> + <td><code>M</code></td> + <td><code>N</code></td> + <td><code>O</code></td> + <td><code>P</code></td> + <td><code>Q</code></td> + <td><code>R</code></td> + <td><code>S</code></td> + <td><code>T</code></td> + <td><code>U</code></td> + <td><code>V</code></td> + <td><code>W</code></td> + <td><code>X</code></td> + <td><code>Y</code></td> + <td><code>Z</code></td> + </tr> + <tr> + <td><code>a</code></td> + <td><code>b</code></td> + <td><code>c</code></td> + <td><code>d</code></td> + <td><code>e</code></td> + <td><code>f</code></td> + <td><code>g</code></td> + <td><code>h</code></td> + <td><code>i</code></td> + <td><code>j</code></td> + <td><code>k</code></td> + <td><code>l</code></td> + <td><code>m</code></td> + <td><code>n</code></td> + <td><code>o</code></td> + <td><code>p</code></td> + <td><code>q</code></td> + <td><code>r</code></td> + <td><code>s</code></td> + <td><code>t</code></td> + <td><code>u</code></td> + <td><code>v</code></td> + <td><code>w</code></td> + <td><code>x</code></td> + <td><code>y</code></td> + <td><code>z</code></td> + </tr> + <tr> + <td><code>0</code></td> + <td><code>1</code></td> + <td><code>2</code></td> + <td><code>3</code></td> + <td><code>4</code></td> + <td><code>5</code></td> + <td><code>6</code></td> + <td><code>7</code></td> + <td><code>8</code></td> + <td><code>9</code></td> + <td><code>-</code></td> + <td><code>_</code></td> + <td><code>.</code></td> + <td><code>~</code></td> + </tr> + * </table> + * </p> + * <p> + * Other characters in a Uri must be percent encoded. + * </p> + * @since 2.2.1 + */ +public class Uri { + private static final String DIGITS = "0123456789ABCDEF"; + + private static final String ENCODING = "UTF8"; + private static final String MSG_ENCODING_NA = "Charset UTF8 not available"; + private static final Pattern patternSingleFS = Pattern.compile("/{1}"); + + /** + * RFC 3986 section 2.3 Unreserved Characters (January 2005) + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String UNRESERVED = "_-.~"; // Harmony: "_-!.~\'()*" + + private static final String punct = ",;:$&+="; + + /** + * RFC 3986 section 2.2 Reserved Characters (January 2005) + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String RESERVED = punct + "!*\'()@/?#[]"; // Harmony: punct + "?/[]@"; + + public static final String RESERVED_2 = punct + "!*\'()@/?[]"; // Harmony: punct + "?/[]@"; + + // Bug 908, issues w/ windows file path char: $ ^ ~ # [ ] + + /** + * Valid charset for RFC 2396 {@code authority}'s {@code user-info}, + * additional to legal {@code alphanum} characters. + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String USERINFO_LEGAL = UNRESERVED + punct; + + /** + * Valid charset for RFC 2396 {@code authority}, + * additional to legal {@code alphanum} characters. + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String AUTHORITY_LEGAL = "@[]" + USERINFO_LEGAL; + + /** + * Valid charset for RFC 2396 {@code path}, + * additional to legal {@code alphanum} characters. + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String PATH_LEGAL = "/!" + USERINFO_LEGAL; + // @ is reserved; Harmony: "/@" + SOME_LEGAL, '!' is re-added, Harmony had it in UNRESERVED + + /** + * Reduced valid charset for RFC 2396 {@code path}, + * additional to legal {@code alphanum} characters. + * <p> + * Excluding special native filesystem characters to be encoded. + * </p> + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String PATH_MIN_LEGAL = "/!_-."; // "/!" + ( unreserved - '~' ) + + /** + * Valid charset for RFC 2396 {@code query}, + * additional to legal {@code alphanum} characters. + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String QUERY_LEGAL = UNRESERVED + RESERVED_2 + "\\\""; + + /** + * Valid charset for RFC 2396 {@code scheme-specific-part}, + * additional to legal {@code alphanum} characters. + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String SSP_LEGAL = QUERY_LEGAL; + + /** + * Valid charset for RFC 2396 {@code fragment}, + * additional to legal {@code alphanum} characters. + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String FRAG_LEGAL = UNRESERVED + RESERVED; + + /** + * Immutable RFC3986 encoded string. + */ + public static class Encoded implements Comparable<Encoded>, CharSequence { + public static final Encoded EMPTY = new Encoded(""); + + private final String s; + + /** + * Casts the given encoded String by creating a new Encoded instance. + * <p> + * No encoding will be performed, use with care. + * </p> + */ + public static Encoded cast(final String encoded) { + return new Encoded(encoded); + } + + Encoded(final String encodedString) { + this.s = encodedString; + } + + /** + * Encodes all characters into their hexadecimal value prepended by '%', except: + * <ol> + * <li>letters ('a'..'z', 'A'..'Z')</li> + * <li>numbers ('0'..'9')</li> + * <li>characters in the legal-set parameter</li> + * <li> others (unicode characters that are not in + * US-ASCII set, and are not ISO Control or are not ISO Space characters)</li> + * </ol> + * <p> + * Uses {@link Uri#encode(String, String)} for implementation.. + * </p> + * + * @param vanilla the string to be encoded + * @param legal extended character set, allowed to be preserved in the vanilla string + */ + public Encoded(final String vanilla, final String legal) { + this.s = encode(vanilla, legal); + } + + public boolean isASCII() { return false; } + + public final String get() { return s; } + + /** + * Decodes the string argument which is assumed to be encoded in the {@code + * x-www-form-urlencoded} MIME content type using the UTF-8 encoding scheme. + * <p> + *'%' and two following hex digit characters are converted to the + * equivalent byte value. All other characters are passed through + * unmodified. + * </p> + * <p> + * e.g. "A%20B%20C %24%25" -> "A B C $%" + * </p> + * <p> + * Uses {@link Uri#decode(String)} for implementation.. + * </p> + */ + public final String decode() { return Uri.decode(s); } + + // + // Basic Object / Identity + // + + @Override + public final String toString() { return s; } + + @Override + public final int hashCode() { return s.hashCode(); } + + /** + * {@inheritDoc} + * + * @param o The comparison argument, either a {@link Encoded} or a {@link String} + * + * @return {@code true} if the given object is equivalent to this instance, + * otherwise {@code false}. + * + * @see #compareTo(Encoded) + * @see #equalsIgnoreCase(Encoded) + */ + @Override + public final boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o instanceof Encoded) { + return s.equals(((Encoded)o).s); + } + return s.equals(o); + } + + // + // CharSequence + // + + @Override + public final int length() { return s.length(); } + + @Override + public final char charAt(final int index) { return s.charAt(index); } + + @Override + public final CharSequence subSequence(final int start, final int end) { return s.subSequence(start, end); } + + @Override + public final int compareTo(final Encoded o) { return s.compareTo(o.s); } + + // + // String derived .. + // + /** See {@link String#concat(String)}. */ + public Encoded concat(final Encoded encoded) { return new Encoded(s.concat(encoded.s)); } + + /** See {@link String#substring(int)}. */ + public final String substring(final int start) { return s.substring(start); } + /** See {@link String#substring(int, int)}. */ + public final String substring(final int start, final int end) { return s.substring(start, end); } + + /** See {@link String#indexOf(int)}. */ + public final int indexOf(final int ch) { return s.indexOf(ch); } + /** See {@link String#indexOf(int, int)}. */ + public final int indexOf(final int ch, final int fromIndex) { return s.indexOf(ch, fromIndex); } + /** See {@link String#indexOf(String)}. */ + public final int indexOf(final String str) { return s.indexOf(str); } + /** See {@link String#indexOf(String, int)}. */ + public final int indexOf(final String str, final int fromIndex) { return s.indexOf(str, fromIndex); } + + /** See {@link String#lastIndexOf(int)}. */ + public final int lastIndexOf(final int ch) { return s.lastIndexOf(ch); } + /** See {@link String#lastIndexOf(int, int)}. */ + public int lastIndexOf(final int ch, final int fromIndex) { return s.lastIndexOf(ch, fromIndex); } + /** See {@link String#lastIndexOf(String)}. */ + public int lastIndexOf(final String str) { return s.lastIndexOf(str); } + /** See {@link String#lastIndexOf(String, int)}. */ + public int lastIndexOf(final String str, final int fromIndex) { return s.lastIndexOf(str, fromIndex); } + + /** See {@link String#equalsIgnoreCase(String)}. */ + public final boolean equalsIgnoreCase(final Encoded anotherEncoded) { return s.equalsIgnoreCase(anotherEncoded.s); } + } + + public static class ASCIIEncoded extends Encoded { + /** + * Casts the given encoded String by creating a new ASCIIEncoded instance. + * <p> + * No encoding will be performed, use with care. + * </p> + */ + public static ASCIIEncoded cast(final String encoded) { + return new ASCIIEncoded(encoded, null); + } + private ASCIIEncoded(final String encoded, final Object unused) { + super(encoded); + } + + /** + * Other characters, which are Unicode chars that are not US-ASCII, and are + * not ISO Control or are not ISO Space chars are not preserved + * and encoded into their hexidecimal value prepended by '%'. + * <p> + * For example: Euro currency symbol -> "%E2%82%AC". + * </p> + * <p> + * Uses {@link Uri#encodeToASCIIString(String)} for implementation. + * </p> + * @param unicode unencoded input + */ + public ASCIIEncoded(final String unicode) { + super(encodeToASCIIString(unicode)); + } + public boolean isASCII() { return true; } + } + + private static void encodeChar2UTF8(final StringBuilder buf, final char ch) { + final byte[] bytes; + try { + bytes = new String(new char[] { ch }).getBytes(ENCODING); + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(MSG_ENCODING_NA, e); + } + // FIXME: UTF-8 produces more than one byte ? Optimization might be possible. + for (int j = 0; j < bytes.length; j++) { + final byte b = bytes[j]; + buf.append('%'); + buf.append(DIGITS.charAt( ( b & 0xf0 ) >> 4 )); + buf.append(DIGITS.charAt( b & 0xf )); + } + } + + /** + * All characters are encoded into their hexadecimal value prepended by '%', except: + * <ol> + * <li>letters ('a'..'z', 'A'..'Z')</li> + * <li>numbers ('0'..'9')</li> + * <li>characters in the legal-set parameter</li> + * <li> others (unicode characters that are not in + * US-ASCII set, and are not ISO Control or are not ISO Space characters)</li> + * </ol> + * <p> + * Use {@link #encodeToASCIIString(String)} for US-ASCII encoding. + * </p> + * <p> + * Consider using {@link Encoded#Encoded(String, String)} in APIs + * to distinguish encoded from unencoded data by type. + * </p> + * + * @param vanilla the string to be encoded + * @param legal extended character set, allowed to be preserved in the vanilla string + * @return java.lang.String the converted string + */ + public static String encode(final String vanilla, final String legal) { + if( null == vanilla ) { + return null; + } + final StringBuilder buf = new StringBuilder(); + for (int i = 0; i < vanilla.length(); i++) { + final char ch = vanilla.charAt(i); + if ( (ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || + legal.indexOf(ch) > -1 || + ( ch > 127 && !Character.isSpaceChar(ch) && !Character.isISOControl(ch) ) + ) { + buf.append(ch); + } else { + encodeChar2UTF8(buf, ch); + } + } + return buf.toString(); + } + + /** + * Other characters, which are Unicode chars that are not US-ASCII, and are + * not ISO Control or are not ISO Space chars are not preserved + * and encoded into their hexidecimal value prepended by '%'. + * <p> + * For example: Euro currency symbol -> "%E2%82%AC". + * </p> + * <p> + * Consider using {@link ASCIIEncoded#ASCIIEncoded(String)} in APIs + * to distinguish encoded from unencoded data by type. + * </p> + * @param unicode string to be converted + * @return java.lang.String the converted string + */ + public static String encodeToASCIIString(final String unicode) { + final StringBuilder buf = new StringBuilder(); + for (int i = 0; i < unicode.length(); i++) { + final char ch = unicode.charAt(i); + if (ch <= 127) { + buf.append(ch); + } else { + encodeChar2UTF8(buf, ch); + } + } + return buf.toString(); + } + + /** + * Safe {@link Encoded#decode()} call on optional {@code encoded} instance. + * @param encoded {@link Encoded} instance to be decoded, may be {@code null}. + * @return the {@link Encoded#decode() decoded} String or {@code null} if {@code encoded} was {@code null}. + */ + public static String decode(final Encoded encoded) { + return null != encoded ? encoded.decode() : null; + } + + /** + * Decodes the string argument which is assumed to be encoded in the {@code + * x-www-form-urlencoded} MIME content type using the UTF-8 encoding scheme. + * <p> + *'%' and two following hex digit characters are converted to the + * equivalent byte value. All other characters are passed through + * unmodified. + * </p> + * <p> + * e.g. "A%20B%20C %24%25" -> "A B C $%" + * </p> + * + * @param encoded The encoded string. + * @return java.lang.String The decoded version. + */ + public static String decode(final String encoded) { + if( null == encoded ) { + return null; + } + final StringBuilder result = new StringBuilder(); + final byte[] buf = new byte[32]; + int bufI = 0; + for (int i = 0; i < encoded.length();) { + final char c = encoded.charAt(i); + if (c == '%') { + bufI = 0; + do { + if (i + 2 >= encoded.length()) { + throw new IllegalArgumentException("missing '%' hex-digits at index "+i); + } + final int d1 = Character.digit(encoded.charAt(i + 1), 16); + final int d2 = Character.digit(encoded.charAt(i + 2), 16); + if (d1 == -1 || d2 == -1) { + throw new IllegalArgumentException("invalid hex-digits at index "+i+": "+encoded.substring(i, i + 3)); + } + buf[bufI++] = (byte) ((d1 << 4) + d2); + if( 32 == bufI ) { + appendUTF8(result, buf, bufI); + bufI = 0; + } + i += 3; + } while (i < encoded.length() && encoded.charAt(i) == '%'); + if( 0 < bufI ) { + appendUTF8(result, buf, bufI); + } + } else { + result.append(c); + i++; + } + } + return result.toString(); + } + private static void appendUTF8(final StringBuilder sb, final byte[] buf, final int count) { + try { + sb.append(new String(buf, 0, count, ENCODING)); + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(MSG_ENCODING_NA, e); + } + } + + /** + * Creates a new Uri instance using the given arguments. + * <p> + * This constructor first creates a temporary Uri string from the given components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * {@code [scheme:]scheme-specific-part[#fragment]} + * </p> + * + * @param scheme the unencoded scheme part of the Uri. + * @param ssp the unencoded scheme-specific-part of the Uri. + * @param fragment the unencoded 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 String scheme, final String ssp, final String 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(IOUtil.SCHEME_SEPARATOR); + } + if ( !emptyString(ssp) ) { + // QUOTE ILLEGAL CHARACTERS + uri.append(encode(ssp, SSP_LEGAL)); + } + if ( !emptyString(fragment) ) { + uri.append(IOUtil.FRAGMENT_SEPARATOR); + // QUOTE ILLEGAL CHARACTERS + uri.append(encode(fragment, FRAG_LEGAL)); + } + return new Uri(new Encoded(uri.toString()), false); + } + + /** + * Creates a new Uri instance using the given arguments. + * <p> + * This constructor first creates a temporary Uri string from the given 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> + * + * @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 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. + * @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 String scheme, final String userinfo, String host, final int port, + final String path, final String query, final String 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, "path doesn't start with '/'"); + } + + final boolean extendedPath; + if( emptyString(query) || emptyString(fragment) ) { + extendedPath = true; + } else { + extendedPath = false; + } + + final StringBuilder uri = new StringBuilder(); + if ( !emptyString(scheme) ) { + uri.append(scheme); + uri.append(IOUtil.SCHEME_SEPARATOR); + } + + if ( !emptyString(userinfo) || !emptyString(host) || port != -1) { + uri.append("//"); + } + + if ( !emptyString(userinfo) ) { + // QUOTE ILLEGAL CHARACTERS in userinfo + uri.append(encode(userinfo, USERINFO_LEGAL)); + uri.append('@'); + } + + if ( !emptyString(host) ) { + // check for ipv6 addresses that hasn't been enclosed + // in square brackets + if (host.indexOf(IOUtil.SCHEME_SEPARATOR) != -1 && host.indexOf(']') == -1 + && host.indexOf('[') == -1) { + host = "[" + host + "]"; + } + uri.append(host); + } + + if ( port != -1 ) { + uri.append(IOUtil.SCHEME_SEPARATOR); + uri.append(port); + } + + if ( !emptyString(path) ) { + // QUOTE ILLEGAL CHARS + uri.append(encode(path, extendedPath ? PATH_MIN_LEGAL : PATH_LEGAL)); + } + + if ( !emptyString(query) ) { + uri.append('?'); + // QUOTE ILLEGAL CHARS + uri.append(encode(query, QUERY_LEGAL)); + } + + if ( !emptyString(fragment) ) { + // QUOTE ILLEGAL CHARS + uri.append(IOUtil.FRAGMENT_SEPARATOR); + uri.append(encode(fragment, FRAG_LEGAL)); + } + return new Uri(new Encoded(uri.toString()), true); + } + + /** + * Creates a new Uri instance using the given arguments. + * <p> + * This constructor first creates a temporary Uri string from the given components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * {@code [scheme:]host[path][#fragment]} + * </p> + * + * @param scheme the unencoded scheme part of the Uri. + * @param host the unencoded host name of the Uri. + * @param path the unencoded path to the resource on the host. + * @param fragment the unencoded 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 String scheme, final String host, final String path, final String fragment) throws URISyntaxException { + return create(scheme, null, host, -1, path, null, fragment); + } + + /** + * Creates a new Uri instance using the given arguments. + * <p> + * This constructor first creates a temporary Uri string from the given components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * {@code [scheme:][//authority][path][?query][#fragment]} + * </p> + * + * @param scheme the unencoded scheme part of the Uri. + * @param authority the unencoded authority part of the Uri. + * @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. + * + * @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 String scheme, final String authority, final String path, final String query, final String 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, "path doesn't start with '/'"); + } + + final boolean extendedPath; + if( emptyString(query) || emptyString(fragment) ) { + extendedPath = true; + } else { + extendedPath = false; + } + + final StringBuilder uri = new StringBuilder(); + if ( !emptyString(scheme) ) { + uri.append(scheme); + uri.append(IOUtil.SCHEME_SEPARATOR); + } + if ( !emptyString(authority) ) { + uri.append("//"); + // QUOTE ILLEGAL CHARS + uri.append(encode(authority, AUTHORITY_LEGAL)); + } + + if ( !emptyString(path) ) { + // QUOTE ILLEGAL CHARS + uri.append(encode(path, extendedPath ? PATH_MIN_LEGAL : PATH_LEGAL)); + } + if ( !emptyString(query) ) { + // QUOTE ILLEGAL CHARS + uri.append('?'); + uri.append(encode(query, QUERY_LEGAL)); + } + if ( !emptyString(fragment) ) { + // QUOTE ILLEGAL CHARS + uri.append(IOUtil.FRAGMENT_SEPARATOR); + uri.append(encode(fragment, FRAG_LEGAL)); + } + return new Uri(new Encoded(uri.toString()), false); + } + + /** + * 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> + * No encoding will be performed on the given {@code encodedUri}, use with care. + * </p> + * @throws URISyntaxException + */ + public static Uri cast(final String encodedUri) throws URISyntaxException { + return new Uri(Encoded.cast(encodedUri)); + } + + /** + * Creates a new Uri instance using the given file-path argument. + * <p> + * This constructor first creates a temporary Uri string from the given components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * {@code file:path} + * </p> + * + * @param path the 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. + */ + public static Uri valueOfFilepath(final String path) throws URISyntaxException { + if ( emptyString(path) ) { + throw new URISyntaxException("", "empty path"); + } + if ( path.charAt(0) != '/' ) { + throw new URISyntaxException(path, "path doesn't start with '/'"); + } + + final StringBuilder uri = new StringBuilder(); + uri.append(IOUtil.FILE_SCHEME); + uri.append(IOUtil.SCHEME_SEPARATOR); + + // QUOTE ILLEGAL CHARS + uri.append(encode(path, PATH_MIN_LEGAL)); + + return new Uri(new Encoded(uri.toString()), false); + } + + /** + * Creates a new Uri instance using the given File instance. + * <p> + * This constructor first creates a temporary Uri string from the given components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * {@code file:path} + * </p> + * + * @param file using {@link IOUtil#slashify(String, boolean, boolean) slashified} {@link File#getAbsolutePath() absolute-path} + * for the path of the {@code file} {@code schema}, utilizing {@link #valueOfFilepath(String)}. + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri valueOf(final File file) throws URISyntaxException { + return Uri.valueOfFilepath(IOUtil.slashify(file.getAbsolutePath(), true, file.isDirectory())); + } + + /** + * Creates a new Uri instance using the given URI instance. + * <p> + * If {@code reencode} is {@code true}, decomposes and decoded parts of the given URI + * and reconstruct a new encoded Uri instance, re-encoding will be performed. + * </p> + * <p> + * If {@code reencode} is {@code false}, the encoded {@link URI#toString()} is being used for the new Uri instance, + * i.e. no re-encoding will be performed. + * </p> + * + * @param uri A given URI instance + * @param reencode flag whether re-encoding shall be performed + * + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri valueOf(final URI uri, final boolean reencode) throws URISyntaxException { + if( !reencode) { + return new Uri(new Encoded(uri.toString())); + } + System.err.println("Uri.createByURI(reencode):"); + System.err.println(" source: "+uri.toString()); + final Uri recomposedUri; + if( uri.isOpaque()) { + // opaque, without host validation + System.err.println(" opaq source: ssp: "+uri.getSchemeSpecificPart()); + System.err.println(" opaq source: frg: "+uri.getFragment()); + recomposedUri = Uri.create(uri.getScheme(), uri.getSchemeSpecificPart(), uri.getFragment()); + } else if( null != uri.getHost() ) { + // with host validation + System.err.println(" host source: ssp: "+uri.getSchemeSpecificPart()); + System.err.println(" host source: frg: "+uri.getFragment()); + recomposedUri = Uri.create(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), + uri.getPath(), uri.getQuery(), uri.getFragment()); + } else { + // without host validation + System.err.println(" source: ssp: "+uri.getSchemeSpecificPart()); + System.err.println(" source: frg: "+uri.getFragment()); + recomposedUri = Uri.create(uri.getScheme(), uri.getAuthority(), + uri.getPath(), uri.getQuery(), uri.getFragment()); + } + System.err.println(" result: "+recomposedUri.toString()); + return recomposedUri; + } + + /** + * Creates a new Uri instance using the given URL instance. + * <p> + * No re-encoding will be performed. + * </p> + * + * @param url A given URL instance + * + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri valueOf(final URL url) throws URISyntaxException { + return new Uri(new Encoded(url.toString())); + } + + // + // All string fields are encoded! + // + + /** Encoded input string used at construction, never {@code null}. */ + public final Encoded input; + + private final Object lazyLock = new Object(); + + /** Encoded input string used at construction, in US-ASCII encoding. */ + private ASCIIEncoded inputASCII; + + private int hash; + + /** Plain {@code scheme}, {@code null} if undefined. */ + public final String scheme; + + /** Encoded {@code scheme-specific-part}, never {@code null}. */ + public final Encoded schemeSpecificPart; + /** Encoded {@code path} part of {@code scheme-specific-part}, never {@code null}. */ + public final Encoded path; + + /** Indicating whether {@code authority} part is defined or not. */ + public final boolean hasAuthority; + /** Encoded {@code authority} part of {@code scheme-specific-part}, {@code null} if undefined. */ + public final Encoded authority; + /** Encoded {@code userinfo} part of {@code authority} and {@code scheme-specific-part}, {@code null} if undefined. */ + public final Encoded userInfo; // part of authority + /** Encoded {@code host} part of {@code authority} and {@code scheme-specific-part}, {@code null} if undefined. */ + public final Encoded host; // part of authority + /** Encoded {@code port} part of {@code authority} and {@code scheme-specific-part}, {@code -1} if undefined. */ + public final int port; // part of authority + + /** Encoded {@code query} part of {@code scheme-specific-part}, {@code null} if undefined. */ + public final Encoded query; + + /** Encoded {@code fragment}, {@code null} if undefined. */ + public final Encoded fragment; + + /** Indicating whether this Uri is absolute, i.e. has a {@code scheme} and hence an absolute {@code scheme-specific-part}. */ + public final boolean absolute; + + /** + * Indicating whether this Uri is opaque, i.e. non-hierarchical {@code scheme-specific-part}. + * <p> + * An opaque Uri has no {@code scheme-specific-part} being parsed, + * i.e. {@code path}, {@code query} and {@code authority} are {@code null}. + * </p> + */ + public final boolean opaque; + + /** + * Creates a new Uri instance according to the given encoded string {@code uri}. + * + * @param uri the RFC3986 encoded RFC2396 Uri representation to be parsed into a Uri object + * @throws URISyntaxException + * if the given string {@code uri} doesn't fit to the + * specification RFC2396 and RFC3986 or could not be parsed correctly. + */ + public Uri(final Encoded uri) throws URISyntaxException { + this(uri, false); + } + + /** Returns true, if this instance is a {@code file} {@code scheme}, otherwise false. */ + public final boolean isFileScheme() { + return IOUtil.FILE_SCHEME.equals( scheme ); + } + + /** + * Returns the encoded {@link #input}, never {@code null}. + */ + public final Encoded getEncoded() { + return input; + } + + /** + * Returns the encoded {@link #input} as String, never {@code null}, same as {@link #getEncoded()}. + */ + @Override + public final String toString() { + return input.get(); + } + + /** + * Returns the encoded {@link #input} encoded in US-ASCII. + */ + public ASCIIEncoded toASCIIString() { + synchronized( lazyLock ) { + if( null == inputASCII ) { + inputASCII = new ASCIIEncoded(input.get()); + } + return inputASCII; + } + } + + /** + * Returns a new {@link URI} instance using the encoded {@link #input} string, {@code new URI(uri.input)}, + * i.e. no re-encoding will be performed. + * @see #toURI(boolean) + */ + public final URI toURI() { + try { + return new URI(input.get()); + } catch (final URISyntaxException e) { + throw new Error(e); // Can't happen + } + } + + /** + * Returns a new {@link URI} instance based upon this instance. + * <p> + * If {@code reencode} is {@code true}, all Uri parts of this instance will be decoded + * and encoded by the URI constructor, i.e. re-encoding will be performed. + * </p> + * <p> + * If {@code reencode} is {@code false}, this instance encoded {@link #input} string is being used, see {@link #toURI()}, + * i.e. no re-encoding will be performed. + * </p> + * + * @throws URISyntaxException + * if the given string {@code uri} doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + * @see #valueOf(URI, boolean) + */ + public final URI toURI(final boolean reencode) throws URISyntaxException { + if( !reencode ) { + return toURI(); + } + final URI recomposedURI; + if( opaque ) { + // opaque, without host validation + recomposedURI = new URI(decode(scheme), decode(schemeSpecificPart), decode(fragment)); + } else if( null != host ) { + // with host validation + recomposedURI = new URI(decode(scheme), decode(userInfo), decode(host), port, + decode(path), decode(query), decode(fragment)); + } else { + // without host validation + recomposedURI = new URI(decode(scheme), decode(authority), + decode(path), decode(query), decode(fragment)); + } + return recomposedURI; + } + + + /** + * Returns a new {@link URL} instance using the encoded {@link #input} string, {@code new URL(uri.input)}, + * i.e. no re-encoding will be performed. + * @throws MalformedURLException + * if an error occurs while creating the URL or no protocol + * handler could be found. + */ + public final URL toURL() throws MalformedURLException { + if (!absolute) { + throw new IllegalArgumentException("Cannot convert relative Uri: "+input); + } + return new URL(input.get()); + } + + /** + * If this instance {@link #isFileScheme() is a file scheme}, + * implementation decodes <i>[ "//"+{@link #authority} ] + {@link #path}</i>,<br> + * then it processes the result if {@link File#separatorChar} <code> == '\\'</code> + * as follows: + * <ul> + * <li>slash -> backslash</li> + * <li>drop a starting single backslash, preserving windows UNC</li> + * </ul> + * <p> + * Otherwise implementation returns {@code null}. + * </p> + */ + public final String getNativeFilePath() { + if( isFileScheme() ) { + final String authorityS; + if( null == authority ) { + authorityS = ""; + } else { + authorityS = "//"+authority.decode(); + } + // return IOUtil.decodeURIToFilePath(authorityS + path); + final String path = authorityS+this.path.decode(); + if( File.separator.equals("\\") ) { + final String r = patternSingleFS.matcher(path).replaceAll("\\\\"); + if( r.startsWith("\\") && !r.startsWith("\\\\") ) { // '\\\\' denotes UNC hostname, which shall not be cut-off + return r.substring(1); + } else { + return r; + } + } + return path; + } + return null; + } + + /** + * If this instance's {@link #schemeSpecificPart} contains a Uri itself, a sub-Uri, + * return {@link #schemeSpecificPart} + {@code #} {@link #fragment} via it's own new Uri instance. + * <p> + * In case this Uri is a {@code jar-scheme}, the {@code query} is omitted, + * since it shall be invalid for {@code jar-schemes} anyway. + * </p> + * <p> + * Otherwise method returns {@code null}. + * </p> + * <pre> + * Example 1: + * This instance: <code>jar:<i>scheme2</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class</code> + * Returned Uri: <code><i>scheme2</i>:/some/path/gluegen-rt.jar</code> + * + * Example 2: + * This instance: <code>jar:<i>scheme2</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class?lala=01#fragment</code> + * 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> + * Returned Uri: <code><i>scheme2</i>:/some/path/gluegen-rt.jar?lala=01#fragment</code> + * </pre> + * @throws URISyntaxException + */ + public final Uri getContainedUri() throws URISyntaxException { + if( !emptyString(schemeSpecificPart) ) { + final StringBuilder sb = new StringBuilder(); + + if( scheme.equals(IOUtil.JAR_SCHEME) ) { + final int idx = schemeSpecificPart.lastIndexOf(IOUtil.JAR_SCHEME_SEPARATOR); + if (0 > idx) { + throw new URISyntaxException(input.get(), "missing jar separator"); + } + sb.append( schemeSpecificPart.substring(0, idx) ); // exclude '!/' + } else { + sb.append( schemeSpecificPart.get() ); + } + if ( !emptyString(fragment) ) { + sb.append(IOUtil.FRAGMENT_SEPARATOR); + sb.append(fragment); + } + try { + final Uri res = new Uri(new Encoded(sb.toString()), false); + if( null != res.scheme ) { + return res; + } + } catch(final URISyntaxException e) { + // OK, does not contain uri + } + } + return null; + } + + + /** + * 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> + * <p> + * If this Uri does not contain any path separator, 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> + * + * 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> + * </pre> + */ + 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+IOUtil.SCHEME_SEPARATOR+schemeSpecificPart.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+IOUtil.SCHEME_SEPARATOR+schemeSpecificPart.substring(0, p+1) )); + } catch (final URISyntaxException ue) { + // not complete, hence removed authority, or even root folder -> return null + } + } + } + } + return null; + } + + /// 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. + * <p> + * <i>protocol</i> may be "file", "http", etc.. + * </p> + * + * @return "<i>protocol</i>:/some/path/" + * @throws IllegalArgumentException if the URI doesn't match the expected formatting, or is null + * @throws URISyntaxException + */ + 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 ':'"); + } + } + try { + return Uri.cast(uriS.substring(0, idx+1)); // exclude jar name, include terminal '/' or ':' + } catch (final URISyntaxException ue) {} + return null; + } + + /** + * Generates a URI for the <i>relativePath</i> relative to the <i>baseURI</i>, + * hence the result is a absolute location. + * <p> + * Impl. operates on the <i>scheme-specific-part</i>, and hence is sub-protocol savvy. + * </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. + * </p> + * + * @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> + */ + public Uri getRelativeOf(final Encoded relativePath) throws URISyntaxException { + return compose(scheme, schemeSpecificPart, relativePath, fragment); + } + + static Uri compose(final String 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('?'); + if( queryI >= 0 ) { + query = schemeSpecificPartS.substring(queryI+1); + schemeSpecificPartS = schemeSpecificPartS.substring(0, queryI); + } 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); + uri.append(':'); + uri.append(schemeSpecificPartS); + if ( null != query ) { + uri.append('?'); + uri.append(query); + } + if ( null != fragment ) { + uri.append('#'); + uri.append(fragment.get()); + } + return Uri.cast(uri.toString()); + } + + /// NEW END + + /** + * {@inheritDoc} + * <p> + * Compares this Uri instance with the given argument {@code o} and + * determines if both are equal. Two Uri instances are equal if all single + * parts are identical in their meaning. + * </p> + * + * @param o + * the Uri this instance has to be compared with. + * @return {@code true} if both Uri instances point to the same resource, + * {@code false} otherwise. + */ + @Override + public final boolean equals(final Object o) { + if (!(o instanceof Uri)) { + return false; + } + final Uri uri = (Uri) o; + + if (uri.fragment == null && fragment != null || uri.fragment != null && fragment == null) { + return false; + } else if (uri.fragment != null && fragment != null) { + if (!equalsHexCaseInsensitive(uri.fragment, fragment)) { + return false; + } + } + + if (uri.scheme == null && scheme != null || uri.scheme != null && scheme == null) { + return false; + } else if (uri.scheme != null && scheme != null) { + if (!uri.scheme.equalsIgnoreCase(scheme)) { + return false; + } + } + + if (uri.opaque && opaque) { + return equalsHexCaseInsensitive(uri.schemeSpecificPart, schemeSpecificPart); + } else if (!uri.opaque && !opaque) { + if (!equalsHexCaseInsensitive(path, uri.path)) { + return false; + } + + if (uri.query != null && query == null || uri.query == null && query != null) { + return false; + } else if (uri.query != null && query != null) { + if (!equalsHexCaseInsensitive(uri.query, query)) { + return false; + } + } + + if (uri.authority != null && authority == null || uri.authority == null && authority != null) { + return false; + } else if (uri.authority != null && authority != null) { + if (uri.host != null && host == null || uri.host == null && host != null) { + return false; + } else if (uri.host == null && host == null) { + // both are registry based, so compare the whole authority + return equalsHexCaseInsensitive(uri.authority, authority); + } else { // uri.host != null && host != null, so server-based + if (!host.equalsIgnoreCase(uri.host)) { + return false; + } + + if (port != uri.port) { + return false; + } + + if ( uri.userInfo != null && userInfo == null || + uri.userInfo == null && userInfo != null + ) { + return false; + } else if (uri.userInfo != null && userInfo != null) { + return equalsHexCaseInsensitive(userInfo, uri.userInfo); + } else { + return true; + } + } + } else { + // no authority + return true; + } + + } else { + // one is opaque, the other hierarchical + return false; + } + } + + /** + * {@inheritDoc} + * <p> + * Gets the hashcode value of this Uri instance. + * </p> + */ + @Override + public final int hashCode() { + synchronized( lazyLock ) { + if (hash == -1) { + hash = getHashString().hashCode(); + } + return hash; + } + } + + /* + * Takes a string that may contain hex sequences like %F1 or %2b and + * converts the hex values following the '%' to lowercase + */ + private String convertHexToLowerCase(final String s) { + if (s.indexOf('%') == -1) { + return s; + } + final StringBuilder result = new StringBuilder(""); + int index = 0, previndex = 0; + while ((index = s.indexOf('%', previndex)) != -1) { + result.append(s.substring(previndex, index + 1)); + result.append(s.substring(index + 1, index + 3).toLowerCase()); + index += 3; + previndex = index; + } + return result.toString(); + } + + /* + * Takes two strings that may contain hex sequences like %F1 or %2b and + * compares them, ignoring case for the hex values. Hex values must always + * occur in pairs as above + */ + private boolean equalsHexCaseInsensitive(final Encoded first, final Encoded second) { + if (first.indexOf('%') != second.indexOf('%')) { + return first.equals(second); + } + + int index = 0, previndex = 0; + while ( ( index = first.indexOf('%', previndex) ) != -1 && + second.indexOf('%', previndex) == index + ) { + if( !first.substring(previndex, index).equals( second.substring(previndex, index) ) ) { + return false; + } + if( !first.substring(index + 1, index + 3).equalsIgnoreCase( second.substring(index + 1, index + 3) ) ) { + return false; + } + index += 3; + previndex = index; + } + return first.substring(previndex).equals( second.substring(previndex) ); + } + + /* + * Form a string from the components of this Uri, similarly to the + * toString() method. But this method converts scheme and host to lowercase, + * and converts escaped octets to lowercase. + */ + private String getHashString() { + final StringBuilder result = new StringBuilder(); + if (scheme != null) { + result.append(scheme.toLowerCase()); + result.append(IOUtil.SCHEME_SEPARATOR); + } + if (opaque) { + result.append(schemeSpecificPart.get()); + } else { + if (authority != null) { + result.append("//"); + if (host == null) { + result.append(authority.get()); + } else { + if (userInfo != null) { + result.append(userInfo.get() + "@"); //$NON-NLS-1$ + } + result.append(host.get().toLowerCase()); + if (port != -1) { + result.append(IOUtil.SCHEME_SEPARATOR + port); + } + } + } + + if (path != null) { + result.append(path.get()); + } + + if (query != null) { + result.append('?'); + result.append(query.get()); + } + } + + if (fragment != null) { + result.append(IOUtil.FRAGMENT_SEPARATOR); + result.append(fragment.get()); + } + return convertHexToLowerCase(result.toString()); + } + + /** + * + * @param input + * @param expectServer + * @throws URISyntaxException + */ + private Uri(final Encoded input, final boolean expectServer) throws URISyntaxException { + if( emptyString(input) ) { + throw new URISyntaxException(input.get(), "empty input"); + } + this.input = input; + + String temp = input.get(); + int index; + // parse into Fragment, Scheme, and SchemeSpecificPart + // then parse SchemeSpecificPart if necessary + + // Fragment + index = temp.indexOf(IOUtil.FRAGMENT_SEPARATOR); + if (index != -1) { + // remove the fragment from the end + fragment = new Encoded( temp.substring(index + 1) ); + validateFragment(input, fragment, index + 1); + temp = temp.substring(0, index); + } else { + fragment = null; + } + + // Scheme and SchemeSpecificPart + final int indexSchemeSep = temp.indexOf(IOUtil.SCHEME_SEPARATOR); + index = indexSchemeSep; + final int indexSSP = temp.indexOf('/'); + final int indexQuerySep = temp.indexOf('?'); + final String schemeSpecificPartS; + + // if a '/' or '?' occurs before the first ':' the uri has no + // specified scheme, and is therefore not absolute + if ( indexSchemeSep != -1 && + ( indexSSP >= indexSchemeSep || indexSSP == -1 ) && + ( indexQuerySep >= indexSchemeSep || indexQuerySep == -1 ) + ) { + // the characters up to the first ':' comprise the scheme + absolute = true; + scheme = temp.substring(0, indexSchemeSep); + if (scheme.length() == 0) { + failExpecting(input, "scheme", indexSchemeSep); + } + validateScheme(input, scheme, 0); + schemeSpecificPartS = temp.substring(indexSchemeSep + 1); + schemeSpecificPart = new Encoded( schemeSpecificPartS ); + if (schemeSpecificPart.length() == 0) { + failExpecting(input, "scheme-specific-part", indexSchemeSep); + } + } else { + absolute = false; + scheme = null; + schemeSpecificPartS = temp; + schemeSpecificPart = new Encoded( schemeSpecificPartS ); + } + + if ( scheme == null || schemeSpecificPartS.length() > 0 && schemeSpecificPartS.charAt(0) == '/' ) { + // Uri is hierarchical, not opaque + opaque = false; + + // Query + temp = schemeSpecificPartS; + index = temp.indexOf('?'); + if (index != -1) { + query = new Encoded( temp.substring(index + 1) ); + temp = temp.substring(0, index); + validateQuery(input, query, indexSSP + 1 + index); + } else { + query = null; + } + + // Authority and Path + if (temp.startsWith("//")) { + index = temp.indexOf('/', 2); + final String authorityS; + if (index != -1) { + authorityS = temp.substring(2, index); + path = new Encoded( temp.substring(index) ); + } else { + authorityS = temp.substring(2); + if (authorityS.length() == 0 && query == null && fragment == null) { + failExpecting(input, "authority, path [, query, fragment]", index); + } + path = Encoded.EMPTY; + // nothing left, so path is empty + // (not null, path should never be null if hierarchical/non-opaque) + } + if ( emptyString(authorityS) ) { + authority = null; + } else { + authority = new Encoded( authorityS ); + validateAuthority(input, authority, indexSchemeSep + 3); + } + } else { // no authority specified + path = new Encoded( temp ); + authority = null; + } + + int pathIndex = 0; + if (indexSSP > -1) { + pathIndex += indexSSP; + } + if (index > -1) { + pathIndex += index; + } + validatePath(input, path, pathIndex); + } else { + // Uri is not hierarchical, Uri is opaque + opaque = true; + query = null; + path = null; + authority = null; + validateSsp(input, schemeSpecificPart, indexSchemeSep + 1); + } + + /** + * determine the host, port and userinfo if the authority parses + * successfully to a server based authority + * + * Behavior in error cases: if forceServer is true, throw + * URISyntaxException with the proper diagnostic messages. if + * forceServer is false assume this is a registry based uri, and just + * return leaving the host, port and userinfo fields undefined. + * + * and there are some error cases where URISyntaxException is thrown + * regardless of the forceServer parameter e.g. malformed ipv6 address + */ + Encoded tempUserinfo = null, tempHost = null; + int tempPort = -1; + boolean authorityComplete; + + if ( null != authority ) { + authorityComplete = true; // set to false later + int hostindex = 0; + + temp = authority.get(); + index = temp.indexOf('@'); + if (index != -1) { + // remove user info + tempUserinfo = new Encoded( temp.substring(0, index) ); + validateUserinfo(authority, tempUserinfo, 0); + temp = temp.substring(index + 1); // host[:port] is left + hostindex = index + 1; + } + + index = temp.lastIndexOf(IOUtil.SCHEME_SEPARATOR); + final int endindex = temp.indexOf(']'); + + if (index != -1 && endindex < index) { + // determine port and host + tempHost = new Encoded( temp.substring(0, index) ); + + if (index < (temp.length() - 1)) { // port part is not empty + try { + tempPort = Integer.parseInt(temp.substring(index + 1)); + if (tempPort < 0) { + if (expectServer) { + fail(authority, "invalid port <"+authority+">", hostindex + index + 1); + } + authorityComplete = false; + } + } catch (final NumberFormatException e) { + if (expectServer) { + fail(authority, "invalid port <"+authority+">, "+e.getMessage(), hostindex + index + 1); + } + authorityComplete = false; + } + } + } else { + tempHost = new Encoded( temp ); + } + + if( authorityComplete ) { + if ( emptyString(tempHost) ) { + if (expectServer) { + fail(authority, "empty host <"+authority+">", hostindex); + } + authorityComplete = false; + } else if (!isValidHost(expectServer, tempHost)) { + if (expectServer) { + fail(authority, "invalid host <"+tempHost+">", hostindex); + } + authorityComplete = false; + } + } + } else { + authorityComplete = false; + } + + if( authorityComplete ) { + // this is a server based uri, + // fill in the userinfo, host and port fields + userInfo = tempUserinfo; + host = tempHost; + port = tempPort; + hasAuthority = true; + } else { + userInfo = null; + host = null; + port = -1; + hasAuthority = false; + } + } + + private static void validateScheme(final Encoded uri, final String scheme, final int index) throws URISyntaxException { + // first char needs to be an alpha char + final char ch = scheme.charAt(0); + if ( !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) ) { + fail(uri, "invalid scheme", index); + } + final int errIdx = validateAlphaNum(scheme, "+-."); + if( 0 <= errIdx ) { + fail(uri, "invalid scheme", index+errIdx); + } + } + + private static void validateSsp(final Encoded uri, final Encoded ssp, final int index) throws URISyntaxException { + final int errIdx = validateEncoded(ssp, SSP_LEGAL); + if( 0 <= errIdx ) { + fail(uri, "invalid scheme-specific-part", index+errIdx); + } + } + + private static void validateAuthority(final Encoded uri, final Encoded authority, final int index) throws URISyntaxException { + final int errIdx = validateEncoded(authority, AUTHORITY_LEGAL); + if( 0 <= errIdx ) { + fail(uri, "invalid authority", index+errIdx); + } + } + + private static void validatePath(final Encoded uri, final Encoded path, final int index) throws URISyntaxException { + final int errIdx = validateEncoded(path, PATH_LEGAL); + if( 0 <= errIdx ) { + fail(uri, "invalid path", index+errIdx); + } + } + + private static void validateQuery(final Encoded uri, final Encoded query, final int index) throws URISyntaxException { + final int errIdx = validateEncoded(query, QUERY_LEGAL); + if( 0 <= errIdx ) { + fail(uri, "invalid query", index+errIdx); + } + } + + private static void validateFragment(final Encoded uri, final Encoded fragment, final int index) throws URISyntaxException { + final int errIdx = validateEncoded(fragment, FRAG_LEGAL); + if( 0 <= errIdx ) { + fail(uri, "invalid fragment", index+errIdx); + } + } + + private static void validateUserinfo(final Encoded uri, final Encoded userinfo, final int index) throws URISyntaxException { + for (int i = 0; i < userinfo.length(); i++) { + final char ch = userinfo.charAt(i); + if (ch == ']' || ch == '[') { + fail(uri, "invalid userinfo", index+i); + } + } + } + + /** + * distinguish between IPv4, IPv6, domain name and validate it based on + * its type + */ + private boolean isValidHost(final boolean expectServer, final Encoded host) throws URISyntaxException { + if (host.charAt(0) == '[') { + // ipv6 address + if (host.charAt(host.length() - 1) != ']') { + fail(input, "invalid host, missing closing ipv6: "+host, 0); + } + if (!isValidIP6Address(host.get())) { + fail(input, "invalid ipv6: "+host, 0); + } + return true; + } + + // '[' and ']' can only be the first char and last char + // of the host name + if (host.indexOf('[') != -1 || host.indexOf(']') != -1) { + fail(input, "invalid host: "+host, 0); + } + + final int index = host.lastIndexOf('.'); + if ( index < 0 || index == host.length() - 1 || + !Character.isDigit(host.charAt(index + 1)) ) + { + // domain name + if (isValidDomainName(host)) { + return true; + } + if (expectServer) { + fail(input, "invalid host, invalid domain-name or ipv4: "+host, 0); + } + return false; + } + + // IPv4 address + if (isValidIPv4Address(host.get())) { + return true; + } + if (expectServer) { + fail(input, "invalid host, invalid ipv4: "+host, 0); + } + return false; + } + + private static boolean isValidDomainName(final Encoded host) { + final String hostS = host.get(); + if( 0 <= validateAlphaNum(hostS, "-.") ) { + return false; + } + String label = null; + final StringTokenizer st = new StringTokenizer(hostS, "."); //$NON-NLS-1$ + while (st.hasMoreTokens()) { + label = st.nextToken(); + if (label.startsWith("-") || label.endsWith("-")) { //$NON-NLS-1$ //$NON-NLS-2$ + return false; + } + } + + if (!label.equals(hostS)) { + final char ch = label.charAt(0); + if (ch >= '0' && ch <= '9') { + return false; + } + } + return true; + } + + private static boolean isValidIPv4Address(final String ipv4Address) { + int index; + int index2; + try { + int num; + index = ipv4Address.indexOf('.'); + num = Integer.parseInt(ipv4Address.substring(0, index)); + if (num < 0 || num > 255) { + return false; + } + index2 = ipv4Address.indexOf('.', index + 1); + num = Integer.parseInt(ipv4Address.substring(index + 1, index2)); + if (num < 0 || num > 255) { + return false; + } + index = ipv4Address.indexOf('.', index2 + 1); + num = Integer.parseInt(ipv4Address.substring(index2 + 1, index)); + if (num < 0 || num > 255) { + return false; + } + num = Integer.parseInt(ipv4Address.substring(index + 1)); + if (num < 0 || num > 255) { + return false; + } + } catch (final Exception e) { + return false; + } + return true; + } + + private static boolean isValidIP6Address(final String ipv6Address) { + final int length = ipv6Address.length(); + boolean doubleColon = false; + int numberOfColons = 0; + int numberOfPeriods = 0; + String word = ""; //$NON-NLS-1$ + char c = 0; + char prevChar = 0; + int offset = 0; // offset for [] ip addresses + + if (length < 2) { + return false; + } + + for (int i = 0; i < length; i++) { + prevChar = c; + c = ipv6Address.charAt(i); + switch (c) { + + // case for an open bracket [x:x:x:...x] + case '[': + if (i != 0) { + return false; // must be first character + } + if (ipv6Address.charAt(length - 1) != ']') { + return false; // must have a close ] + } + if ((ipv6Address.charAt(1) == IOUtil.SCHEME_SEPARATOR_CHAR) + && (ipv6Address.charAt(2) != IOUtil.SCHEME_SEPARATOR_CHAR)) { + return false; + } + offset = 1; + if (length < 4) { + return false; + } + break; + + // case for a closed bracket at end of IP [x:x:x:...x] + case ']': + if (i != length - 1) { + return false; // must be last character + } + if (ipv6Address.charAt(0) != '[') { + return false; // must have a open [ + } + break; + + // case for the last 32-bits represented as IPv4 + // x:x:x:x:x:x:d.d.d.d + case '.': + numberOfPeriods++; + if (numberOfPeriods > 3) { + return false; + } + if (!isValidIP4Word(word)) { + return false; + } + if (numberOfColons != 6 && !doubleColon) { + return false; + } + // a special case ::1:2:3:4:5:d.d.d.d allows 7 colons + // with + // an IPv4 ending, otherwise 7 :'s is bad + if (numberOfColons == 7 + && ipv6Address.charAt(0 + offset) != IOUtil.SCHEME_SEPARATOR_CHAR + && ipv6Address.charAt(1 + offset) != IOUtil.SCHEME_SEPARATOR_CHAR) { + return false; + } + word = ""; //$NON-NLS-1$ + break; + + case IOUtil.SCHEME_SEPARATOR_CHAR: + numberOfColons++; + if (numberOfColons > 7) { + return false; + } + if (numberOfPeriods > 0) { + return false; + } + if (prevChar == IOUtil.SCHEME_SEPARATOR_CHAR) { + if (doubleColon) { + return false; + } + doubleColon = true; + } + word = ""; //$NON-NLS-1$ + break; + + default: + if (word.length() > 3) { + return false; + } + if (!isValidHexChar(c)) { + return false; + } + word += c; + } + } + + // Check if we have an IPv4 ending + if (numberOfPeriods > 0) { + if (numberOfPeriods != 3 || !isValidIP4Word(word)) { + return false; + } + } else { + // If we're at then end and we haven't had 7 colons then there + // is a problem unless we encountered a doubleColon + if (numberOfColons != 7 && !doubleColon) { + return false; + } + + // If we have an empty word at the end, it means we ended in + // either a : or a . + // If we did not end in :: then this is invalid + if (word == "" && ipv6Address.charAt(length - 1 - offset) != IOUtil.SCHEME_SEPARATOR_CHAR //$NON-NLS-1$ + && ipv6Address.charAt(length - 2 - offset) != IOUtil.SCHEME_SEPARATOR_CHAR) { + return false; + } + } + + return true; + } + + private static boolean isValidIP4Word(final String word) { + char c; + if (word.length() < 1 || word.length() > 3) { + return false; + } + for (int i = 0; i < word.length(); i++) { + c = word.charAt(i); + if (!(c >= '0' && c <= '9')) { + return false; + } + } + if (Integer.parseInt(word) > 255) { + return false; + } + return true; + } + + /** + * Validate a string by checking if it contains any characters other than: + * <ol> + * <li>letters ('a'..'z', 'A'..'Z')</li> + * <li>numbers ('0'..'9')</li> + * <li>characters in the legal-set parameter</li> + * <li> others (unicode characters that are not in + * US-ASCII set, and are not ISO Control or are not ISO Space characters)</li> + * </ol> + * + * @param encoded + * {@code java.lang.String} the string to be validated + * @param legal + * {@code java.lang.String} the characters allowed in the String + * s + */ + private static int validateEncoded(final Encoded encoded, final String legal) { + for (int i = 0; i < encoded.length();) { + final char ch = encoded.charAt(i); + if (ch == '%') { + do { + if (i + 2 >= encoded.length()) { + throw new IllegalArgumentException("missing '%' hex-digits at index "+i); + } + final int d1 = Character.digit(encoded.charAt(i + 1), 16); + final int d2 = Character.digit(encoded.charAt(i + 2), 16); + if (d1 == -1 || d2 == -1) { + throw new IllegalArgumentException("invalid hex-digits at index "+i+": "+encoded.substring(i, i + 3)); + } + i += 3; + } while (i < encoded.length() && encoded.charAt(i) == '%'); + continue; + } + if ( !( (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || legal.indexOf(ch) > -1 || + (ch > 127 && !Character.isSpaceChar(ch) && !Character.isISOControl(ch)) + ) + ) { + return i; + } + i++; + } + return -1; + } + private static int validateAlphaNum(final String s, final String legal) { + for (int i = 0; i < s.length();) { + final char ch = s.charAt(i); + if ( !( (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || legal.indexOf(ch) > -1 + ) + ) { + return i; + } + i++; + } + return -1; + } + + private static boolean isValidHexChar(final char c) { + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); + } + private static boolean emptyString(final Encoded s) { + return null == s || 0 == s.length(); + } + private static boolean emptyString(final String s) { + return null == s || 0 == s.length(); + } + + private static void fail(final Encoded input, final String reason, final int p) throws URISyntaxException { + throw new URISyntaxException(input.get(), reason, p); + } + private static void failExpecting(final Encoded input, final String expected, final int p) throws URISyntaxException { + fail(input, "Expecting " + expected, p); + } +}
\ No newline at end of file diff --git a/src/java/com/jogamp/common/util/IOUtil.java b/src/java/com/jogamp/common/util/IOUtil.java index 5819fbc..7a5b7ec 100644 --- a/src/java/com/jogamp/common/util/IOUtil.java +++ b/src/java/com/jogamp/common/util/IOUtil.java @@ -52,6 +52,7 @@ import jogamp.common.os.AndroidUtils; import jogamp.common.os.PlatformPropsImpl; import com.jogamp.common.net.AssetURLContext; +import com.jogamp.common.net.Uri; import com.jogamp.common.nio.Buffers; import com.jogamp.common.os.MachineDescription; import com.jogamp.common.os.Platform; @@ -62,6 +63,10 @@ public class IOUtil { /** {@value} */ public static final String SCHEME_SEPARATOR = ":"; /** {@value} */ + public static final char SCHEME_SEPARATOR_CHAR = ':'; + /** {@value} */ + public static final char FRAGMENT_SEPARATOR = '#'; + /** {@value} */ public static final String FILE_SCHEME = "file"; /** {@value} */ public static final String HTTP_SCHEME = "http"; @@ -289,6 +294,9 @@ public class IOUtil { * */ + private static final Pattern patternSingleBS = Pattern.compile("\\\\{1}"); + private static final Pattern patternSingleFS = Pattern.compile("/{1}"); + /** * * @param path @@ -298,7 +306,7 @@ public class IOUtil { * @throws URISyntaxException if path is empty or has no parent directory available while resolving <code>../</code> */ public static String slashify(final String path, final boolean startWithSlash, final boolean endWithSlash) throws URISyntaxException { - String p = path.replace('\\', '/'); // unify file separator + String p = patternSingleBS.matcher(path).replaceAll("/"); if (startWithSlash && !p.startsWith("/")) { p = "/" + p; } @@ -309,24 +317,6 @@ public class IOUtil { } /** - * Using the simple conversion via File -> URI, assuming proper characters. - * @throws URISyntaxException if path is empty or has no parent directory available while resolving <code>../</code> - * @throws URISyntaxException if the resulting string does not comply w/ an RFC 2396 URI - */ - public static URI toURISimple(final File file) throws URISyntaxException { - return new URI(FILE_SCHEME, null, slashify(file.getAbsolutePath(), true /* startWithSlash */, file.isDirectory() /* endWithSlash */), null); - } - - /** - * Using the simple conversion via File -> URI, assuming proper characters. - * @throws URISyntaxException if path is empty or has no parent directory available while resolving <code>../</code> - * @throws URISyntaxException if the resulting string does not comply w/ an RFC 2396 URI - */ - public static URI toURISimple(final String protocol, final String path, final boolean isDirectory) throws URISyntaxException { - return new URI(protocol, null, slashify(new File(path).getAbsolutePath(), true /* startWithSlash */, isDirectory /* endWithSlash */), null); - } - - /** * Returns the lowercase suffix of the given file name (the text * after the last '.' in the file name). Returns null if the file * name has no suffix. Only operates on the given file name; @@ -445,6 +435,7 @@ public class IOUtil { * @return "<i>protocol</i>:/some/path/" * @throws IllegalArgumentException if the URI doesn't match the expected formatting, or is null * @throws URISyntaxException + * @deprecated Use {@link Uri#getDirectory()} */ public static URI getURIDirname(final URI uri) throws IllegalArgumentException, URISyntaxException { if(null == uri) { @@ -468,6 +459,7 @@ public class IOUtil { * @return "<i>protocol</i>:/some/path/" * @throws IllegalArgumentException if the URI doesn't match the expected formatting, or is null * @throws URISyntaxException + * @deprecated Use {@link Uri#getDirectory()} */ public static String getURIDirname(String uriS) throws IllegalArgumentException, URISyntaxException { if(null == uriS) { @@ -488,25 +480,11 @@ public class IOUtil { uriS = uriS.substring(0, idx+1); // exclude jar name, include terminal '/' or ':' if( DEBUG ) { - System.err.println("getJarURIDirname res: "+uriS); + System.err.println("getURIDirname res: "+uriS); } return uriS; } - /** - * Simply returns {@link URI#toURL()}. - * @param uri - * @return - * @throws IOException - * @throws IllegalArgumentException - * @throws URISyntaxException - * - * @deprecated Useless - */ - public static URL toURL(final URI uri) throws IOException, IllegalArgumentException, URISyntaxException { - return uri.toURL(); - } - /*** * * RESOURCE LOCATION STUFF @@ -705,177 +683,7 @@ public class IOUtil { return path; } - /** - * Generates a URI for the <i>relativePath</i> relative to the <i>baseURI</i>, - * hence the result is a absolute location. - * <p> - * Impl. operates on the <i>scheme-specific-part</i>, and hence is sub-protocol savvy. - * </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. - * </p> - * - * @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> - */ - public static URI getRelativeOf(final URI baseURI, final String relativePath) throws URISyntaxException { - return compose(baseURI.getScheme(), baseURI.getRawSchemeSpecificPart(), relativePath, baseURI.getRawFragment()); - } - - /** - * Wraps {@link #getRelativeOf(URI, String)} for convenience. - * @param relativePath denotes a relative file to the baseLocation's parent directory (URI encoded) - * @throws IOException - */ - public static URL getRelativeOf(final URL baseURL, final String relativePath) throws IOException { - try { - return getRelativeOf(baseURL.toURI(), relativePath).toURL(); - } catch (final URISyntaxException e) { - throw new IOException(e); - } - } - - /** - * Generates a URI for the <i>relativePath</i> relative to the <i>schemeSpecificPart</i>, - * hence the result is a absolute location. - * <p> - * <i>schemeSpecificPart</i>'s query, if exist is split to <i>path</i> and <i>query</i>. - * </p> - * <p> - * In case <i>path</i> is not a path ending w/ '/', it's a assumed to be a file and it's parent is being used. - * </p> - * - * @param scheme scheme of the resulting URI - * @param schemeSpecificPart may include a query, which is separated while processing (URI encoded) - * @param relativePath denotes a relative file to the baseLocation's parent directory (URI encoded) - * @param fragment the URI fragment (URI encoded) - * @throws URISyntaxException if path is empty or has no parent directory available while resolving <code>../</code> - * @see #encodeToURI(String) - */ - public static URI compose(final String scheme, String schemeSpecificPart, final String relativePath, final String fragment) throws URISyntaxException { - // cut off optional query in scheme-specific-part - final String query; - final int queryI = schemeSpecificPart.lastIndexOf('?'); - if( queryI >= 0 ) { - query = schemeSpecificPart.substring(queryI+1); - schemeSpecificPart = schemeSpecificPart.substring(0, queryI); - } else { - query = null; - } - if( null != relativePath ) { - if( !schemeSpecificPart.endsWith("/") ) { - schemeSpecificPart = getParentOf(schemeSpecificPart); - } - schemeSpecificPart = schemeSpecificPart + relativePath; - } - schemeSpecificPart = cleanPathString( schemeSpecificPart ); - final StringBuilder uri = new StringBuilder(); - uri.append(scheme); - uri.append(':'); - uri.append(schemeSpecificPart); - if ( null != query ) { - uri.append('?'); - uri.append(query); - } - if ( null != fragment ) { - uri.append('#'); - uri.append(fragment); - } - return new URI(uri.toString()); - } - - private static final Pattern patternSpaceRaw = Pattern.compile(" "); - private static final Pattern patternSpaceEnc = Pattern.compile("%20"); - - /** - * Escapes characters not complying w/ RFC 2396 and the {@link URI#URI(String)} ctor. - * <ul> - * <li>SPACE -> %20</li> - * </ul> - * @deprecated Useless - */ - public static String encodeToURI(final String vanilla) { - return patternSpaceRaw.matcher(vanilla).replaceAll("%20"); // Uri TODO: Uri.encode(vanilla, Uri.PATH_MIN_LEGAL); - } - - /** - * Reverses escaping of characters as performed via {@link #encodeToURI(String)}. - * <ul> - * <li>%20 -> SPACE</li> - * </ul> - * @deprecated Use {@link #decodeURIIfFilePath(URI)} - */ - public static String decodeFromURI(final String encodedUri) { - return patternSpaceEnc.matcher(encodedUri).replaceAll(" "); // Uri TODO: Uri.decode(encoded); - } - - private static final Pattern patternSingleBS = Pattern.compile("\\\\{1}"); - private static final Pattern patternSingleFS = Pattern.compile("/{1}"); - - /** - * Encodes file path characters not complying w/ RFC 2396 and the {@link URI#URI(String)} ctor. - * <p> - * Implementation processes the <code>filePath</code> if {@link File#separatorChar} <code> == '\\'</code> - * as follows: - * <ul> - * <li>backslash -> slash</li> - * <li>ensure starting with slash</li> - * </ul> - * </p> - * <p> - * Note that this method does not perform <i>space</i> encoding, - * which can be utilized via {@link #encodeToURI(String)}. - * </p> - * <p> - * Even though Oracle's JarURLStreamHandler can handle backslashes and - * erroneous URIs w/ e.g. Windows file 'syntax', other may not (Netbeans).<br> - * See Bug 857 - http://jogamp.org/bugzilla/show_bug.cgi?id=857 - * </p> - * @see #encodeToURI(String) - * @deprecated Useless - */ - public static String encodeFilePathToURI(final String filePath) { - if( File.separator.equals("\\") ) { - final String r = patternSingleBS.matcher(filePath).replaceAll("/"); - if( !r.startsWith("/") ) { - return "/" + r; - } else { - return r; - } - } - return filePath; - } - - /** - * Completes decoding uri-file path characters complying w/ RFC 2396 to native file-path. - * <p> - * Implementation decodes the space-encoding <code>path={@link #decodeFromURI(String) decodeFromURI}(uriPath)</code>. - * </p> - * <p> - * Then it processes the <code>path</code> if {@link File#separatorChar} <code> == '\\'</code> - * as follows: - * <ul> - * <li>slash -> backslash</li> - * <li>drop a starting single backslash, preserving windows UNC</li> - * </ul> - * </p> - * @param encodedUriPath URI encoded path - * @see #decodeFromURI(String) - * @deprecated Use {@link #decodeURIIfFilePath(URI)} - */ - public static String decodeURIToFilePath(final String encodedUriPath) { - final String path = patternSpaceEnc.matcher(encodedUriPath).replaceAll(" "); // Uri TODO: Uri.decode(encoded); - if( File.separator.equals("\\") ) { - final String r = patternSingleFS.matcher(path).replaceAll("\\\\"); - if( r.startsWith("\\") && !r.startsWith("\\\\") ) { // '\\\\' denotes UNC hostname, which shall not be cut-off - return r.substring(1); - } else { - return r; - } - } - return path; - } + public static final Pattern patternSpaceEnc = Pattern.compile("%20"); /** * If <code>uri</code> is a <i>file scheme</i>, @@ -890,9 +698,7 @@ public class IOUtil { * <p> * Otherwise it returns the {@link URI#toASCIIString()} encoded URI. * </p> - * - * @see #decodeFromURI(String) - * @see #decodeURIToFilePath(String) + * @deprecated Use {@link Uri#getNativeFilePath()}. */ public static String decodeURIIfFilePath(final URI uri) { if( IOUtil.FILE_SCHEME.equals( uri.getScheme() ) ) { diff --git a/src/java/com/jogamp/common/util/JarUtil.java b/src/java/com/jogamp/common/util/JarUtil.java index 3fe03bf..329ea51 100644 --- a/src/java/com/jogamp/common/util/JarUtil.java +++ b/src/java/com/jogamp/common/util/JarUtil.java @@ -46,6 +46,7 @@ import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import com.jogamp.common.net.Uri; import com.jogamp.common.os.NativeLibrary; import com.jogamp.common.os.Platform; @@ -263,6 +264,7 @@ public class JarUtil { * @throws IllegalArgumentException if the URI doesn't match the expected formatting or is null * @throws URISyntaxException if the URI could not be translated into a RFC 2396 URI * @see {@link IOUtil#getClassURL(String, ClassLoader)} + * @deprecated Use {@link Uri#getContainedUri()} and validate it's scheme, etc. */ public static URI getJarSubURI(final URI classJarURI) throws IllegalArgumentException, URISyntaxException { if(null == classJarURI) { diff --git a/src/java/com/jogamp/common/util/cache/TempJarCache.java b/src/java/com/jogamp/common/util/cache/TempJarCache.java index 1b322d7..8979a43 100644 --- a/src/java/com/jogamp/common/util/cache/TempJarCache.java +++ b/src/java/com/jogamp/common/util/cache/TempJarCache.java @@ -38,6 +38,7 @@ import java.util.jar.JarFile; import jogamp.common.Debug; +import com.jogamp.common.net.Uri; import com.jogamp.common.os.NativeLibrary; import com.jogamp.common.util.IOUtil; import com.jogamp.common.util.JarUtil; @@ -412,7 +413,7 @@ public class TempJarCache { checkInitialized(); final File f = new File(tmpFileCache.getTempDir(), name); if(f.exists()) { - return IOUtil.toURISimple(f); + return Uri.valueOf(f).toURI(); } return null; } diff --git a/src/junit/com/jogamp/common/net/AssetURLConnectionBase.java b/src/junit/com/jogamp/common/net/AssetURLConnectionBase.java index e16003f..40acec9 100644 --- a/src/junit/com/jogamp/common/net/AssetURLConnectionBase.java +++ b/src/junit/com/jogamp/common/net/AssetURLConnectionBase.java @@ -23,15 +23,15 @@ public abstract class AssetURLConnectionBase extends JunitTracer { /** In gluegen.test.jar */ protected static final String test_asset_test1_url = "asset:gluegen-test/info.txt"; protected static final String test_asset_test1_entry = "gluegen-test/info.txt"; - protected static final String test_asset_test2_rel = "data/AssetURLConnectionTest.txt"; + protected static final Uri.Encoded test_asset_test2_rel = Uri.Encoded.cast("data/AssetURLConnectionTest.txt"); protected static final String test_asset_test2a_url = "asset:com/jogamp/common/net/data/AssetURLConnectionTest.txt"; protected static final String test_asset_test2b_url = "asset:/com/jogamp/common/net/data/AssetURLConnectionTest.txt"; protected static final String test_asset_test2_entry = "com/jogamp/common/net/data/AssetURLConnectionTest.txt"; - protected static final String test_asset_test3_rel = "RelativeData.txt"; + protected static final Uri.Encoded test_asset_test3_rel = Uri.Encoded.cast("RelativeData.txt"); protected static final String test_asset_test3a_url = "asset:com/jogamp/common/net/data/RelativeData.txt"; protected static final String test_asset_test3b_url = "asset:/com/jogamp/common/net/data/RelativeData.txt"; protected static final String test_asset_test3_entry = "com/jogamp/common/net/data/RelativeData.txt"; - protected static final String test_asset_test4_rel = "../data2/RelativeData2.txt"; + protected static final Uri.Encoded test_asset_test4_rel = Uri.Encoded.cast("../data2/RelativeData2.txt"); protected static final String test_asset_test4a_url = "asset:com/jogamp/common/net/data2/RelativeData2.txt"; protected static final String test_asset_test4b_url = "asset:/com/jogamp/common/net/data2/RelativeData2.txt"; protected static final String test_asset_test4_entry = "com/jogamp/common/net/data2/RelativeData2.txt"; diff --git a/src/junit/com/jogamp/common/net/AssetURLConnectionRegisteredTest.java b/src/junit/com/jogamp/common/net/AssetURLConnectionRegisteredTest.java index 7648990..cb6200d 100644 --- a/src/junit/com/jogamp/common/net/AssetURLConnectionRegisteredTest.java +++ b/src/junit/com/jogamp/common/net/AssetURLConnectionRegisteredTest.java @@ -1,7 +1,6 @@ package com.jogamp.common.net; import java.io.IOException; -import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; @@ -48,12 +47,12 @@ public class AssetURLConnectionRegisteredTest extends AssetURLConnectionBase { Assert.assertEquals(test_asset_test2a_url, urlConn0.getURL().toExternalForm()); testAssetConnection(urlConn0, test_asset_test2_entry); - final URI uri1 = IOUtil.getRelativeOf(urlConn0.getURL().toURI(), test_asset_test3_rel); + final Uri uri1 = Uri.valueOf(urlConn0.getURL()).getRelativeOf(test_asset_test3_rel); Assert.assertNotNull(uri1); Assert.assertEquals(test_asset_test3a_url, uri1.toString()); testAssetConnection(uri1.toURL().openConnection(), test_asset_test3_entry); - final URI uri2 = IOUtil.getRelativeOf(urlConn0.getURL().toURI(), test_asset_test4_rel); + final Uri uri2 = Uri.valueOf(urlConn0.getURL()).getRelativeOf(test_asset_test4_rel); Assert.assertNotNull(uri2); Assert.assertEquals(test_asset_test4a_url, uri2.toString()); testAssetConnection(uri2.toURL().openConnection(), test_asset_test4_entry); @@ -66,12 +65,12 @@ public class AssetURLConnectionRegisteredTest extends AssetURLConnectionBase { Assert.assertEquals(test_asset_test2b_url, urlConn0.getURL().toExternalForm()); testAssetConnection(urlConn0, test_asset_test2_entry); - final URI uri1 = IOUtil.getRelativeOf(urlConn0.getURL().toURI(), test_asset_test3_rel); + final Uri uri1 = Uri.valueOf(urlConn0.getURL()).getRelativeOf(test_asset_test3_rel); Assert.assertNotNull(uri1); Assert.assertEquals(test_asset_test3b_url, uri1.toString()); testAssetConnection(uri1.toURL().openConnection(), test_asset_test3_entry); - final URI uri2 = IOUtil.getRelativeOf(urlConn0.getURL().toURI(), test_asset_test4_rel); + final Uri uri2 = Uri.valueOf(urlConn0.getURL()).getRelativeOf(test_asset_test4_rel); Assert.assertNotNull(uri2); Assert.assertEquals(test_asset_test4b_url, uri2.toString()); testAssetConnection(uri2.toURL().openConnection(), test_asset_test4_entry); diff --git a/src/junit/com/jogamp/common/net/AssetURLConnectionUnregisteredTest.java b/src/junit/com/jogamp/common/net/AssetURLConnectionUnregisteredTest.java index 5fbde49..5167abb 100644 --- a/src/junit/com/jogamp/common/net/AssetURLConnectionUnregisteredTest.java +++ b/src/junit/com/jogamp/common/net/AssetURLConnectionUnregisteredTest.java @@ -1,7 +1,6 @@ package com.jogamp.common.net; import java.io.IOException; -import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; @@ -39,14 +38,14 @@ public class AssetURLConnectionUnregisteredTest extends AssetURLConnectionBase { @Test public void assetUnregisteredIOUtilGetResourceRel0_RT() throws IOException, URISyntaxException { - final URLConnection urlConn0 = IOUtil.getResource(this.getClass(), test_asset_test2_rel); + final URLConnection urlConn0 = IOUtil.getResource(this.getClass(), test_asset_test2_rel.get()); testAssetConnection(urlConn0, test_asset_test2_entry); - final URI uri1 = IOUtil.getRelativeOf(urlConn0.getURL().toURI(), test_asset_test3_rel); + final Uri uri1 = Uri.valueOf(urlConn0.getURL()).getRelativeOf(test_asset_test3_rel); Assert.assertNotNull(uri1); // JARFile URL .. testAssetConnection(uri1.toURL().openConnection(), test_asset_test3_entry); - final URI uri2 = IOUtil.getRelativeOf(urlConn0.getURL().toURI(), test_asset_test4_rel); + final Uri uri2 = Uri.valueOf(urlConn0.getURL()).getRelativeOf(test_asset_test4_rel); Assert.assertNotNull(uri2); testAssetConnection(uri2.toURL().openConnection(), test_asset_test4_entry); } diff --git a/src/junit/com/jogamp/common/net/TestUri01.java b/src/junit/com/jogamp/common/net/TestUri01.java new file mode 100644 index 0000000..361304c --- /dev/null +++ b/src/junit/com/jogamp/common/net/TestUri01.java @@ -0,0 +1,363 @@ +package com.jogamp.common.net; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; + +import jogamp.common.os.PlatformPropsImpl; + +import org.junit.Assert; +import org.junit.Test; + +import com.jogamp.common.net.URIDumpUtil; +import com.jogamp.common.os.Platform; +import com.jogamp.junit.util.JunitTracer; + +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestUri01 extends JunitTracer { + + @Test + public void test00URIEscapeSpecialChars() throws IOException, URISyntaxException { + { + final String vanilla = "XXX ! # $ & ' ( ) * + , / : ; = ? @ [ ]"; + final Uri.Encoded escaped = Uri.Encoded.cast("XXX%20!%20%23%20%24%20%26%20%27%20%28%20%29%20%2A%20%2B%20%2C%20/%20%3A%20%3B%20%3D%20%3F%20%40%20%5B%20%5D"); + System.err.println("vanilla "+vanilla); + final Uri.Encoded esc1 = new Uri.Encoded(vanilla, Uri.PATH_MIN_LEGAL); + System.err.println("esc1 "+esc1); + Assert.assertEquals(escaped, esc1); + + final String invEsc1 = esc1.decode(); + System.err.println("inv(esc1) "+invEsc1); + Assert.assertEquals(vanilla, invEsc1); + } + { + final String vanilla = "/XXX R!# R$&'()*+,/:;=?z@y[x]"; + final Uri.Encoded escaped = Uri.Encoded.cast("/XXX%20R!%23%20R%24%26%27%28%29%2A%2B%2C/%3A%3B%3D%3Fz%40y%5Bx%5D"); + System.err.println("vanilla "+vanilla); + final Uri.Encoded esc1 = new Uri.Encoded(vanilla, Uri.PATH_MIN_LEGAL); + System.err.println("esc1 "+esc1); + Assert.assertEquals(escaped, esc1); + + final String invEsc1 = esc1.decode(); + System.err.println("inv(esc1) "+invEsc1); + Assert.assertEquals(vanilla, invEsc1); + } + { + // Bug 908: $ ^ ~ # [ ] + final String vanilla = "/XXX $ ^ ~ # [ ]"; + showDump0x(vanilla); + } + { + // Windows invalid File characters: * ? " < > | + final String vanilla = "/XXX ! & ' ( ) + , / ; = @ [ ]"; + showDump0x(vanilla); + } + } + @Test + public void test01URIEscapeCommonChars() throws IOException, URISyntaxException { + { + final String vanilla = "/XXX \"%-.<>\\^_`{|}~"; + final Uri.Encoded escaped = Uri.Encoded.cast("/XXX%20%22%25-.%3C%3E%5C%5E_%60%7B%7C%7D%7E"); + System.err.println("vanilla "+vanilla); + final Uri.Encoded esc1 = new Uri.Encoded(vanilla, Uri.PATH_MIN_LEGAL); + System.err.println("esc1 "+esc1); + Assert.assertEquals(escaped, esc1); + + final String invEsc1 = esc1.decode(); + System.err.println("inv(esc1) "+invEsc1); + Assert.assertEquals(vanilla, invEsc1); + showDump0x(vanilla); + } + } + private static void showDump0x(final String string) throws IOException, URISyntaxException { + final File file = new File(string); + System.err.println("file "+file); + System.err.println("file.path.dec "+file.getPath()); + System.err.println("file.path.abs "+file.getAbsolutePath()); + System.err.println("file.path.can "+file.getCanonicalPath()); + + System.err.println("File-path -> Uri:"); + final Uri uri0 = Uri.valueOfFilepath(string); + URIDumpUtil.showUri(uri0); + + System.err.println("Uri -> File:"); + final Uri uri2 = Uri.valueOf(file); + URIDumpUtil.showUri(uri2); + + System.err.println("Uri -> URI:"); + final URI uri3 = uri2.toURI(); + URIDumpUtil.showURI(uri3); + + System.err.println("URI -> Uri (keep encoding):"); + final Uri uri4 = Uri.valueOf(uri3, false); + URIDumpUtil.showUri(uri4); + + System.err.println("URI -> Uri (re-encode):"); + final Uri uri5 = Uri.valueOf(uri3, true); + URIDumpUtil.showUri(uri5); + } + + @Test + public void test03EqualsAndHashCode() throws IOException, URISyntaxException { + { + final Uri uri0 = Uri.cast("http://localhost/test01.html#tag01"); + final Uri uri1 = Uri.create("http", null, "localhost", -1, "/test01.html", null, "tag01"); + final Uri uri2 = Uri.create("http", "localhost", "/test01.html", "tag01"); + + Assert.assertEquals(uri0, uri1); + Assert.assertEquals(uri0.hashCode(), uri1.hashCode()); + + Assert.assertEquals(uri0, uri2); + Assert.assertEquals(uri0.hashCode(), uri2.hashCode()); + + Assert.assertEquals(uri1, uri2); + Assert.assertEquals(uri1.hashCode(), uri2.hashCode()); + + final Uri uriA = Uri.create("http", null, "localhost", -1, "/test02.html", null, "tag01"); + final Uri uriB = Uri.create("http", null, "localhost", -1, "/test01.html", null, "tag02"); + final Uri uriC = Uri.create("http", null, "lalalhost", -1, "/test01.html", null, "tag01"); + final Uri uriD = Uri.create("sftp", null, "localhost", -1, "/test01.html", null, "tag01"); + + Assert.assertNotEquals(uri1, uriA); + Assert.assertNotEquals(uri1, uriB); + Assert.assertNotEquals(uri1, uriC); + Assert.assertNotEquals(uri1, uriD); + } + { // 3 [scheme:][//[user-info@]host[:port]]path[?query][#fragment] + final Uri uri0 = Uri.cast("http://user@localhost:80/test01.html?test=01&test=02#tag01"); + final Uri uri1 = Uri.create("http", "user", "localhost", 80, "/test01.html", "test=01&test=02", "tag01"); + + Assert.assertEquals(uri0, uri1); + Assert.assertEquals(uri0.hashCode(), uri1.hashCode()); + + final Uri uriA = Uri.cast("http://user@localhost:80/test01.html?test=01&test=02#tag02"); + final Uri uriB = Uri.cast("http://user@localhost:80/test01.html?test=01&test=03#tag01"); + final Uri uriC = Uri.cast("http://user@localhost:80/test04.html?test=01&test=02#tag01"); + final Uri uriD = Uri.cast("http://user@localhost:88/test01.html?test=01&test=02#tag01"); + final Uri uriE = Uri.cast("http://user@lalalhost:80/test01.html?test=01&test=02#tag01"); + final Uri uriF = Uri.cast("http://test@localhost:80/test01.html?test=01&test=02#tag01"); + final Uri uriG = Uri.cast("sftp://user@localhost:80/test01.html?test=01&test=02#tag01"); + + Assert.assertNotEquals(uri1, uriA); + Assert.assertNotEquals(uri1, uriB); + Assert.assertNotEquals(uri1, uriC); + Assert.assertNotEquals(uri1, uriD); + Assert.assertNotEquals(uri1, uriE); + Assert.assertNotEquals(uri1, uriF); + Assert.assertNotEquals(uri1, uriG); + } + } + + @Test + public void test04ContainedUri() throws IOException, URISyntaxException { + { + final Uri input = Uri.cast("http://localhost/test01.html#tag01"); + final Uri contained = input.getContainedUri(); + Assert.assertNull(contained); + } + { + 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(); + Assert.assertEquals(expected, contained); + Assert.assertEquals(expected.hashCode(), contained.hashCode()); + } + { + final Uri input = Uri.cast("jar:file://localhost/test01.jar!/"); + final Uri expected = Uri.cast("file://localhost/test01.jar"); + final Uri contained = input.getContainedUri(); + Assert.assertEquals(expected, contained); + Assert.assertEquals(expected.hashCode(), contained.hashCode()); + } + { + 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(); + Assert.assertEquals(expected, contained); + Assert.assertEquals(expected.hashCode(), contained.hashCode()); + } + } + + @Test + public void test05ParentUri() throws IOException, URISyntaxException { + { + final Uri input = Uri.cast("http://localhost/"); + final Uri parent = input.getParent(); + Assert.assertNull(parent); + } + { + final Uri input = Uri.cast("jar:http://localhost/test01.jar!/com/Lala.class"); + final Uri expected1 = Uri.cast("jar:http://localhost/test01.jar!/com/"); + final Uri expected2 = Uri.cast("jar:http://localhost/test01.jar!/"); + final Uri expected3 = Uri.cast("jar:http://localhost/"); + final Uri parent1 = input.getParent(); + final Uri parent2 = parent1.getParent(); + final Uri parent3 = parent2.getParent(); + Assert.assertEquals(expected1, parent1); + Assert.assertEquals(expected1.hashCode(), parent1.hashCode()); + Assert.assertEquals(expected2, parent2); + Assert.assertEquals(expected2.hashCode(), parent2.hashCode()); + Assert.assertEquals(expected3, parent3); + Assert.assertEquals(expected3.hashCode(), parent3.hashCode()); + } + { + final Uri input = Uri.cast("http://localhost/dir/test01.jar?lala=01#frag01"); + final Uri expected1 = Uri.cast("http://localhost/dir/"); + final Uri expected2 = Uri.cast("http://localhost/"); + final Uri parent1 = input.getParent(); + final Uri parent2 = parent1.getParent(); + Assert.assertEquals(expected1, parent1); + Assert.assertEquals(expected1.hashCode(), parent1.hashCode()); + Assert.assertEquals(expected2, parent2); + Assert.assertEquals(expected2.hashCode(), parent2.hashCode()); + } + } + + @Test + public void test10HttpUri2URL() throws IOException, URISyntaxException { + testUri2URL(getSimpleTestName("."), TestUri03Resolving.uriHttpSArray); + } + + @Test + public void test20FileUnixUri2URL() throws IOException, URISyntaxException { + testUri2URL(getSimpleTestName("."), TestUri03Resolving.uriFileSArrayUnix); + } + + @Test + public void test21FileWindowsUri2URL() throws IOException, URISyntaxException { + testUri2URL(getSimpleTestName("."), TestUri03Resolving.uriFileSArrayWindows); + } + + @Test + public void test30FileUnixUri2URL() throws IOException, URISyntaxException { + if( Platform.OSType.WINDOWS != PlatformPropsImpl.OS_TYPE ) { + testFile2Uri(getSimpleTestName("."), TestUri03Resolving.fileSArrayUnix); + } + } + + @Test + public void test31FileWindowsUri2URL() throws IOException, URISyntaxException { + if( Platform.OSType.WINDOWS == PlatformPropsImpl.OS_TYPE ) { + testFile2Uri(getSimpleTestName("."), TestUri03Resolving.fileSArrayWindows); + } + } + + static void testUri2URL(final String testname, final String[][] uriSArray) throws IOException, URISyntaxException { + boolean ok = true; + for(int i=0; i<uriSArray.length; i++) { + final String[] uriSPair = uriSArray[i]; + final String uriSource = uriSPair[0]; + System.err.println("SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS "+testname+": "+(i+1)+"/"+uriSArray.length); + ok = testUri2URL(Uri.Encoded.cast(uriSource)) && ok; + System.err.println("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE "+testname+": "+(i+1)+"/"+uriSArray.length); + } + Assert.assertTrue("One or more errors occured see stderr above", ok); + } + + static boolean testUri2URL(final Uri.Encoded uriSource) throws IOException, URISyntaxException { + System.err.println("uriSource : "+uriSource); + final Uri uri0 = new Uri(uriSource); + URIDumpUtil.showUri(uri0); + + final URI actualURI = uri0.toURI(); + URIDumpUtil.showURI(actualURI); + final Uri.Encoded actualURIStr = Uri.Encoded.cast(actualURI.toString()); + + final URL actualURL = uri0.toURL(); + URIDumpUtil.showURL(actualURL); + final Uri.Encoded actualURLStr = Uri.Encoded.cast(actualURL.toExternalForm()); + + System.err.println("expected_URX: "+uriSource); + + final boolean equalsURI = uriSource.equals(actualURIStr); + System.err.println("actual URI: "+actualURIStr+" - "+(equalsURI?"OK":"ERROR")); + final boolean equalsURL = uriSource.equals(actualURLStr); + System.err.println("actual URL: "+actualURLStr+" - "+(equalsURL?"OK":"ERROR")); + URIDumpUtil.showReencodedURIOfUri(uri0); + URIDumpUtil.showReencodedUriOfURI(actualURI); + + final boolean ok = equalsURL && equalsURI; + + // now test open .. + Throwable t = null; + URLConnection con = null; + try { + con = actualURL.openConnection(); + } catch (final Throwable _t) { + t = _t; + } + if( null != t ) { + System.err.println("XXX: "+t.getClass().getName()+": "+t.getMessage()); + t.printStackTrace(); + } else { + System.err.println("XXX: No openConnection() failure"); + System.err.println("XXX: "+con); + } + return ok; + } + + static void testFile2Uri(final String testname, final String[][] uriSArray) throws IOException, URISyntaxException { + boolean ok = true; + for(int i=0; i<uriSArray.length; i++) { + final String[] uriSPair = uriSArray[i]; + final String uriSource = uriSPair[0]; + final String uriEncExpected= uriSPair[1]; + final String fileExpected= uriSPair[2]; + System.err.println("SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS "+testname+": "+(i+1)+"/"+uriSArray.length); + ok = testFile2Uri(uriSource, Uri.Encoded.cast(uriEncExpected), fileExpected) && ok; + System.err.println("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE "+testname+": "+(i+1)+"/"+uriSArray.length); + } + Assert.assertTrue("One or more errors occured see stderr above", ok); + } + + static boolean testFile2Uri(final String fileSource, final Uri.Encoded uriEncExpected, final String fileExpected) throws IOException, URISyntaxException { + System.err.println("fileSource: "+fileSource); + final File file = new File(fileSource); + System.err.println("file: "+file.getAbsolutePath()); + final Uri uri1 = Uri.valueOf(file); + System.err.println("uri1.string: "+uri1.toString()); + URIDumpUtil.showUri(uri1); + URIDumpUtil.showURL(uri1.toURL()); + URIDumpUtil.showReencodedURIOfUri(uri1); + + final URL actualUrl = uri1.toURL(); + final String actualFileS = uri1.getNativeFilePath(); + final boolean equalsFilePath = fileExpected.equals(actualFileS); + System.err.println("expected_path: "+fileExpected); + System.err.println("actual___file-path: "+actualFileS+" - "+(equalsFilePath?"OK":"ERROR")); + final boolean equalsEncUri = uriEncExpected.equals(uri1.input); + System.err.println("expected__encUri: "+uriEncExpected); + System.err.println("actual_______Uri: "+uri1.input+" - "+(equalsEncUri?"OK":"ERROR")); + final boolean ok = equalsEncUri && equalsFilePath; + + System.err.println("actual_______URL: "+actualUrl.toExternalForm()); + + // now test open .. + Throwable t = null; + URLConnection con = null; + try { + con = actualUrl.openConnection(); + } catch (final Throwable _t) { + t = _t; + } + if( null != t ) { + System.err.println("XXX: "+t.getClass().getName()+": "+t.getMessage()); + t.printStackTrace(); + } else { + System.err.println("XXX: No openConnection() failure"); + System.err.println("XXX: "+con); + } + return ok; + } + + public static void main(final String args[]) throws IOException { + final String tstname = TestUri01.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } +} diff --git a/src/junit/com/jogamp/common/util/TestIOUtilURICompose.java b/src/junit/com/jogamp/common/net/TestUri02Composing.java index 3369afd..33d17b8 100644 --- a/src/junit/com/jogamp/common/util/TestIOUtilURICompose.java +++ b/src/junit/com/jogamp/common/net/TestUri02Composing.java @@ -1,22 +1,34 @@ -package com.jogamp.common.util; +package com.jogamp.common.net; import java.io.IOException; import java.net.MalformedURLException; -import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; -import com.jogamp.common.util.IOUtil; import com.jogamp.junit.util.JunitTracer; import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; @FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class TestIOUtilURICompose extends JunitTracer { +public class TestUri02Composing extends JunitTracer { + + @BeforeClass + public static void assetRegistration() throws Exception { + try { + System.err.println("******* Asset URL Stream Handler Registration: PRE"); + Assert.assertTrue("GenericURLStreamHandlerFactory.register() failed", AssetURLContext.registerHandler(AssetURLConnectionRegisteredTest.class.getClassLoader())); + Assert.assertNotNull(AssetURLContext.getRegisteredHandler()); + System.err.println("******* Asset URL Stream Handler Registration: POST"); + } catch (final Exception e) { + setTestSupported(false); + throw e; + } + } @Test public void test01URLCompositioning() throws IOException, URISyntaxException { @@ -28,10 +40,10 @@ public class TestIOUtilURICompose extends JunitTracer { testURNCompositioning("http://domain.com/web1/index.html?lala=23&lili=24#anchor"); testURNCompositioning("http://domain.com:1234/web1/index.html?lala=23&lili=24#anchor"); - final URI file1URI = new URI("asset:jar:file:/web1/file1.jar!/rootDir/file1.txt"); + final Uri file1URI = Uri.cast("asset:jar:file:/web1/file1.jar!/rootDir/file1.txt"); testURICompositioning(file1URI); - testURICompositioning(file1URI, new URI("asset:jar:file:/web1/file1.jar!/rootDir/./file1.txt")); - testURICompositioning(file1URI, new URI("asset:jar:file:/web1/file1.jar!/rootDir/dummyParent/../file1.txt")); + testUriCompositioning(file1URI, Uri.cast("asset:jar:file:/web1/file1.jar!/rootDir/./file1.txt")); + testUriCompositioning(file1URI, Uri.cast("asset:jar:file:/web1/file1.jar!/rootDir/dummyParent/../file1.txt")); final URL file1URL = new URL("asset:jar:file:/web1/file1.jar!/rootDir/file1.txt"); testURLCompositioning(file1URL); @@ -40,20 +52,16 @@ public class TestIOUtilURICompose extends JunitTracer { } static void testURNCompositioning(final String urn) throws MalformedURLException, URISyntaxException { - testURICompositioning( new URI(urn) ); + testURICompositioning( Uri.cast(urn) ); testURLCompositioning( new URL(urn) ); } - static void testURICompositioning(final URI uri) throws MalformedURLException, URISyntaxException { - testURICompositioning(uri, uri); + static void testURICompositioning(final Uri uri) throws MalformedURLException, URISyntaxException { + testUriCompositioning(uri, uri); } - static void testURICompositioning(final URI refURI, final URI uri1) throws MalformedURLException, URISyntaxException { - final String scheme = uri1.getScheme(); - final String ssp = uri1.getRawSchemeSpecificPart(); - final String fragment = uri1.getRawFragment(); - - System.err.println("scheme <"+scheme+">, ssp <"+ssp+">, fragment <"+fragment+">"); - final URI uri2 = IOUtil.compose(scheme, ssp, null, fragment); + 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); System.err.println("URL-equals: "+refURI.equals(uri2)); System.err.println("URL-ref : <"+refURI+">"); @@ -66,13 +74,9 @@ public class TestIOUtilURICompose extends JunitTracer { testURLCompositioning(url, url); } static void testURLCompositioning(final URL refURL, final URL url1) throws MalformedURLException, URISyntaxException { - final URI uri1 = url1.toURI(); - final String scheme = uri1.getScheme(); - final String ssp = uri1.getRawSchemeSpecificPart(); - final String fragment = uri1.getRawFragment(); - - System.err.println("scheme <"+scheme+">, ssp <"+ssp+">, fragment <"+fragment+">"); - final URI uri2 = IOUtil.compose(scheme, ssp, null, fragment); + 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); System.err.println("URL-equals(1): "+refURL.toURI().equals(uri2)); System.err.println("URL-equals(2): "+refURL.equals(uri2.toURL())); @@ -80,13 +84,13 @@ public class TestIOUtilURICompose extends JunitTracer { System.err.println("URL-ref : <"+refURL+">"); System.err.println("URL-orig : <"+url1+">"); System.err.println("URL-comp : <"+uri2+">"); - Assert.assertEquals(refURL.toURI(), uri2); + Assert.assertEquals(Uri.valueOf(refURL), uri2); Assert.assertEquals(refURL, uri2.toURL()); Assert.assertTrue(refURL.sameFile(uri2.toURL())); } public static void main(final String args[]) throws IOException { - final String tstname = TestIOUtilURICompose.class.getName(); + final String tstname = TestUri02Composing.class.getName(); org.junit.runner.JUnitCore.main(tstname); } } diff --git a/src/junit/com/jogamp/common/util/TestIOUtilURIHandling.java b/src/junit/com/jogamp/common/net/TestUri03Resolving.java index 66dbacf..0d65363 100644 --- a/src/junit/com/jogamp/common/util/TestIOUtilURIHandling.java +++ b/src/junit/com/jogamp/common/net/TestUri03Resolving.java @@ -1,4 +1,4 @@ -package com.jogamp.common.util; +package com.jogamp.common.net; import java.io.File; import java.io.IOException; @@ -12,7 +12,7 @@ import jogamp.common.os.PlatformPropsImpl; import org.junit.Assert; import org.junit.Test; -import com.jogamp.common.net.URIDumpUtil; +import com.jogamp.common.net.Uri; import com.jogamp.common.os.Platform; import com.jogamp.common.util.IOUtil; import com.jogamp.junit.util.JunitTracer; @@ -21,7 +21,7 @@ import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; @FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class TestIOUtilURIHandling extends JunitTracer { +public class TestUri03Resolving extends JunitTracer { // Bug 908, issues w/ windows file path char: $ ^ ~ # [ ] @@ -117,40 +117,33 @@ public class TestIOUtilURIHandling extends JunitTracer { "/gluegen/build-x86_64 öä lala/gluegen-rt.jar"}, new String[] {"/gluegen/A$/B^/C~/D#/E[/F]/gluegen-rt.jar", - // "file:/gluegen/A%24/B%5E/C%7E/D%23/E%5B/F%5D/gluegen-rt.jar", // goal w/ new Uri class - "file:/gluegen/A$/B%5E/C~/D%23/E%5B/F%5D/gluegen-rt.jar", // current Java URI/URL decoding - "/gluegen/A$/B^/C~/D#/E[/F]/gluegen-rt.jar" }, // goal w/ new Uri class + "file:/gluegen/A%24/B%5E/C%7E/D%23/E%5B/F%5D/gluegen-rt.jar", + "/gluegen/A$/B^/C~/D#/E[/F]/gluegen-rt.jar" }, new String[] {"/gluegen/$/^/~/#/[/]/gluegen-rt.jar", - // "file:/gluegen/%24/%5E/%7E/%23/%5B/%5D/gluegen-rt.jar", - "file:/gluegen/$/%5E/~/%23/%5B/%5D/gluegen-rt.jar", // current Java URI/URL decoding + "file:/gluegen/%24/%5E/%7E/%23/%5B/%5D/gluegen-rt.jar", "/gluegen/$/^/~/#/[/]/gluegen-rt.jar" }, }; public static final String[][] fileSArrayWindows = new String[][] { new String[] {"C:/gluegen/build-x86_64/gluegen-rt.jar", - // "file:/C%3A/gluegen/build-x86_64/gluegen-rt.jar", - "file:/C:/gluegen/build-x86_64/gluegen-rt.jar", + "file:/C%3A/gluegen/build-x86_64/gluegen-rt.jar", "C:\\gluegen\\build-x86_64\\gluegen-rt.jar"}, new String[] {"C:/gluegen/"+'\u0394'+"/gluegen-rt.jar", - // "file:/C%3A/gluegen/"+'\u0394'+"/gluegen-rt.jar", - "file:/C:/gluegen/"+'\u0394'+"/gluegen-rt.jar", + "file:/C%3A/gluegen/"+'\u0394'+"/gluegen-rt.jar", "C:\\gluegen\\"+'\u0394'+"\\gluegen-rt.jar"}, new String[] {"C:/gluegen/build-x86_64 lala/gluegen-rt.jar", - // "file:/C%3A/gluegen/build-x86_64%20lala/gluegen-rt.jar", - "file:/C:/gluegen/build-x86_64%20lala/gluegen-rt.jar", + "file:/C%3A/gluegen/build-x86_64%20lala/gluegen-rt.jar", "C:\\gluegen\\build-x86_64 lala\\gluegen-rt.jar"}, new String[] {"C:/gluegen/build-x86_64 öä lala/gluegen-rt.jar", - // "file:/C%3A/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar", - "file:/C:/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar", + "file:/C%3A/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar", "C:\\gluegen\\build-x86_64 öä lala\\gluegen-rt.jar"}, new String[] {"C:\\gluegen\\build-x86_64 öä lala\\gluegen-rt.jar", - // "file:/C%3A/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar", - "file:/C:/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar", + "file:/C%3A/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar", "C:\\gluegen\\build-x86_64 öä lala\\gluegen-rt.jar"}, new String[] {"\\\\filehost\\gluegen\\build-x86_64 öä lala\\gluegen-rt.jar", @@ -158,13 +151,11 @@ public class TestIOUtilURIHandling extends JunitTracer { "\\\\filehost\\gluegen\\build-x86_64 öä lala\\gluegen-rt.jar"}, new String[] {"C:/gluegen/A$/B^/C~/D#/E[/F]/gluegen-rt.jar", - // "file:/C%3A/gluegen/A%24/B%5E/C%7E/D%23/E%5B/F%5D/gluegen-rt.jar", - "file:/C:/gluegen/A$/B%5E/C~/D%23/E%5B/F%5D/gluegen-rt.jar", + "file:/C%3A/gluegen/A%24/B%5E/C%7E/D%23/E%5B/F%5D/gluegen-rt.jar", "C:\\gluegen\\A$\\B^\\C~\\D#\\E[\\F]\\gluegen-rt.jar" }, new String[] {"C:/gluegen/$/^/~/#/[/]/gluegen-rt.jar", - // "file:/C%3A/gluegen/%24/%5E/%7E/%23/%5B/%5D/gluegen-rt.jar", - "file:/C:/gluegen/$/%5E/~/%23/%5B/%5D/gluegen-rt.jar", // current Java URI/URL decoding + "file:/C%3A/gluegen/%24/%5E/%7E/%23/%5B/%5D/gluegen-rt.jar", "C:\\gluegen\\$\\^\\~\\#\\[\\]\\gluegen-rt.jar" }, }; @@ -183,7 +174,7 @@ public class TestIOUtilURIHandling extends JunitTracer { System.err.println("uri0.ascii : "+uri0.toASCIIString()); boolean ok = true; { - final URI uri1 = IOUtil.toURISimple(file); + final URI uri1 = Uri.valueOf(file).toURI(); final boolean equalString= uri0.toString().equals(uri1.toString()); final boolean equalPath = uri0.getPath().equals(uri1.getPath()); final boolean equalASCII= uri0.toASCIIString().equals(uri1.toASCIIString()); @@ -196,19 +187,14 @@ public class TestIOUtilURIHandling extends JunitTracer { final String s2 = IOUtil.slashify(file.getAbsolutePath(), true /* startWithSlash */, file.isDirectory() /* endWithSlash */); System.err.println("uri2.slashify: "+s2); { - // Expected !equals due to double-escaping of space %20 -> %25%20 - // Double escaping is due to IOUtil.encodeToURI(s2). - @SuppressWarnings("deprecation") - final String s3 = IOUtil.encodeToURI(s2); - System.err.println("uri2.encoded: "+s3); - final URI uri1 = new URI(IOUtil.FILE_SCHEME, null, s3, null); + final URI uri1 = new URI(IOUtil.FILE_SCHEME, null, s2, null); final boolean equalString= uri0.toString().equals(uri1.toString()); final boolean equalPath = uri0.getPath().equals(uri1.getPath()); final boolean equalASCII= uri0.toASCIIString().equals(uri1.toASCIIString()); - System.err.println("uri2.string: "+uri1.toString()+" - "+(equalString?"ERROR EQUAL":"OK NOT_EQUAL")); - System.err.println("uri2.path : "+uri1.getPath()+" - "+(equalPath?"ERROR EQUAL":"OK NOT_EQUAL")); - System.err.println("uri2.ascii : "+uri1.toASCIIString()+" - "+(equalASCII?"ERROR EQUAL":"OK NOT_EQUAL")); - ok = !equalString && !equalPath && !equalASCII && ok; + System.err.println("uri2.string: "+uri1.toString()+" - "+(equalString?"OK":"ERROR")); + System.err.println("uri2.path : "+uri1.getPath()+" - "+(equalPath?"OK":"ERROR")); + System.err.println("uri2.ascii : "+uri1.toASCIIString()+" - "+(equalASCII?"OK":"ERROR")); + ok = equalString && equalPath && equalASCII && ok; } final URI uri1 = new URI(IOUtil.FILE_SCHEME, null, s2, null); final boolean equalString= uri0.toString().equals(uri1.toString()); @@ -287,30 +273,27 @@ public class TestIOUtilURIHandling extends JunitTracer { final String[] uriSPair = uriSArray[i]; final String uriSource = uriSPair[0]; System.err.println("SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS "+testname+": "+(i+1)+"/"+uriSArray.length); - ok = testURI2URL(uriSource) && ok; + ok = testURI2URL(Uri.Encoded.cast(uriSource)) && ok; System.err.println("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE "+testname+": "+(i+1)+"/"+uriSArray.length); } Assert.assertTrue("One or more errors occured see stderr above", ok); } - static boolean testURI2URL(final String uriSource) throws IOException, URISyntaxException { - final URI uri0 = new URI(uriSource); - URIDumpUtil.showURI(uri0); + static boolean testURI2URL(final Uri.Encoded uriEncodedSource) throws IOException, URISyntaxException { + final Uri uri0 = new Uri(uriEncodedSource); + URIDumpUtil.showUri(uri0); - final String expected1 = uriSource.toString(); - final String expected2 = expected1.replaceFirst("///", "/"); - System.err.println("expected__s0: "+uriSource); + final String expected1 = uriEncodedSource.toString(); + System.err.println("expected__s0: "+uriEncodedSource); System.err.println("expected__d1: "+expected1); - System.err.println("expected__d2: "+expected2); final URL actualURL = uri0.toURL(); final String actualURLStr = actualURL.toString(); - final boolean equalsURLSrc = uriSource.equals(actualURLStr); + final boolean equalsURLSrc = uriEncodedSource.equals(actualURLStr); final boolean equalsURLDec1 = expected1.equals(actualURLStr); - final boolean equalsURLDec2 = expected2.equals(actualURLStr); - final boolean equalsURL = equalsURLSrc || equalsURLDec1 || equalsURLDec2; + final boolean equalsURL = equalsURLSrc || equalsURLDec1; System.err.println("actual : "+actualURLStr+" - "+(equalsURL?"OK":"ERROR")+ - " - equalSrc "+equalsURLSrc+", equalDec1 "+equalsURLDec1+", equalDec2 "+equalsURLDec2); + " - equalSrc "+equalsURLSrc+", equalDec1 "+equalsURLDec1); final boolean ok = equalsURL; @@ -340,30 +323,28 @@ public class TestIOUtilURIHandling extends JunitTracer { final String uriEncExpected= uriSPair[1]; final String fileExpected= uriSPair[2]; System.err.println("SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS "+testname+": "+(i+1)+"/"+uriSArray.length); - ok = testFile2URI(uriSource, uriEncExpected, fileExpected) && ok; + ok = testFile2URI(uriSource, Uri.Encoded.cast(uriEncExpected), fileExpected) && ok; System.err.println("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE "+testname+": "+(i+1)+"/"+uriSArray.length); } Assert.assertTrue("One or more errors occured see stderr above", ok); } - static boolean testFile2URI(final String fileSource, final String uriEncExpected, final String fileExpected) throws IOException, URISyntaxException { + static boolean testFile2URI(final String fileSource, final Uri.Encoded uriEncExpected, final String fileExpected) throws IOException, URISyntaxException { System.err.println("fileSource: "+fileSource); final File file = new File(fileSource); System.err.println("file: "+file.getAbsolutePath()); - final URI uri0 = IOUtil.toURISimple(file); - final URI uri1 = file.toURI(); - URIDumpUtil.showURI(uri0); - URIDumpUtil.showURI(uri1); + final Uri uri0 = Uri.valueOf(file); + URIDumpUtil.showReencodedURIOfUri(uri0); - final URL actualUrl = uri1.toURL(); - final String actualFileS = IOUtil.decodeURIIfFilePath(uri1); + final URL actualUrl = uri0.toURL(); + final String actualFileS = uri0.getNativeFilePath(); final boolean equalsFilePath = fileExpected.equals(actualFileS); System.err.println("expected_path: "+fileExpected); System.err.println("actual___file-path: "+actualFileS+" - "+(equalsFilePath?"OK":"ERROR")); - final boolean equalsEncUri = uriEncExpected.equals(uri1.toString()); + final boolean equalsEncUri = uriEncExpected.equals(uri0.getEncoded()); System.err.println("expected__encUri: "+uriEncExpected); - System.err.println("actual_______Uri: "+uri1.toString()+" - "+(equalsEncUri?"OK":"ERROR")); + System.err.println("actual_______Uri: "+uri0.getEncoded()+" - "+(equalsEncUri?"OK":"ERROR")); final boolean ok = equalsEncUri && equalsFilePath; System.err.println("actual_______URL: "+actualUrl.toExternalForm()); @@ -387,7 +368,7 @@ public class TestIOUtilURIHandling extends JunitTracer { } public static void main(final String args[]) throws IOException { - final String tstname = TestIOUtilURIHandling.class.getName(); + final String tstname = TestUri03Resolving.class.getName(); org.junit.runner.JUnitCore.main(tstname); } } diff --git a/src/junit/com/jogamp/common/net/TestNetIOURIReservedCharsBug908.java b/src/junit/com/jogamp/common/net/TestUri99LaunchOnReservedCharPathBug908.java index 904fca5..011b18c 100644 --- a/src/junit/com/jogamp/common/net/TestNetIOURIReservedCharsBug908.java +++ b/src/junit/com/jogamp/common/net/TestUri99LaunchOnReservedCharPathBug908.java @@ -71,7 +71,7 @@ import com.jogamp.junit.util.MiscUtils; * </p> */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class TestNetIOURIReservedCharsBug908 extends JunitTracer { +public class TestUri99LaunchOnReservedCharPathBug908 extends JunitTracer { static class TestClassLoader extends URLClassLoader { public TestClassLoader(final URL[] urls) { super(urls); @@ -163,7 +163,7 @@ public class TestNetIOURIReservedCharsBug908 extends JunitTracer { } public static void main(final String args[]) throws IOException { - final String tstname = TestNetIOURIReservedCharsBug908.class.getName(); + final String tstname = TestUri99LaunchOnReservedCharPathBug908.class.getName(); org.junit.runner.JUnitCore.main(tstname); } diff --git a/src/junit/com/jogamp/common/net/URIDumpUtil.java b/src/junit/com/jogamp/common/net/URIDumpUtil.java index 1a74742..a7d050a 100644 --- a/src/junit/com/jogamp/common/net/URIDumpUtil.java +++ b/src/junit/com/jogamp/common/net/URIDumpUtil.java @@ -42,4 +42,57 @@ public class URIDumpUtil { System.err.println("2.3.0 query: "+uri.getRawQuery()+" (raw), "+uri.getQuery()+" (dec)"); System.err.println("3.0.0 fragment: "+uri.getRawFragment()+" (raw), "+uri.getFragment()+" (dec)"); } + + public static void showUri(final Uri uri) throws URISyntaxException { + showUri("ZZZZZZ Uri "+uri+", isOpaque "+uri.opaque+", isAbs "+uri.absolute+", hasAuth "+uri.hasAuthority, uri); + } + + public static void showUri(final String message, final Uri uri) throws URISyntaxException { + System.err.println(message); + + System.err.println("0.0.0 string: "+uri.toString()); + System.err.println("0.0.0 ascii : "+uri.toASCIIString()); + System.err.println("0.0.0 native-file: "+uri.getNativeFilePath()); + System.err.println("0.0.0 contained: "+uri.getContainedUri()); + + System.err.println("1.0.0 scheme: "+uri.scheme); + System.err.println("2.0.0 scheme-part: "+uri.schemeSpecificPart+" (raw), "+Uri.decode(uri.schemeSpecificPart)+" (dec)"); + System.err.println("2.1.0 auth: "+uri.authority+" (raw), "+Uri.decode(uri.authority)+" (dec)"); + System.err.println("2.1.1 user-info: "+uri.userInfo+" (raw), "+Uri.decode(uri.userInfo)+" (dec)"); + System.err.println("2.1.1 host: "+uri.host); + System.err.println("2.1.1 port: "+uri.port); + System.err.println("2.2.0 path: "+uri.path+" (raw), "+Uri.decode(uri.path)+" (dec)"); + System.err.println("2.3.0 query: "+uri.query+" (raw), "+Uri.decode(uri.query)+" (dec)"); + System.err.println("3.0.0 fragment: "+uri.fragment+" (raw), "+Uri.decode(uri.fragment)+" (dec)"); + } + + /** + * Just showing different encoding of Uri -> URI + * + * @param uri + * @throws URISyntaxException + */ + public static void showReencodedURIOfUri(final Uri uri) throws URISyntaxException { + final URI recomposedURI = uri.toURI(true); + showURI("YYYYYY Recomposed URI "+recomposedURI+", isOpaque "+recomposedURI.isOpaque()+", isAbs "+recomposedURI.isAbsolute(), recomposedURI); + final String recomposedURIStr = recomposedURI.toString(); + final boolean equalsRecompURI = uri.input.equals(recomposedURIStr); + System.err.println("source Uri: "+uri.input); + System.err.println("recomp URI: "+recomposedURIStr+" - "+(equalsRecompURI?"EQUAL":"UNEQUAL")); + } + + /** + * Just showing different encoding of URI -> Uri + * + * @param uri + * @throws URISyntaxException + */ + public static void showReencodedUriOfURI(final URI uri) throws URISyntaxException { + final Uri recomposedUri = Uri.valueOf(uri, true); + showUri("ZZZZZZ Recomposed Uri "+recomposedUri+", isOpaque "+recomposedUri.opaque+", isAbs "+recomposedUri.absolute+", hasAuth "+recomposedUri.hasAuthority, recomposedUri); + final String recomposedUriStr = recomposedUri.toString(); + final boolean equalsRecompUri = uri.toString().equals(recomposedUriStr); + System.err.println("source URI: "+uri.toString()); + System.err.println("recomp Uri: "+recomposedUriStr+" - "+(equalsRecompUri?"EQUAL":"UNEQUAL")); + } } diff --git a/src/junit/com/jogamp/common/util/TestTempJarCache.java b/src/junit/com/jogamp/common/util/TestTempJarCache.java index ce06e9a..cd825fe 100644 --- a/src/junit/com/jogamp/common/util/TestTempJarCache.java +++ b/src/junit/com/jogamp/common/util/TestTempJarCache.java @@ -29,7 +29,6 @@ package com.jogamp.common.util; import java.io.File; - import java.io.IOException; import java.lang.reflect.Method; import java.net.URI; @@ -45,7 +44,6 @@ import org.junit.Test; import org.junit.runners.MethodSorters; import com.jogamp.common.GlueGenVersion; -import com.jogamp.common.jvm.JNILibLoaderBase; import com.jogamp.common.os.AndroidVersion; import com.jogamp.common.os.NativeLibrary; import com.jogamp.common.os.Platform; diff --git a/src/junit/com/jogamp/common/util/TestVersionSemantics.java b/src/junit/com/jogamp/common/util/TestVersionSemantics.java index 77d3b72..edc0dc2 100644 --- a/src/junit/com/jogamp/common/util/TestVersionSemantics.java +++ b/src/junit/com/jogamp/common/util/TestVersionSemantics.java @@ -67,8 +67,8 @@ import com.jogamp.junit.util.VersionSemanticsUtil; public class TestVersionSemantics extends JunitTracer { static final String jarFile = "gluegen-rt.jar"; static final VersionNumberString preVersionNumber = new VersionNumberString("2.2.0"); - // static final Delta.CompatibilityType expectedCompatibilityType = Delta.CompatibilityType.NON_BACKWARD_COMPATIBLE; - static final Delta.CompatibilityType expectedCompatibilityType = Delta.CompatibilityType.BACKWARD_COMPATIBLE_USER; + static final Delta.CompatibilityType expectedCompatibilityType = Delta.CompatibilityType.NON_BACKWARD_COMPATIBLE; + // static final Delta.CompatibilityType expectedCompatibilityType = Delta.CompatibilityType.BACKWARD_COMPATIBLE_USER; static final DiffCriteria diffCriteria = new SimpleDiffCriteria(); // static final DiffCriteria diffCriteria = new PublicDiffCriteria(); |