<commit_before>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package permafrost.tundra.net.uri;

import com.wm.data.IData;
import com.wm.data.IDataCursor;
import com.wm.data.IDataFactory;
import com.wm.data.IDataUtil;
import permafrost.tundra.lang.ArrayHelper;
import permafrost.tundra.lang.CharsetHelper;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;

/**
 * A collection of convenience methods for working with URIs.
 */
public class URIHelper {
    /**
     * The default character set used for URI strings.
     */
    public static final Charset DEFAULT_CHARSET = Charset.forName("US-ASCII");
    /**
     * The default character set name used for URI strings.
     */
    public static final String DEFAULT_CHARSET_NAME = DEFAULT_CHARSET.name();

    /**
     * Disallow instantiation of this class.
     */
    private URIHelper() {}

    /**
     * Parses a URI string into an IData representation.
     *
     * @param input The URI string to be parsed.
     * @return An IData representation of the give URI.
     * @throws URISyntaxException If the given string is an invalid URI.
     */
    public static IData parse(String input) throws URISyntaxException {
        if (input == null) return null;

        // treat Windows UNC file URIs as server-based rather than path-based
        if (input.toLowerCase().startsWith("file:////")) {
            input = "file://" + input.substring(9, input.length());
        }

        IData output = IDataFactory.create();
        IDataCursor cursor = output.getCursor();

        try {
            URI uri = new URI(input);
            uri.normalize();

            String scheme = uri.getScheme();
            // schemes are case-insensitive, according to RFC 2396
            if (scheme != null) scheme = scheme.toLowerCase();
            if (scheme != null) IDataUtil.put(cursor, "scheme", scheme);

            IData query = null;
            String ssp = uri.getRawSchemeSpecificPart();

            if (uri.isOpaque()) {
                if (ssp.contains("?")) {
                    query = URIQueryHelper.parse(ssp.substring(ssp.indexOf("?") + 1, ssp.length()), true);
                    ssp = ssp.substring(0, ssp.indexOf("?"));
                }
                if (ssp != null) IDataUtil.put(cursor, "body", decode(ssp));
            } else {
                String authority = uri.getAuthority();
                if (authority != null) {
                    IData server = IDataFactory.create();
                    IDataCursor sc = server.getCursor();

                    // parse user-info component according to the format user:[password]
                    String user = uri.getUserInfo();
                    String password = <PASSWORD>;
                    String userInfoSeparator = ":";
                    if (user != null && user.contains(userInfoSeparator)) {
                        password = user.substring(user.indexOf(userInfoSeparator) + 1, user.length());
                        user = user.substring(0, user.indexOf(userInfoSeparator));
                    }
                    if (user != null) IDataUtil.put(sc, "user", user);
                    if (password != null) IDataUtil.put(sc, "password", password);

                    // hosts are case-insensitive, according to RFC 2396
                    String host = uri.getHost();
                    if (host != null) IDataUtil.put(sc, "host", host.toLowerCase());

                    // if port is -1, then it wasn't specified in the URI
                    int port = uri.getPort();
                    if (port >= 0) IDataUtil.put(sc, "port", "" + uri.getPort());

                    sc.destroy();

                    // if host is null, then this URI must be registry-based
                    IData authorityDocument = IDataFactory.create();
                    IDataCursor ac = authorityDocument.getCursor();
                    if (host == null) {
                        IDataUtil.put(ac, "registry", authority);
                    } else {
                        IDataUtil.put(ac, "server", server);
                    }
                    ac.destroy();

                    IDataUtil.put(cursor, "authority", authorityDocument);
                }

                String path = uri.getPath();
                String[] paths = URIPathHelper.parse(path);

                String file = null;
                if (!path.endsWith("/")) {
                    file = ArrayHelper.get(paths, -1);
                    paths = ArrayHelper.drop(paths, -1);
                }

                if (paths != null && paths.length > 0) IDataUtil.put(cursor, "path", paths);
                if (file != null && !file.equals("")) IDataUtil.put(cursor, "file", file);

                query = URIQueryHelper.parse(uri.getRawQuery(), true);
            }

            if (query != null) IDataUtil.put(cursor, "query", query);

            String fragment = uri.getFragment();
            if (fragment != null) IDataUtil.put(cursor, "fragment", fragment);

            IDataUtil.put(cursor, "absolute?", "" + uri.isAbsolute());
            IDataUtil.put(cursor, "opaque?", "" + uri.isOpaque());
        } finally {
            cursor.destroy();
        }

        return output;
    }

