/**
* Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright ownership. Apereo
* licenses this file to you 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 the
* following location:
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>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.apereo.portal.url;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang.Validate;
/**
* Builds a URL.
*
* <p>This class is not thread safe.
*
*/
public final class UrlStringBuilder extends BaseEncodedStringBuilder {
private static final long serialVersionUID = 1L;
private final String protocol;
private final String host;
private final Integer port;
private final String context;
private final List<String> path = new LinkedList<String>();
private final Map<String, List<String>> parameters = new LinkedHashMap<String, List<String>>();
/**
* Creates a URL with no host, protocol or port. The URL will start with a /
*
* @param encoding The encoding to use for parameters
*/
public UrlStringBuilder(String encoding, String context) {
super(encoding);
this.protocol = null;
this.host = null;
this.port = null;
this.context = context;
}
/**
* Creates a URL with no host, protocol or port. The URL will start with protocol://host
*
* @param encoding The encoding to use for parameters
*/
public UrlStringBuilder(String encoding, String protocol, String host) {
this(encoding, protocol, host, null);
}
/**
* Creates a URL with no host, protocol or port. The URL will start with protocol://host:port
*
* @param encoding The encoding to use for parameters
*/
public UrlStringBuilder(String encoding, String protocol, String host, Integer port) {
super(encoding);
Validate.notNull(protocol, "protocol can not be null");
Validate.notNull(host, " host can not be null");
this.protocol = protocol;
this.host = host;
this.port = port;
this.context = null;
}
/** Copy constructor */
public UrlStringBuilder(UrlStringBuilder urlBuilder) {
super(urlBuilder.getEncoding());
this.host = urlBuilder.host;
this.protocol = urlBuilder.protocol;
this.port = urlBuilder.port;
this.context = urlBuilder.context;
this.path.addAll(urlBuilder.path);
for (final Map.Entry<String, List<String>> paramEntry : urlBuilder.parameters.entrySet()) {
final String key = paramEntry.getKey();
List<String> value = paramEntry.getValue();
if (value != null) {
value = new ArrayList<String>(value);
}
this.parameters.put(key, value);
}
}
protected <T> List<T> copy(List<T> l) {
return l == null ? null : new ArrayList<T>(l);
}
protected <T> List<T> copy(T[] t) {
return t == null ? null : new ArrayList<T>(Arrays.asList(t));
}
/**
* Sets a URL parameter, replacing any existing parameter with the same name.
*
* @param name Parameter name, can not be null
* @param values Values for the parameter, null is valid
* @return this
*/
public UrlStringBuilder setParameter(String name, String... values) {
this.setParameter(name, values != null ? Arrays.asList(values) : null);
return this;
}
/** @see #setParameter(String, String...) */
public UrlStringBuilder setParameter(String name, List<String> values) {
Validate.notNull(name, "parameter name cannot be null");
this.parameters.put(name, this.copy(values));
return this;
}
/**
* Adds to a URL parameter, if a parameter with the same name already exists its values are
* added to
*
* @param name Parameter name, can not be null
* @param values Values for the parameter, null is valid
* @return this
*/
public UrlStringBuilder addParameter(String name, String... values) {
this.addParameter(name, values != null ? Arrays.asList(values) : null);
return this;
}
/** @see #addParameter(String, List) */
public UrlStringBuilder addParameter(String name, List<String> values) {
Validate.notNull(name, "parameter name cannot be null");
List<String> existingValues = this.parameters.get(name);
if (existingValues == null) {
existingValues = new ArrayList<String>(values != null ? values.size() : 0);
this.parameters.put(name, existingValues);
}
if (values != null) {
existingValues.addAll(values);
}
return this;
}
/**
* Calls {@link #setParameters(String, Map)} with "" for the namespace
*
* @see #setParameters(String, Map)
*/
public UrlStringBuilder setParameters(Map<String, List<String>> parameters) {
this.setParameters("", parameters);
return this;
}
/**
* Removes all existing parameters and sets the contents of the specified Map as the parameters.
*
* @param namespace String to prepend to each parameter name in the Map
* @param parameters Map of parameters to set
* @return this
*/
public UrlStringBuilder setParameters(String namespace, Map<String, List<String>> parameters) {
for (final String name : parameters.keySet()) {
Validate.notNull(name, "parameter map cannot contain any null keys");
}
this.parameters.clear();
this.addParameters(namespace, parameters);
return this;
}
/**
* Calls {@link #addParameters(String, Map)} with "" for the namespace
*
* @see #addParameters(String, Map)
*/
public UrlStringBuilder addParameters(Map<String, List<String>> parameters) {
this.addParameters("", parameters);
return this;
}
/**
* Adds the contents of the specified Map as the parameters, the values of the Map are
* List<String>
*
* @param namespace String to prepend to each parameter name in the Map
* @param parameters Map of parameters to set
* @return this
*/
public UrlStringBuilder addParameters(String namespace, Map<String, List<String>> parameters) {
for (final String name : parameters.keySet()) {
Validate.notNull(name, "parameter map cannot contain any null keys");
}
for (final Map.Entry<String, List<String>> newParamEntry : parameters.entrySet()) {
final String name = newParamEntry.getKey();
final List<String> values = this.copy(newParamEntry.getValue());
this.parameters.put(namespace + name, values);
}
return this;
}
/**
* Adds the contents of the specified Map as the parameters, the values of the Map are String[]
*
* @param namespace String to prepend to each parameter name in the Map
* @param parameters Map of parameters to set
* @return this
*/
public UrlStringBuilder addParametersArray(String namespace, Map<String, String[]> parameters) {
for (final String name : parameters.keySet()) {
Validate.notNull(name, "parameter map cannot contain any null keys");
}
for (final Map.Entry<String, String[]> newParamEntry : parameters.entrySet()) {
final String name = newParamEntry.getKey();
final List<String> values = this.copy(newParamEntry.getValue());
this.parameters.put(namespace + name, values);
}
return this;
}
/**
* Removes any existing path elements and sets the provided elements as the path
*
* @param elements Path elements to set
* @return this
*/
public UrlStringBuilder setPath(String... elements) {
Validate.noNullElements(elements, "elements cannot be null");
this.path.clear();
this.addPath(elements);
return this;
}
/**
* Adds a single element to the path.
*
* @param element The element to add
* @return this
*/
public UrlStringBuilder addPath(String element) {
Validate.notNull(element, "element cannot be null");
this.path.add(element);
return this;
}
/**
* Adds the provided elements to the path
*
* @param elements Path elements to add
* @return this
*/
public UrlStringBuilder addPath(String... elements) {
Validate.noNullElements(elements, "elements cannot be null");
for (final String element : elements) {
this.path.add(element);
}
return this;
}
/* (non-Javadoc)
* @see java.lang.Object#clone()
*/
@Override
public Object clone() {
return new UrlStringBuilder(this);
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (!this.getClass().isInstance(obj)) {
return false;
}
return this.toString().equals(obj.toString());
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return this.toString().hashCode();
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
final StringBuilder url = new StringBuilder();
//Add protocol://host:port if they are set
if (this.host != null) {
url.append(this.protocol).append("://").append(this.host);
if (this.port != null) {
url.append(":").append(this.port);
}
}
if (this.context != null) {
url.append("/").append(context);
}
//If no host/port/context and no path start with a /
else if (this.path.size() == 0) {
url.append("/");
}
//Add the path
for (final String element : this.path) {
url.append("/").append(this.encode(element));
}
//Add parameters
if (this.parameters.size() > 0) {
url.append("?");
for (final Iterator<Map.Entry<String, List<String>>> paramEntryItr =
this.parameters.entrySet().iterator();
paramEntryItr.hasNext();
) {
final Entry<String, List<String>> paramEntry = paramEntryItr.next();
String name = paramEntry.getKey();
final List<String> values = paramEntry.getValue();
name = this.encode(name);
if (values == null || values.size() == 0) {
url.append(name);
} else {
for (final Iterator<String> valueItr = values.iterator();
valueItr.hasNext();
) {
String value = valueItr.next();
if (value == null) {
value = "";
}
value = this.encode(value);
url.append(name).append("=").append(value);
if (valueItr.hasNext()) {
url.append("&");
}
}
}
if (paramEntryItr.hasNext()) {
url.append("&");
}
}
}
return url.toString();
}
}