package org.restler.util; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.escape.Escaper; import com.google.common.net.UrlEscapers; import org.restler.client.RestlerException; import java.net.URI; import java.net.URISyntaxException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.function.BiFunction; import java.util.function.BinaryOperator; import java.util.stream.Collectors; public class UriBuilder { private static final Escaper urlPathSegmentEscaper = UrlEscapers.urlPathSegmentEscaper(); private static final Escaper urlFormParameterEscaper = UrlEscapers.urlFormParameterEscaper(); private static final Map<String, Integer> defaultSchemePort; static { defaultSchemePort = new HashMap<String, Integer>() {{ put("http", 80); put("https", 443); }}; } private String host; private int port; private String path; private String scheme; private ImmutableMultimap<String, String> queryParams = ImmutableMultimap.of(); private Map<String, ?> pathVariables = ImmutableMap.of(); public UriBuilder(String baseUrl) { this(toUri(baseUrl)); } public UriBuilder(URI baseUrl) { scheme = baseUrl.getScheme(); host = baseUrl.getHost(); port = validPort(baseUrl); path = baseUrl.getPath(); } private static int validPort(URI baseUrl) { return baseUrl.getPort() != -1 ? baseUrl.getPort() : defaultSchemePort.computeIfAbsent(baseUrl.getScheme(), UriBuilder::throwError); } private static Integer throwError(String scheme) { throw new RestlerException("Unsupported scheme: " + scheme); } private static URI toUri(String baseUrl) { try { return new URI(baseUrl); } catch (URISyntaxException e) { throw new RestlerException(e); } } public UriBuilder host(String host) { this.host = host; return this; } public UriBuilder port(int port) { this.port = port; return this; } public UriBuilder replacePath(String path) { this.path = addSlash(path); return this; } public UriBuilder path(String path) { String pathWithoutSlash = path.startsWith("/") ? path.substring(1) : path; if (this.path == null) { this.path = addSlash(path); } else { if (this.path.endsWith("/")) { this.path += pathWithoutSlash; } else { this.path += addSlash(path); } } return this; } private String addSlash(String path) { return path.startsWith("/") ? path : "/" + path; } public UriBuilder scheme(String scheme) { this.scheme = scheme; return this; } public UriBuilder queryParams(ImmutableMultimap<String, String> queryParams) { this.queryParams = queryParams; return this; } public UriBuilder pathVariables(Map<String, ?> pathVariables) { this.pathVariables = Collections.unmodifiableMap(pathVariables); return this; } public URI build() { try { String path = substituteVariables(this.path); return new URI(scheme + "://" + host + ":" + port + path + queryParamsString()); } catch (URISyntaxException e) { throw new RestlerException(e); } } private String substituteVariables(String path) { BiFunction<String, Map.Entry<String, ?>, String> accumulator = (String acc, Map.Entry<String, ?> e) -> acc.replaceAll("\\{" + e.getKey() + "\\}", urlPathSegmentEscaper.escape(String.valueOf(e.getValue()))); BinaryOperator<String> combiner = (String __, String expandedPath) -> expandedPath; return pathVariables.entrySet().stream().reduce(path, accumulator, combiner); } private String queryParamsString() { String entryStream = queryParams.entries().stream(). map(entryPair -> urlFormParameterEscaper.escape(entryPair.getKey()) + "=" + urlFormParameterEscaper.escape(entryPair.getValue())). collect(Collectors.joining("&")); if (entryStream.length() > 0) { return "?" + entryStream; } else { return ""; } } }