    /**
     * Emits a URI given as an IData document as a string.
     *
     * @param input An IData containing a specific URI structure to be serialized.
     * @return The URI string representing the given IData.
     * @throws URISyntaxException If the given string is an invalid URI.
     */
    public static String emit(IData input) throws URISyntaxException {
        if (input == null) return null;

        String output = null;
        IDataCursor cursor = input.getCursor();

        try {
            URI uri = null;
            String type = IDataUtil.getString(cursor, "type");

            String scheme = IDataUtil.getString(cursor, "scheme");
            // schemes are case-insensitive, according to RFC 2396
            if (scheme != null) scheme = scheme.toLowerCase();

            String fragment = IDataUtil.getString(cursor, "fragment");
            IData queryDocument = IDataUtil.getIData(cursor, "query");
            String query = URIQueryHelper.emit(queryDocument, true);

            String body = IDataUtil.getString(cursor, "body");
            IData authority = IDataUtil.getIData(cursor, "authority");
            String[] paths = IDataUtil.getStringArray(cursor, "path");
            String file = IDataUtil.getString(cursor, "file");

            if (body == null && !(authority == null && paths == null && file == null)) {
                // create an hierarchical URI
                String path = "";
                if (paths != null) {
                    path = "/" + ArrayHelper.join(paths, "/");
                }
                path = path + "/";
                if (file != null) {
                    path = path + file;
                }

                if (authority == null) {
                    uri = new URI(scheme, null, path, null, null);
                } else {
                    IDataCursor ac = authority.getCursor();
                    String registry = IDataUtil.getString(ac, "registry");
                    IData server = IDataUtil.getIData(ac, "server");
                    ac.destroy();

                    if (registry == null) {
                        IDataCursor sc = server.getCursor();
                        String host = IDataUtil.getString(sc, "host");
                        // hosts are case-insensitive, according to RFC 2396
                        if (host != null) host = host.toLowerCase();

                        String portString = IDataUtil.getString(sc, "port");
                        int port = -1;
                        if (portString != null) port = Integer.parseInt(portString);

                        String userinfo = IDataUtil.getString(sc, "user");
                        if (userinfo != null && !(userinfo.equals(""))) {
                            String password = IDataUtil.getString(sc, "password");
                            if (password != null && !(password.equals(""))) userinfo = userinfo + ":" + password;
                        } else {
                            userinfo = null; // ignore empty strings
                        }

                        sc.destroy();
                        uri = new URI(scheme, userinfo, host, port, path, null, null);
                    } else {
                        uri = new URI(scheme, registry, path, null, null);
                    }
                }
            } else {
                uri = new URI(scheme, body, null);
            }
            output = uri.normalize().toASCIIString();
            if (query != null) output = output + "?" + query;
            if (fragment != null && !(fragment.equals(""))) output = output + "#" + encode(fragment);

            // support Windows UNC file URIs, and work-around java bug with
            // file URIs where scheme is followed by ':/' rather than '://'
            if (output.startsWith("file:") && uri.getHost() == null) {
                output = "file://" + output.substring(5, output.length());
            }
        } finally {
            cursor.destroy();
        }

        return output;
    }

    /**
     * Normalizes a URI string, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URI.html#normalize().
     *
     * @param input The URI string to be normalized.
     * @return The normalized URI string.
     * @throws URISyntaxException If the given string is an invalid URI.
     */
    public static String normalize(String input) throws URISyntaxException {
        return emit(parse(input));
    }

    /**
     * URI encodes a string, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLEncoder.html.
     *
     * @param input The string to be URI encoded.
     * @return The string after being URI encoded.
     */
    public static String encode(String input) {
        return encode(input, DEFAULT_CHARSET);
    }

    /**
     * URI encodes a string, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLEncoder.html.
     *
     * @param input       The string to be URI encoded.
     * @param charsetName The character set to use when URI encoding the string.
     * @return The string after being URI encoded.
     */
    public static String encode(String input, String charsetName) {
        return encode(input, CharsetHelper.normalize(charsetName, DEFAULT_CHARSET));
    }

    /**
     * URI encodes a string, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLEncoder.html.
     *
     * @param input   The string to be URI encoded.
     * @param charset The character set to use when URI encoding the string.
     * @return The string after being URI encoded.
     */
    public static String encode(String input, Charset charset) {
        if (input == null) return null;

        String output = null;

        try {
            output = java.net.URLEncoder.encode(input, CharsetHelper.normalize(charset, DEFAULT_CHARSET).name()).replace("+", "%20");
        } catch (UnsupportedEncodingException ex) {
            throw new IllegalArgumentException(ex); // this should never happen
        }

        return output;
    }

