Skip to content

Commit

Permalink
UriUtils: Also move addPathComponent to utility class + add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sjoerdtalsma committed Aug 16, 2018
1 parent 6c0831a commit 053e091
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
Expand All @@ -31,7 +30,8 @@
import static java.util.Collections.unmodifiableSet;
import static java.util.Objects.requireNonNull;
import static nl.talsmasoftware.umldoclet.util.FileUtils.openReaderTo;
import static nl.talsmasoftware.umldoclet.util.UriUtils.addParam;
import static nl.talsmasoftware.umldoclet.util.UriUtils.addHttpParam;
import static nl.talsmasoftware.umldoclet.util.UriUtils.addPathComponent;

final class ExternalLink {

Expand All @@ -49,7 +49,7 @@ final class ExternalLink {
Optional<URI> resolveType(String packagename, String typeName) {
if (packages().contains(packagename)) {
String document = packagename.replace('.', '/') + "/" + typeName + ".html";
return Optional.of(addParam(makeAbsolute(addPathComponent(docUri, document)), "is-external", "true"));
return Optional.of(addHttpParam(makeAbsolute(addPathComponent(docUri, document)), "is-external", "true"));
}
return Optional.empty();
}
Expand Down Expand Up @@ -90,23 +90,4 @@ private static URI createUri(String uri) {
}
}

private static URI addPathComponent(URI uri, String component) {
try {
String scheme = uri.getScheme();
String userInfo = uri.getUserInfo();
String host = uri.getHost();
int port = uri.getPort();
String path = uri.getPath();
path = path == null ? component
: path.endsWith("/") || component.startsWith("/") ? path + component
: path + "/" + component;
String query = uri.getQuery();
String fragment = uri.getFragment();
return new URI(scheme, userInfo, host, port, path, query, fragment);
} catch (URISyntaxException use) {
throw new IllegalStateException("Could not add path component \"" + component + "\" to " + uri + ": "
+ use.getMessage(), use);
}
}

}
100 changes: 88 additions & 12 deletions src/main/java/nl/talsmasoftware/umldoclet/util/UriUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,108 @@

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Optional;

/**
* Utility class for manipulating URI's
*
* @author Sjoerd Talsma
*/
public final class UriUtils {
/**
* For simple roll-our-own hex encoding.
*/
private static final char[] HEX = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

private UriUtils() {
throw new UnsupportedOperationException();
}

public static URI addParam(URI uri, String name, String value) {
if (uri == null || name == null || value == null) return uri;
try {
String scheme = uri.getScheme();
String query = uri.getQuery();
if (scheme != null && !"file".equals(scheme)) {
if (query == null || query.isEmpty()) query = name + "=" + value;
else query = query + "&" + name + "=" + value;
}
return new URI(scheme, uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), query, uri.getFragment());
/**
* This method adds a 'component' to the path of an URI.
*
* @param uri The URI to add a path component to
* @param component The component to add to the end of the uri path, separated by a slash ({@code '/'}) character
* @return The new URI
*/
public static URI addPathComponent(URI uri, String component) {
if (uri != null && component != null) try {
return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(),
Optional.ofNullable(uri.getPath()).map(path -> join(path, component, '/')).orElse(component),
uri.getQuery(), uri.getFragment());
} catch (URISyntaxException use) {
throw new IllegalStateException("Could not add path query parameter \"" + name + "=" + value + "\" to "
+ uri + ": " + use.getMessage(), use);
throw new IllegalArgumentException("Could not add path component \"" + component + "\" to " + uri + ": "
+ use.getMessage(), use);
}
return uri;
}

/**
* This method adds a query parameter to an existing URI and takes care of proper encoding etc.
* <p>
* Since query parameters are scheme-specific, this method only applies to URI's with the following schemes:
* <ol>
* <li>{@code "http"}</li>
* <li>{@code "https"}</li>
* </ol>
*
* @param uri The URI to add an HTTP parameter to
* @param name The name of the parameter to add
* @param value The value of the parameter to add
* @return The URI to which a parameter may have been added
*/
public static URI addHttpParam(URI uri, String name, String value) {
if (uri != null && name != null && value != null && ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme()))) {
final String base = uri.toASCIIString();
final int queryIdx = base.indexOf('?');
final int fragmentIdx = base.indexOf('#', queryIdx < 0 ? 0 : queryIdx);
StringBuilder newUri = new StringBuilder(fragmentIdx >= 0 ? base.substring(0, fragmentIdx) : base);
newUri.append(queryIdx < 0 ? '?' : '&');
appendEncoded(newUri, name);
newUri.append('=');
appendEncoded(newUri, value);
if (fragmentIdx >= 0) newUri.append(base, fragmentIdx, base.length());
return URI.create(newUri.toString());
}
return uri;
}

// TODO use UTF-8 before escaping, this only works for ascii (which is all we currently need)
private static void appendEncoded(StringBuilder sb, String value) {
for (char ch : value.toCharArray()) {
if (isUnreserved(ch)) sb.append(ch);
else appendEscapedByte(sb, (byte) ch);
}
}

private static String join(String left, String right, char separator) {
if (left.isEmpty()) return right;
else if (right.isEmpty()) return left;
String sep = "" + separator;
return left.endsWith(sep) || right.startsWith(sep) ? left + right : left + separator + right;
}

