Skip to content

Commit

Permalink
Merge pull request #238 from ocpsoft/encoding
Browse files Browse the repository at this point in the history
Encoding fixes and fixes for #195 and #224
  • Loading branch information
chkal authored Jul 19, 2016
2 parents 04c995b + 3a6546e commit 114ab36
Show file tree
Hide file tree
Showing 33 changed files with 1,000 additions and 405 deletions.
11 changes: 10 additions & 1 deletion addressbuilder/src/main/java/org/ocpsoft/urlbuilder/Address.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
*/
package org.ocpsoft.urlbuilder;

import java.util.List;
import java.util.Map;

/**
* Represents a valid web address, or valid web address fragment.
*
Expand Down Expand Up @@ -84,7 +87,8 @@ public interface Address
String getSchemeSpecificPart();

/**
* Return <code>true</code> if this {@link Address} has a scheme specific part section, otherwise return <code>false</code>.
* Return <code>true</code> if this {@link Address} has a scheme specific part section, otherwise return
* <code>false</code>.
*/
boolean isSchemeSpecificPartSet();

Expand All @@ -93,6 +97,11 @@ public interface Address
*/
String getQuery();

/**
* Get the query parameters of this {@link Address}, or <code>null</code> if no query is set.
*/
Map<String, List<Object>> getQueryParameters();

/**
* Return <code>true</code> if this {@link Address} contains a query section, otherwise return <code>false</code>.
*/
Expand Down
186 changes: 160 additions & 26 deletions addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -27,7 +28,8 @@
import org.ocpsoft.urlbuilder.util.Encoder;

/**
* Representation of a uniform resource locator, or web address. Internal state is not encoded, plain UTF-8.
* Representation of a uniform resource locator, or web address. Internal state is stored as it is originally provided,
* and must be encoded or decoded as necessary.
*
* @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
*/
Expand Down Expand Up @@ -58,6 +60,21 @@ public static AddressBuilderBase begin()
* Generate an {@link Address} representing the current state of this {@link AddressBuilder}.
*/
protected Address build()
{
if (address == null)
{
address = new ParameterizedAddressResult(this);
}
return address;
}

/**
* Generate an {@link Address} representing the current literal state of this {@link AddressBuilder}.
* <p>
* (Does not apply parameterization. E.g. The URL `/{foo}` will be treated as literal text, as opposed to calling
* {@link #build()}, which would result in `foo` being treated as a parameterized expression)
*/
protected Address buildLiteral()
{
if (address == null)
{
Expand All @@ -67,8 +84,8 @@ protected Address build()
}

/**
* Create a new {@link Address} from the given fully encoded URL. Improperly formatted or encoded URLs are not
* parse-able and will result in an exception.
* Create a new {@link Address} from the given URL. Improperly formatted or encoded URLs are not parse-able and will
* result in an exception. No builder parameterization is possible using this method.
*
* @see http://en.wikipedia.org/wiki/URI_scheme
* @throws IllegalArgumentException when the input URL or URL fragment is not valid.
Expand All @@ -79,16 +96,18 @@ public static Address create(String url) throws IllegalArgumentException
URI u = new URI(url);
String scheme = u.getScheme();
String host = u.getHost();
if(scheme != null && host == null)
return AddressBuilder.begin().scheme(u.getScheme()).schemeSpecificPart(u.getRawSchemeSpecificPart()).build();
if (scheme != null && host == null)
return AddressBuilder.begin().scheme(u.getScheme()).schemeSpecificPart(u.getRawSchemeSpecificPart())
.buildLiteral();
else
return AddressBuilder.begin().scheme(scheme).domain(host).port(u.getPort())
.pathEncoded(u.getRawPath()).queryLiteral(u.getRawQuery()).anchor(u.getRawFragment()).build();
return AddressBuilder.begin().scheme(scheme).domain(host).port(u.getPort())
.path(u.getRawPath()).queryLiteral(u.getRawQuery()).anchor(u.getRawFragment()).buildLiteral();
}
catch (URISyntaxException e) {
throw new IllegalArgumentException(
"[" + url + "] is not a valid URL fragment. Consider encoding relevant portions of the URL with ["
+ Encoder.class + "]", e);
+ Encoder.class
+ "], or use the provided builder pattern via this class to specify part encoding.", e);
}
}

Expand Down Expand Up @@ -130,8 +149,8 @@ AddressBuilderPort port(int port)
}

/**
* Set the non-encoded path section of this {@link Address}. The given value will be stored without additional
* encoding or decoding.
* Set the path section of this {@link Address}. The given value will be stored without additional encoding or
* decoding.
*/
AddressBuilderPath path(CharSequence path)
{
Expand All @@ -140,31 +159,70 @@ AddressBuilderPath path(CharSequence path)
}

/**
* Set the encoded path section of this {@link Address}. The given value will be decoded before it is stored.
* Set the path section of this {@link Address}. The given value will be decoded before it is stored.
*/
AddressBuilderPath pathEncoded(CharSequence path)
AddressBuilderPath pathDecoded(CharSequence path)
{
this.path = Decoder.path(path);
return new AddressBuilderPath(this);
}

/**
* Set a query-parameter to a value or multiple values. The given name and values will be encoded before they are
* stored.
* Set the path section of this {@link Address}. The given value will be encoded before it is stored.
*/
AddressBuilderPath pathEncoded(CharSequence path)
{
this.path = Encoder.path(path);
return new AddressBuilderPath(this);
}

/**
* Set a query-parameter to a value or multiple values. The given name and values will be stored without additional
* encoding or decoding.
*/
AddressBuilderQuery query(CharSequence name, Object... values)
{
this.queries.put(Encoder.query(name.toString()), Parameter.create(name.toString(), true, values));
this.queries.put(name.toString(), Parameter.create(name.toString(), values));
return new AddressBuilderQuery(this);
}

/**
* Set a query-parameter value or multiple values. The given name and values be decoded before they are stored.
*/
AddressBuilderQuery queryDecoded(CharSequence name, Object... values)
{
if (values != null)
{
List<Object> encodedValues = new ArrayList<Object>(values.length);
for (Object value : values)
{
if (value == null)
encodedValues.add(value);
else
encodedValues.add(Decoder.query(value.toString()));
}
this.queries.put(Decoder.query(name.toString()), Parameter.create(name.toString(), encodedValues));
}
return new AddressBuilderQuery(this);
}

/**
* Set a pre-encoded query-parameter to a pre-encoded value or multiple values. The given name and values be stored
* without additional encoding or decoding.
* Set a query-parameter to a value or multiple values. The given name and values be encoded before they are stored.
*/
AddressBuilderQuery queryEncoded(CharSequence name, Object... values)
{
this.queries.put(name.toString(), Parameter.create(name.toString(), false, values));
if (values != null)
{
List<Object> encodedValues = new ArrayList<Object>(values.length);
for (Object value : values)
{
if (value == null)
encodedValues.add(value);
else
encodedValues.add(Encoder.query(value.toString()));
}
this.queries.put(Encoder.query(name.toString()), Parameter.create(name.toString(), encodedValues));
}
return new AddressBuilderQuery(this);
}

Expand Down Expand Up @@ -224,7 +282,7 @@ AddressBuilderQuery queryLiteral(String query)
}