    /**
     * URI encodes a string list, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLEncoder.html.
     *
     * @param input A list of strings to be URI encoded.
     * @return The new copy of the list of strings after being URI encoded.
     */
    public static String[] encode(String[] input) {
        return encode(input, DEFAULT_CHARSET);
    }

    /**
     * URI encodes a string list, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLEncoder.html.
     *
     * @param input       A list of strings to be URI encoded.
     * @param charsetName The character set to use when URI encoding the strings.
     * @return The new copy of the list of strings after being URI encoded.
     */
    public static String[] encode(String[] input, String charsetName) {
        return encode(input, CharsetHelper.normalize(charsetName, DEFAULT_CHARSET));
    }

    /**
     * URI encodes a string list, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLEncoder.html.
     *
     * @param input   A list of strings to be URI encoded.
     * @param charset The character set to use when URI encoding the strings.
     * @return The new copy of the list of strings after being URI encoded.
     */
    public static String[] encode(String[] input, Charset charset) {
        if (input == null) return null;

        String[] output = new String[input.length];

        for (int i = 0; i < input.length; i++) {
            output[i] = encode(input[i], charset);
        }

        return output;
    }

    /**
     * URI decodes a string, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLDecoder.html.
     *
     * @param input The string to be URI decoded.
     * @return The string after being URI decoded.
     */
    public static String decode(String input) {
        return decode(input, DEFAULT_CHARSET);
    }

    /**
     * URI decodes a string, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLDecoder.html.
     *
     * @param input       The string to be URI decoded.
     * @param charsetName The character set to use when URI decoding the string.
     * @return The string after it has been URI decoded.
     */
    public static String decode(String input, String charsetName) {
        return decode(input, CharsetHelper.normalize(charsetName, DEFAULT_CHARSET));
    }

    /**
     * URI decodes a string, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLDecoder.html.
     *
     * @param input   The string to be URI decoded.
     * @param charset The character set to use when URI decoding the string.
     * @return The string after it has been URI decoded.
     */
    public static String decode(String input, Charset charset) {
        if (input == null) return null;

        String output = null;

        try {
            output = java.net.URLDecoder.decode(input, CharsetHelper.normalize(charset, DEFAULT_CHARSET).name());
        } catch (UnsupportedEncodingException ex) {
            throw new IllegalArgumentException(ex); // this should never happen
        }

        return output;
    }

    /**
     * URI decodes a string list, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLDecoder.html.
     *
     * @param input The list of strings to be URI decoded.
     * @return A new copy of the list of strings after being URI decoded.
     */
    public static String[] decode(String[] input) {
        return decode(input, DEFAULT_CHARSET);
    }

    /**
     * URI decodes a string list, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLDecoder.html.
     *
     * @param input       The list of strings to be URI decoded.
     * @param charsetName The character set to use when URI decoding the strings.
     * @return A new copy of the list of strings after being URI decoded.
     */
    public static String[] decode(String[] input, String charsetName) {
        return decode(input, CharsetHelper.normalize(charsetName, DEFAULT_CHARSET));
    }


    /**
     * URI decodes a string list, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLDecoder.html.
     *
     * @param input   The list of strings to be URI decoded.
     * @param charset The character set to use when URI decoding the strings.
     * @return A new copy of the list of strings after being URI decoded.
     */
    public static String[] decode(String[] input, Charset charset) {
        if (input == null) return null;

        String[] output = new String[input.length];

        for (int i = 0; i < input.length; i++) {
            output[i] = decode(input[i], charset);
        }

        return output;
    }

    /**
     * Validates if the given string can be parsed as a URI.
     *
     * @param uri   The string to be validated as a URI.
     * @param raise If true, throws an exception if the string is not a valid URI.
     * @return      True if the given string is a valid URI, otherwise false.
     * @throws URISyntaxException If raise is true and the given string is not a valid URI.
     */
    public static boolean validate(String uri, boolean raise) throws URISyntaxException {
        boolean valid = false;
        try {
            valid = parse(uri) != null;
        } catch(URISyntaxException ex) {
            if (raise) throw ex;
        }
        return valid;
    }
}<commit_msg>Replace URIHelper.validate with URIHelper.substitute method, for performing variable substitution against the parsed components of a URI string<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package permafrost.tundra.net.uri;

