001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2016 ForgeRock AS. 015 */ 016package org.forgerock.api.util; 017 018import static org.forgerock.api.util.ValidationUtil.isEmpty; 019 020import java.util.ArrayList; 021import java.util.List; 022import java.util.regex.Matcher; 023import java.util.regex.Pattern; 024 025import org.forgerock.api.enums.ParameterSource; 026import org.forgerock.api.models.Parameter; 027 028/** Utilities for working with API Description paths and path-parameters. */ 029public final class PathUtil { 030 031 /** Pattern for replacing multiple forward-slashes with a single forward-slash. */ 032 private static final Pattern SQUASH_FORWARD_SLASHES_PATTERN = Pattern.compile("[/]{2,}"); 033 /** Pattern for removing multiple trailing-slashes. */ 034 private static final Pattern TRAILING_SLASHES_PATTERN = Pattern.compile("[/]+$"); 035 /** Pattern for finding curly-brace-delimited path-variables in a URL-path. */ 036 private static final Pattern PATH_VARIABLE_PATTERN = Pattern.compile("\\{([^{}]+)\\}"); 037 038 private PathUtil() { 039 // empty 040 } 041 042 /** 043 * Builds a forward-slash-delimited path, with duplicate forward-slashes removed, and trailing slashes removed. 044 * 045 * @param segment First path segment 046 * @param moreSegments Additional path segments or {@code null} 047 * @return Path 048 */ 049 public static String buildPath(final String segment, final String... moreSegments) { 050 if (isEmpty(segment)) { 051 throw new IllegalArgumentException("segment argument required"); 052 } 053 final StringBuilder path = new StringBuilder().append('/').append(segment); 054 if (moreSegments != null) { 055 for (final String s : moreSegments) { 056 path.append('/').append(s); 057 } 058 } 059 060 // squash forward-slashes 061 final Matcher m = SQUASH_FORWARD_SLASHES_PATTERN.matcher(path); 062 final String normalized = m.find() ? m.replaceAll("/") : path.toString(); 063 064 // remove trailing-slashes 065 return TRAILING_SLASHES_PATTERN.matcher(normalized).replaceAll(""); 066 } 067 068 /** 069 * Searches for curly-braces in the given {@code pathSegment}, and creates a path-parameter for each that are found. 070 * 071 * @param pathSegment Path-segment 072 * @return Path-parameters or {@code null} 073 */ 074 public static Parameter[] buildPathParameters(final String pathSegment) { 075 if (!isEmpty(pathSegment)) { 076 final Matcher m = PATH_VARIABLE_PATTERN.matcher(pathSegment); 077 if (m.find()) { 078 final List<Parameter> parameters = new ArrayList<>(); 079 int start = 0; 080 while (m.find(start)) { 081 parameters.add(Parameter.parameter() 082 .name(m.group(1)) 083 .type("string") 084 .source(ParameterSource.PATH) 085 .required(true) 086 .build()); 087 start = m.end(); 088 } 089 return parameters.toArray(new Parameter[parameters.size()]); 090 } 091 } 092 return null; 093 } 094 095 /** 096 * Merges {@link Parameter} values into the given {@code parameterList}, where conflicting 097 * {@link Parameter#getName() parameter-names} will be replaced, and new parameters will otherwise be added. 098 * 099 * @param parameterList Current list of parameters 100 * @param parameters Additional parameters to merge or {@code null} 101 * @return {@code parameterList} field 102 */ 103 public static List<Parameter> mergeParameters(final List<Parameter> parameterList, 104 final Parameter... parameters) { 105 if (parameters != null) { 106 for (final Parameter parameter : parameters) { 107 // replace parameter if name already exists, otherwise add parameter to end of list 108 int replaceIndex = -1; 109 for (int i = 0; i < parameterList.size(); ++i) { 110 if (parameterList.get(i).getName().equals(parameter.getName())) { 111 replaceIndex = i; 112 break; 113 } 114 } 115 if (replaceIndex != -1) { 116 parameterList.set(replaceIndex, parameter); 117 } else { 118 parameterList.add(parameter); 119 } 120 } 121 } 122 return parameterList; 123 } 124}