/*
* Copyright 2014 Grow Bit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.turbogwt.net.client;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.http.client.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.turbogwt.core.collections.client.LightMap;
import org.turbogwt.net.shared.MultivaluedParamComposition;
import org.turbogwt.net.shared.Uri;
import org.turbogwt.net.shared.UriBuilder;
import org.turbogwt.net.shared.UriBuilderException;
/**
* Default implementation of {@link org.turbogwt.net.shared.UriBuilder}.
*
* @author Danilo Reinert
*/
public class UriBuilderImpl extends UriBuilder {
private MultivaluedParamComposition strategy = MultivaluedParamComposition.REPEATED_PARAM;
private String scheme;
private String user;
private String password;
private String host;
private Integer port;
private JsArrayString segments;
private String fragment;
private Map<String, Object[]> queryParams;
private Map<String, Map<String, Object[]>> matrixParams;
@Override
public UriBuilder multivaluedParamComposition(MultivaluedParamComposition strategy)
throws IllegalArgumentException {
assertNotNull(strategy, "MultivaluedParamComposition strategy cannot be null.");
this.strategy = strategy;
return this;
}
@Override
public UriBuilder scheme(String scheme) throws IllegalArgumentException {
// TODO: check scheme validity
this.scheme = scheme;
return this;
}
@Override
public UriBuilder user(String user) {
if (user == null) {
this.user = null;
this.password = null;
} else {
this.user = user;
}
return this;
}
@Override
public UriBuilder password(String password) {
this.password = password;
return this;
}
@Override
public UriBuilder host(String host) throws IllegalArgumentException {
// TODO: check host validity
this.host = host;
return this;
}
@Override
public UriBuilder port(int port) throws IllegalArgumentException {
if (port > -1) {
this.port = port;
} else {
this.port = null;
}
return this;
}
@Override
public UriBuilder path(String path) {
assertNotNull(path, "Path cannot be null.");
if (!path.isEmpty()) {
String[] splittedSegments = path.split("/");
ensureSegments();
for (String segment : splittedSegments) {
if (!segment.isEmpty())
this.segments.push(segment);
}
}
return this;
}
@Override
public UriBuilder segment(Object... segments) throws IllegalArgumentException {
assertNotNull(segments, "Segments cannot be null.");
ensureSegments();
for (Object o : segments) {
String segment = o.toString();
assertNotNullOrEmpty(segment, "Segment cannot be null or empty.", false);
this.segments.push(segment);
}
return this;
}
@Override
public UriBuilder matrixParam(String name, Object... values) throws IllegalArgumentException {
assertNotNullOrEmpty(name, "Parameter name cannot be null or empty.", false);
assertNotNull(values, "Parameter values cannot be null.");
if (matrixParams == null) {
matrixParams = GWT.create(LightMap.class);
}
// TODO: validate this assertion
assertNotNull(segments, "There is no segment added to the URI. " +
"There must be at least one segment added in order to bind matrix parameters");
String segment = segments.get(segments.length() - 1);
Map<String, Object[]> segmentParams = matrixParams.get(segment);
if (segmentParams == null) {
segmentParams = GWT.create(LightMap.class);
matrixParams.put(segment, segmentParams);
}
// TODO: instead of setting the array, incrementally add to an existing one?
segmentParams.put(name, values);
return this;
}
@Override
public UriBuilder queryParam(String name, Object... values) throws IllegalArgumentException {
assertNotNull(name, "Parameter name cannot be null.");
assertNotNull(values, "Parameter values cannot be null.");
if (queryParams == null)
queryParams = GWT.create(LightMap.class);
queryParams.put(name, values);
return this;
}
@Override
public UriBuilder fragment(String fragment) {
this.fragment = fragment;
return this;
}
@Override
public Uri build(Object... values) {
final String encScheme = encodePart(scheme);
final String encUser = encodePart(user);
final String encPassword = encodePart(password);
final String encHost = encodePart(host);
List<String> templateParams = new ArrayList<>();
StringBuilder pathBuilder = new StringBuilder();
if (segments != null) {
for (int i = 0; i < segments.length(); i++) {
final String segment = segments.get(i);
final String parsed = parsePart(values, templateParams, segment);
pathBuilder.append(URL.encodePathSegment(parsed));
// Check if there are matrix params for this segment
if (matrixParams != null) {
Map<String, Object[]> segmentParams = matrixParams.get(segment);
if (segmentParams != null) {
pathBuilder.append(";");
Set<String> params = segmentParams.keySet();
for (String param : params) {
pathBuilder.append(strategy.asUriPart(";", param, segmentParams.get(param))).append(';');
}
pathBuilder.deleteCharAt(pathBuilder.length() - 1);
}
}
pathBuilder.append('/');
}
pathBuilder.deleteCharAt(pathBuilder.length() - 1);
}
final String encPath = pathBuilder.toString();
StringBuilder queryBuilder = null;
if (queryParams != null) {
queryBuilder = new StringBuilder();
Set<String> params = queryParams.keySet();
for (String param : params) {
queryBuilder.append(strategy.asUriPart("&", param, queryParams.get(param))).append('&');
}
queryBuilder.deleteCharAt(queryBuilder.length() - 1);
}
final String encQuery = queryBuilder != null ? queryBuilder.toString() : null;
final String encFragment = fragment != null ? encodePart(parsePart(values, templateParams, fragment)) : null;
return new Uri(encScheme, encUser, encPassword, encHost, port, encPath, encQuery, encFragment);
}
private String encodePart(String segment) {
return segment != null ? URL.encodePathSegment(segment) : null;
}
private String parsePart(Object[] values, List<String> templateParams, String segment) {
int cursor = segment.indexOf("{");
while (cursor > -1) {
int closingBracket = segment.indexOf("}", cursor);
if (closingBracket > -1) {
final String param = segment.substring(cursor + 1, closingBracket);
int i = templateParams.indexOf(param);
if (i == -1) {
// Check if has more template values
if (values.length < templateParams.size() + 1)
throw new UriBuilderException("The supplied values are not enough to replace the existing " +
"template params");
// Add template param
i = templateParams.size();
templateParams.add(param);
}
final String value = values[i].toString();
segment = segment.substring(0, cursor) + value + segment.substring(closingBracket + 1);
cursor = segment.indexOf("{", closingBracket + 1);
} else {
cursor = -1;
}
}
return segment;
}
/**
* Assert that the value is not null.
*
* @param value the value
* @param message the message to include with any exceptions
*
* @throws IllegalArgumentException if value is null
*/
private void assertNotNull(Object value, String message) throws IllegalArgumentException {
if (value == null) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that the value is not null or empty.
*
* @param value the value
* @param message the message to include with any exceptions
* @param isState if true, throw a state exception instead
*
* @throws IllegalArgumentException if value is null
* @throws IllegalStateException if value is null and isState is true
*/
private void assertNotNullOrEmpty(String value, String message, boolean isState) throws IllegalArgumentException {
if (value == null || value.length() == 0) {
if (isState) {
throw new IllegalStateException(message);
} else {
throw new IllegalArgumentException(message);
}
}
}
private void ensureSegments() {
if (this.segments == null)
this.segments = (JsArrayString) JsArrayString.createArray();
}
}