import com.wm.data.IData;
import com.wm.data.IDataCursor;
import com.wm.data.IDataFactory;
import com.wm.data.IDataUtil;
import permafrost.tundra.data.IDataHelper;
import permafrost.tundra.lang.ArrayHelper;
import permafrost.tundra.lang.CharsetHelper;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;

/**
 * A collection of convenience methods for working with URIs.
 */
public class URIHelper {
    /**
     * The default character set used for URI strings.
     */
    public static final Charset DEFAULT_CHARSET = Charset.forName("US-ASCII");
    /**
     * The default character set name used for URI strings.
     */
    public static final String DEFAULT_CHARSET_NAME = DEFAULT_CHARSET.name();

    /**
     * Disallow instantiation of this class.
     */
    private URIHelper() {}

    /**
     * Parses a URI string into an IData representation.
     *
     * @param input The URI string to be parsed.
     * @return An IData representation of the give URI.
     * @throws URISyntaxException If the given string is an invalid URI.
     */
    public static IData parse(String input) throws URISyntaxException {
        if (input == null) return null;

        // treat Windows UNC file URIs as server-based rather than path-based
        if (input.toLowerCase().startsWith("file:////")) {
            input = "file://" + input.substring(9, input.length());
        }

        IData output = IDataFactory.create();
        IDataCursor cursor = output.getCursor();

        try {
            URI uri = new URI(input);
            uri.normalize();

            String scheme = uri.getScheme();
            // schemes are case-insensitive, according to RFC 2396
            if (scheme != null) scheme = scheme.toLowerCase();
            if (scheme != null) IDataUtil.put(cursor, "scheme", scheme);

            IData query = null;
            String ssp = uri.getRawSchemeSpecificPart();

            if (uri.isOpaque()) {
                if (ssp.contains("?")) {
                    query = URIQueryHelper.parse(ssp.substring(ssp.indexOf("?") + 1, ssp.length()), true);
                    ssp = ssp.substring(0, ssp.indexOf("?"));
                }
                if (ssp != null) IDataUtil.put(cursor, "body", decode(ssp));
            } else {
                String authority = uri.getAuthority();
                if (authority != null) {
                    IData server = IDataFactory.create();
                    IDataCursor sc = server.getCursor();

                    // parse user-info component according to the format user:[password]
                    String user = uri.getUserInfo();
                    String password = <PASSWORD>;
                    String userInfoSeparator = ":";
                    if (user != null && user.contains(userInfoSeparator)) {
                        password = user.substring(user.indexOf(userInfoSeparator) + 1, user.length());
                        user = user.substring(0, user.indexOf(userInfoSeparator));
                    }
                    if (user != null) IDataUtil.put(sc, "user", user);
                    if (password != null) IDataUtil.put(sc, "password", password);

                    // hosts are case-insensitive, according to RFC 2396
                    String host = uri.getHost();
                    if (host != null) IDataUtil.put(sc, "host", host.toLowerCase());

                    // if port is -1, then it wasn't specified in the URI
                    int port = uri.getPort();
                    if (port >= 0) IDataUtil.put(sc, "port", "" + uri.getPort());

                    sc.destroy();

                    // if host is null, then this URI must be registry-based
                    IData authorityDocument = IDataFactory.create();
                    IDataCursor ac = authorityDocument.getCursor();
                    if (host == null) {
                        IDataUtil.put(ac, "registry", authority);
                    } else {
                        IDataUtil.put(ac, "server", server);
                    }
                    ac.destroy();

                    IDataUtil.put(cursor, "authority", authorityDocument);
                }

                String path = uri.getPath();
                String[] paths = URIPathHelper.parse(path);

                String file = null;
                if (!path.endsWith("/")) {
                    file = ArrayHelper.get(paths, -1);
                    paths = ArrayHelper.drop(paths, -1);
                }

                if (paths != null && paths.length > 0) IDataUtil.put(cursor, "path", paths);
                if (file != null && !file.equals("")) IDataUtil.put(cursor, "file", file);

                query = URIQueryHelper.parse(uri.getRawQuery(), true);
            }

            if (query != null) IDataUtil.put(cursor, "query", query);

            String fragment = uri.getFragment();
            if (fragment != null) IDataUtil.put(cursor, "fragment", fragment);

            IDataUtil.put(cursor, "absolute?", "" + uri.isAbsolute());
            IDataUtil.put(cursor, "opaque?", "" + uri.isOpaque());
        } finally {
            cursor.destroy();
        }

        return output;
    }

