LinkUtilities.java

// © 2026 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
package com.ibm.icu.util;

import com.ibm.icu.impl.links.LinkHandlingUtilities;
import com.ibm.icu.impl.links.LinkHandlingUtilities.UrlInternals;
import com.ibm.icu.impl.links.LinkHandlingUtilities.UrlInternals.EndStatus;

/**
 * Utility class for assisting with detecting links (URLs or emails) in text, and formatting them
 * for display, implementing the algorithms in https://www.unicode.org/reports/tr58/ to handle
 * Unicode characters properly. It supplies lower level APIs for use in augmenting existing scanners
 * and formatters.
 *
 * @draft ICU 79
 */
public class LinkUtilities {

    // private constructor to prevent default construction
    private LinkUtilities() {}

    /**
     * Lower level utility for finding the end of a PathQueryFragment (PQF) in text. It assumes that
     * the start position is immediately after an identified domain name. The purpose of this
     * routine is for fitting into algorithms that are already in use, just taking over for the PQF
     * scanning. For more information, see https://www.unicode.org/reports/tr58/.
     *
     * @param source the text to be scanned
     * @param start the position in the text to be scanned from. It should be immediately after a
     *     domain name.
     * @return the exclusive-end position of the PQF, or the start value if there is none.
     * @draft ICU 79
     */
    public static int scanPathQueryFragment(CharSequence source, int start, int limit) {
        return LinkHandlingUtilities.parsePathQueryFragment(source.toString(), start);
    }

    /**
     * Lower level utility for finding the start of an email address in text. It assumes that the
     * limit position is at an '@' + identified domain name, and will scan backwards from there. The
     * purpose of this routine is for fitting into algorithms that are already in use, just handling
     * the email {@code local-part}. It does not scan back through "mailto:".
     *
     * @param source the text to be scanned
     * @param start the position that is the earliest that should be considered in a backwards scan
     * @param limit the position to start scanning backwards from — should be the position of the
     *     '@' (which is just before the domain_name)
     * @return the start of the email local part, or limit if no email local part is found
     * @draft ICU 79
     */
    public static int scanBackEmailLocalPart(CharSequence source, int start, int limit) {
        return LinkHandlingUtilities.scanEmailBackwards(source, start, limit);
    }

    /**
     * Enum for determining whether any percent-escaping is minimal or maximal, for use with {@link
     * #escapePathQueryFragment()}.
     *
     * @draft ICU 79
     */
    public enum Extent {
        /**
         * Minimal percent-escaping only percent-escapes non-ASCII where necessary.
         *
         * @draft ICU 79
         */
        MINIMAL,
        /**
         * Maximal percent-escaping percent-escapes all non-ASCII.
         *
         * @draft ICU 79
         */
        MAXIMAL
    }

    /**
     * Escapes a URL according to the Extent parameter
     *
     * @param source In the source, it is assumed that ASCII syntax characters requiring escaping
     *     have already been escaped. For example, a literal / in a path segment would already be
     *     percent-escaped. For more information, see https://www.unicode.org/reports/tr58/.
     * @param extent either MINIMAL or MAXIMAL
     * @return an escaped string according to the extent parameter.
     * @draft ICU 79
     */
    public static String escapePathQueryFragment(String source, Extent extent) {
        UrlInternals ui = UrlInternals.from(source);
        switch (extent) {
            case MINIMAL:
                return ui.minimalEscape(EndStatus.FINAL);
            case MAXIMAL:
                return ui.fullEscape();
            default:
                throw new InternalError();
        }
    }
}