/**
* In the <a href="http://tools.ietf.org/html/rfc3986">URI specification (RFC-3986)</a>,
* unreserved characters are defined as {@code ALPHA / DIGIT / "-" / "." / "_" / "~"}.
*
* @param ch The character to examine.
* @return Whether the character is an unreserved
*/
private static boolean isUnreserved(char ch) {
return Character.isLetterOrDigit(ch) || ch == '-' || ch == '.' || ch == '_' || ch == '~';
}

/**
* Appends the byte as percent-encoded hex value (three characters).
*
* @param builder The builder to append to
* @param value the type to be appended as percent-encoded
*/
private static void appendEscapedByte(StringBuilder builder, byte value) {
builder.append('%');
builder.append(HEX[(value >> 4) & 0x0f]);
builder.append(HEX[value & 0x0f]);
}

}
82 changes: 76 additions & 6 deletions src/test/java/nl/talsmasoftware/umldoclet/util/UriUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;

public class UriUtilsTest {

Expand All @@ -31,31 +32,100 @@ public void testUnsupportedConstructor() {
Testing.assertUnsupportedConstructor(UriUtils.class);
}

@Test
public void testAddPathComponent_nulls() {
final URI uri = URI.create("http://www.google.com");
assertThat(UriUtils.addPathComponent(null, "component"), is(nullValue()));
assertThat(UriUtils.addPathComponent(uri, null), is(sameInstance(uri)));
}

@Test
public void testAddPathComponent() {
URI uri = URI.create("http://www.google.com?q=query#fragment");
String expected = "http://www.google.com";
String query = "?q=query#fragment";

uri = UriUtils.addPathComponent(uri, "/component");
expected += "/component";
assertThat(uri, is(URI.create(expected + query)));

uri = UriUtils.addPathComponent(uri, "endsWithSlash/");
expected += "/endsWithSlash/";
assertThat(uri, is(URI.create(expected + query)));

uri = UriUtils.addPathComponent(uri, "last");
expected += "last";
assertThat(uri, is(URI.create(expected + query)));
}

@Test
public void testAddPathComponent_specialCharacters() {
URI uri = URI.create("http://www.google.com?q=query#fragment");
String expectedPath = "http://www.google.com";
String query = "?q=query#fragment";

uri = UriUtils.addPathComponent(uri, "/with-dashes");
expectedPath += "/with-dashes";
assertThat(uri, is(URI.create(expectedPath + query)));

uri = UriUtils.addPathComponent(uri, "and spaces/");
expectedPath += "/and%20spaces/";
assertThat(uri, is(URI.create(expectedPath + query)));

uri = UriUtils.addPathComponent(uri, "or ? question-marks");
expectedPath += "or%20%3F%20question-marks";
assertThat(uri, is(URI.create(expectedPath + query)));
}

@Test(expected = IllegalArgumentException.class)
public void testAddPathComponent_relativePathInAbsoluteUri() {
UriUtils.addPathComponent(URI.create("http://www.google.com?q=query"), "relative");
}

@Test
public void testAddParam_nulls() {
final URI uri = URI.create("http://www.google.com");
assertThat(UriUtils.addParam(null, "name", "value"), is(nullValue()));
assertThat(UriUtils.addParam(uri, null, "value"), is(equalTo(URI.create("http://www.google.com"))));
assertThat(UriUtils.addParam(uri, "name", null), is(equalTo(URI.create("http://www.google.com"))));
assertThat(UriUtils.addHttpParam(null, "name", "value"), is(nullValue()));
assertThat(UriUtils.addHttpParam(uri, null, "value"), is(equalTo(URI.create("http://www.google.com"))));
assertThat(UriUtils.addHttpParam(uri, "name", null), is(equalTo(URI.create("http://www.google.com"))));
}

@Test
public void testAddParam_relativeLink() {
final URI uri = URI.create("../relativepath");
assertThat(UriUtils.addParam(uri, "name", "value"), is(equalTo(uri)));
assertThat(UriUtils.addHttpParam(uri, "name", "value"), is(equalTo(uri)));
}

@Test
public void testAddParam_fileLink() {
final URI uri = URI.create("file:/absolutepath");
assertThat(UriUtils.addParam(uri, "name", "value"), is(equalTo(uri)));
assertThat(UriUtils.addHttpParam(uri, "name", "value"), is(equalTo(uri)));
}

@Test
public void testAddParam_httpLink() {
final URI uri = URI.create("http://www.google.com");
assertThat(UriUtils.addParam(uri, "q", "This is my query"),
assertThat(UriUtils.addHttpParam(uri, "q", "This is my query"),
is(equalTo(URI.create("http://www.google.com?q=This%20is%20my%20query"))));
}

@Test
public void testAddParam_secondParameter_withFragment() {
final URI uri = URI.create("https://www.google.com?q=This%20is%20my%20query#fragment");
assertThat(UriUtils.addHttpParam(uri, "q", "And this is my second"),
is(equalTo(URI.create("https://www.google.com?q=This%20is%20my%20query&q=And%20this%20is%20my%20second#fragment"))));
}

@Test
public void testAddParam_specialQueryCharacters() {
URI uri = URI.create("http://www.google.com#fragment");
uri = UriUtils.addHttpParam(uri, "query parameter#", "left = right");
String expected = "http://www.google.com?query%20parameter%23=left%20%3D%20right";
assertThat(uri, is(equalTo(URI.create(expected + "#fragment"))));

uri = UriUtils.addHttpParam(uri, "what's the query?", "this & that");
expected += "&what%27s%20the%20query%3F=this%20%26%20that";
assertThat(uri, is(equalTo(URI.create(expected + "#fragment"))));
}

}

0 comments on commit 053e091

Please sign in to comment.