    /**
     * Emits a URI given as an IData document as a string.
     *
     * @param input An IData containing a specific URI structure to be serialized.
     * @return The URI string representing the given IData.
     * @throws URISyntaxException If the given string is an invalid URI.
     */
    public static String emit(IData input) throws URISyntaxException {
        if (input == null) return null;

        String output = null;
        IDataCursor cursor = input.getCursor();

        try {
            URI uri = null;
            String type = IDataUtil.getString(cursor, "type");

            String scheme = IDataUtil.getString(cursor, "scheme");
            // schemes are case-insensitive, according to RFC 2396
            if (scheme != null) scheme = scheme.toLowerCase();

            String fragment = IDataUtil.getString(cursor, "fragment");
            IData queryDocument = IDataUtil.getIData(cursor, "query");
            String query = URIQueryHelper.emit(queryDocument, true);

            String body = IDataUtil.getString(cursor, "body");
            IData authority = IDataUtil.getIData(cursor, "authority");
            String[] paths = IDataUtil.getStringArray(cursor, "path");
            String file = IDataUtil.getString(cursor, "file");

            if (body == null && !(authority == null && paths == null && file == null)) {
                // create an hierarchical URI
                String path = "";
                if (paths != null) {
                    path = "/" + ArrayHelper.join(paths, "/");
                }
                path = path + "/";
                if (file != null) {
                    path = path + file;
                }

                if (authority == null) {
                    uri = new URI(scheme, null, path, null, null);
                } else {
                    IDataCursor ac = authority.getCursor();
                    String registry = IDataUtil.getString(ac, "registry");
                    IData server = IDataUtil.getIData(ac, "server");
                    ac.destroy();

                    if (registry == null) {
                        IDataCursor sc = server.getCursor();
                        String host = IDataUtil.getString(sc, "host");
                        // hosts are case-insensitive, according to RFC 2396
                        if (host != null) host = host.toLowerCase();

                        String portString = IDataUtil.getString(sc, "port");
                        int port = -1;
                        if (portString != null) port = Integer.parseInt(portString);

                        String userinfo = IDataUtil.getString(sc, "user");
                        if (userinfo != null && !(userinfo.equals(""))) {
                            String password = IDataUtil.getString(sc, "password");
                            if (password != null && !(password.equals(""))) userinfo = userinfo + ":" + password;
                        } else {
                            userinfo = null; // ignore empty strings
                        }

                        sc.destroy();
                        uri = new URI(scheme, userinfo, host, port, path, null, null);
                    } else {
                        uri = new URI(scheme, registry, path, null, null);
                    }
                }
            } else {
                uri = new URI(scheme, body, null);
            }
            output = uri.normalize().toASCIIString();
            if (query != null) output = output + "?" + query;
            if (fragment != null && !(fragment.equals(""))) output = output + "#" + encode(fragment);

            // support Windows UNC file URIs, and work-around java bug with
            // file URIs where scheme is followed by ':/' rather than '://'
            if (output.startsWith("file:") && uri.getHost() == null) {
                output = "file://" + output.substring(5, output.length());
            }
        } finally {
            cursor.destroy();
        }

        return output;
    }

    /**
     * Normalizes a URI string, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URI.html#normalize().
     *
     * @param input The URI string to be normalized.
     * @return The normalized URI string.
     * @throws URISyntaxException If the given string is an invalid URI.
     */
    public static String normalize(String input) throws URISyntaxException {
        return emit(parse(input));
    }

    /**
     * URI encodes a string, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLEncoder.html.
     *
     * @param input The string to be URI encoded.
     * @return The string after being URI encoded.
     */
    public static String encode(String input) {
        return encode(input, DEFAULT_CHARSET);
    }

    /**
     * URI encodes a string, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLEncoder.html.
     *
     * @param input       The string to be URI encoded.
     * @param charsetName The character set to use when URI encoding the string.
     * @return The string after being URI encoded.
     */
    public static String encode(String input, String charsetName) {
        return encode(input, CharsetHelper.normalize(charsetName, DEFAULT_CHARSET));
    }

    /**
     * URI encodes a string, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLEncoder.html.
     *
     * @param input   The string to be URI encoded.
     * @param charset The character set to use when URI encoding the string.
     * @return The string after being URI encoded.
     */
    public static String encode(String input, Charset charset) {
        if (input == null) return null;

        String output = null;

        try {
            output = java.net.URLEncoder.encode(input, CharsetHelper.normalize(charset, DEFAULT_CHARSET).name()).replace("+", "%20");
        } catch (UnsupportedEncodingException ex) {
            throw new IllegalArgumentException(ex); // this should never happen
        }

        return output;
    }

