PathUtil.java

/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package org.forgerock.api.util;

import static org.forgerock.api.util.ValidationUtil.isEmpty;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.forgerock.api.enums.ParameterSource;
import org.forgerock.api.models.Parameter;

/** Utilities for working with API Description paths and path-parameters. */
public final class PathUtil {

    /** Pattern for replacing multiple forward-slashes with a single forward-slash. */
    private static final Pattern SQUASH_FORWARD_SLASHES_PATTERN = Pattern.compile("[/]{2,}");
    /** Pattern for removing multiple trailing-slashes. */
    private static final Pattern TRAILING_SLASHES_PATTERN = Pattern.compile("[/]+$");
    /** Pattern for finding curly-brace-delimited path-variables in a URL-path. */
    private static final Pattern PATH_VARIABLE_PATTERN = Pattern.compile("\\{([^{}]+)\\}");

    private PathUtil() {
        // empty
    }

    /**
     * Builds a forward-slash-delimited path, with duplicate forward-slashes removed, and trailing slashes removed.
     *
     * @param segment First path segment
     * @param moreSegments Additional path segments or {@code null}
     * @return Path
     */
    public static String buildPath(final String segment, final String... moreSegments) {
        if (isEmpty(segment)) {
            throw new IllegalArgumentException("segment argument required");
        }
        final StringBuilder path = new StringBuilder().append('/').append(segment);
        if (moreSegments != null) {
            for (final String s : moreSegments) {
                path.append('/').append(s);
            }
        }

        // squash forward-slashes
        final Matcher m = SQUASH_FORWARD_SLASHES_PATTERN.matcher(path);
        final String normalized = m.find() ? m.replaceAll("/") : path.toString();

        // remove trailing-slashes
        return TRAILING_SLASHES_PATTERN.matcher(normalized).replaceAll("");
    }

    /**
     * Searches for curly-braces in the given {@code pathSegment}, and creates a path-parameter for each that are found.
     *
     * @param pathSegment Path-segment
     * @return Path-parameters or {@code null}
     */
    public static Parameter[] buildPathParameters(final String pathSegment) {
        if (!isEmpty(pathSegment)) {
            final Matcher m = PATH_VARIABLE_PATTERN.matcher(pathSegment);
            if (m.find()) {
                final List<Parameter> parameters = new ArrayList<>();
                int start = 0;
                while (m.find(start)) {
                    parameters.add(Parameter.parameter()
                            .name(m.group(1))
                            .type("string")
                            .source(ParameterSource.PATH)
                            .required(true)
                            .build());
                    start = m.end();
                }
                return parameters.toArray(new Parameter[parameters.size()]);
            }
        }
        return null;
    }

    /**
     * Merges {@link Parameter} values into the given {@code parameterList}, where conflicting
     * {@link Parameter#getName() parameter-names} will be replaced, and new parameters will otherwise be added.
     *
     * @param parameterList Current list of parameters
     * @param parameters Additional parameters to merge or {@code null}
     * @return {@code parameterList} field
     */
    public static List<Parameter> mergeParameters(final List<Parameter> parameterList,
            final Parameter... parameters) {
        if (parameters != null) {
            for (final Parameter parameter : parameters) {
                // replace parameter if name already exists, otherwise add parameter to end of list
                int replaceIndex = -1;
                for (int i = 0; i < parameterList.size(); ++i) {
                    if (parameterList.get(i).getName().equals(parameter.getName())) {
                        replaceIndex = i;
                        break;
                    }
                }
                if (replaceIndex != -1) {
                    parameterList.set(replaceIndex, parameter);
                } else {
                    parameterList.add(parameter);
                }
            }
        }
        return parameterList;
    }
}