for (Entry<CharSequence, List<CharSequence>> entry : params.entrySet()) {
queryEncoded(entry.getKey(), entry.getValue().toArray());
query(entry.getKey(), entry.getValue().toArray());
}
}
return new AddressBuilderQuery(this);
Expand Down Expand Up @@ -253,26 +311,102 @@ AddressBuilderAnchor anchor(CharSequence anchor)
}

/**
* Set a parameter name and value or values. Any supplied values will be encoded appropriately for their location in
* the {@link Address}.
* Set a parameter name and value or values. The supplied values will be stored without additional encoding.
*/
void set(CharSequence name, Object... values)
{
this.parameters.put(name.toString(), Parameter.create(name.toString(), true, values));
this.parameters.put(name.toString(), Parameter.create(name.toString(), values));
}

/**
* Set a pre-encoded parameter name and value or values. The values will be stored with no additional encoding or
* decoding.
* Set a parameter name and value or values. The values will be decoded before they are stored.
*/
void setDecoded(CharSequence name, Object... values)
{
if (values != null)
{
List<Object> encodedValues = new ArrayList<Object>(values.length);
for (Object value : values)
{
if (value == null)
encodedValues.add(value);
else
encodedValues.add(Decoder.path(value.toString()));
}
this.parameters.put(name.toString(), Parameter.create(name.toString(), encodedValues));
}
}

/**
* Set a parameter name and value or values. The values will be encoded before they are stored.
*/
void setEncoded(CharSequence name, Object... values)
{
this.parameters.put(name.toString(), Parameter.create(name.toString(), false, values));
if (values != null)
{
List<Object> encodedValues = new ArrayList<Object>(values.length);
for (Object value : values)
{
if (value == null)
encodedValues.add(value);
else
encodedValues.add(Encoder.path(value.toString()));
}
this.parameters.put(name.toString(), Parameter.create(name.toString(), encodedValues));
}
}

@Override
public String toString()
{
return build().toString();
return buildLiteral().toString();
}

/**
* Package private method for {@link Address} implementations to use for rendering.
*/
static StringBuilder toString(Address address)
{
StringBuilder result = new StringBuilder();

if (address.isSchemeSet())
result.append(address.getScheme()).append(":");

if (address.isSchemeSpecificPartSet())
{
result.append(address.getSchemeSpecificPart());
}
else
{
if (address.isDomainSet())
result.append("//").append(address.getDomain());

if (address.isPortSet())
result.append(":").append(address.getPort());

if (address.isPathSet())
result.append(address.getPath());

if (address.isQuerySet())
{
if (address.isDomainSet() && !address.isPathSet())
result.append("/");
result.append('?').append(address.getQuery());
}

if (address.isAnchorSet())
result.append('#').append(address.getAnchor());
}
return result;
}

Map<String, List<Object>> getQueries()
{
Map<String, List<Object>> result = new LinkedHashMap<String, List<Object>>();
for (Entry<CharSequence, Parameter> entry : this.queries.entrySet()) {
CharSequence key = entry.getKey();
result.put(key == null ? null : key.toString(), entry.getValue().getValues());
}
return Collections.unmodifiableMap(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*
* @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
*/
public class AddressBuilderAnchor
public class AddressBuilderAnchor implements BuildableAddress
{
private AddressBuilder parent;

Expand All @@ -29,14 +29,18 @@ public class AddressBuilderAnchor
this.parent = parent;
}

/**
* Generate an {@link Address} representing the current state of this {@link AddressBuilder}.
*/
@Override
public Address build()
{
return parent.build();
}

@Override
public Address buildLiteral()
{
return parent.buildLiteral();
}

@Override
public String toString()
{
Expand Down
Loading

0 comments on commit 114ab36

Please sign in to comment.