    /**
     * URI encodes a string list, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLEncoder.html.
     *
     * @param input A list of strings to be URI encoded.
     * @return The new copy of the list of strings after being URI encoded.
     */
    public static String[] encode(String[] input) {
        return encode(input, DEFAULT_CHARSET);
    }

    /**
     * URI encodes a string list, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLEncoder.html.
     *
     * @param input       A list of strings to be URI encoded.
     * @param charsetName The character set to use when URI encoding the strings.
     * @return The new copy of the list of strings after being URI encoded.
     */
    public static String[] encode(String[] input, String charsetName) {
        return encode(input, CharsetHelper.normalize(charsetName, DEFAULT_CHARSET));
    }

    /**
     * URI encodes a string list, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLEncoder.html.
     *
     * @param input   A list of strings to be URI encoded.
     * @param charset The character set to use when URI encoding the strings.
     * @return The new copy of the list of strings after being URI encoded.
     */
    public static String[] encode(String[] input, Charset charset) {
        if (input == null) return null;

        String[] output = new String[input.length];

        for (int i = 0; i < input.length; i++) {
            output[i] = encode(input[i], charset);
        }

        return output;
    }

    /**
     * URI decodes a string, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLDecoder.html.
     *
     * @param input The string to be URI decoded.
     * @return The string after being URI decoded.
     */
    public static String decode(String input) {
        return decode(input, DEFAULT_CHARSET);
    }

    /**
     * URI decodes a string, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLDecoder.html.
     *
     * @param input       The string to be URI decoded.
     * @param charsetName The character set to use when URI decoding the string.
     * @return The string after it has been URI decoded.
     */
    public static String decode(String input, String charsetName) {
        return decode(input, CharsetHelper.normalize(charsetName, DEFAULT_CHARSET));
    }

    /**
     * URI decodes a string, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLDecoder.html.
     *
     * @param input   The string to be URI decoded.
     * @param charset The character set to use when URI decoding the string.
     * @return The string after it has been URI decoded.
     */
    public static String decode(String input, Charset charset) {
        if (input == null) return null;

        String output = null;

        try {
            output = java.net.URLDecoder.decode(input, CharsetHelper.normalize(charset, DEFAULT_CHARSET).name());
        } catch (UnsupportedEncodingException ex) {
            throw new IllegalArgumentException(ex); // this should never happen
        }

        return output;
    }

    /**
     * URI decodes a string list, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLDecoder.html.
     *
     * @param input The list of strings to be URI decoded.
     * @return A new copy of the list of strings after being URI decoded.
     */
    public static String[] decode(String[] input) {
        return decode(input, DEFAULT_CHARSET);
    }

    /**
     * URI decodes a string list, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLDecoder.html.
     *
     * @param input       The list of strings to be URI decoded.
     * @param charsetName The character set to use when URI decoding the strings.
     * @return A new copy of the list of strings after being URI decoded.
     */
    public static String[] decode(String[] input, String charsetName) {
        return decode(input, CharsetHelper.normalize(charsetName, DEFAULT_CHARSET));
    }


    /**
     * URI decodes a string list, refer: http://docs.oracle.com/javase/6/docs/api/java/net/URLDecoder.html.
     *
     * @param input   The list of strings to be URI decoded.
     * @param charset The character set to use when URI decoding the strings.
     * @return A new copy of the list of strings after being URI decoded.
     */
    public static String[] decode(String[] input, Charset charset) {
        if (input == null) return null;

        String[] output = new String[input.length];

        for (int i = 0; i < input.length; i++) {
            output[i] = decode(input[i], charset);
        }

        return output;
    }

    /**
     * Performs variable substitution on the components of the given URI string.
     *
     * @param uri   The URI string to perform variable substitution on.
     * @param scope The scope variables are resolved against.
     * @return      The resulting URI string after variable substitution.
     * @throws URISyntaxException If the given string is not a valid URI.
     */
    public static String substitute(String uri, IData scope) throws URISyntaxException {
        return emit(IDataHelper.substitute(parse(uri), scope, true));
    }
}
{'fraction_non_alphanumeric': 0.10616756194279607, 'fraction_numerical': 0.0027561380318924544, 'mean_word_length': 1.9740715958514554, 'pattern_counts': {'":': 4, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4328951', 'n_tokens_mistral': 9683, 'n_tokens_neox': 9182, 'n_words': 3108}