Builder to construct DomainNameMapping.
Motivation: DomainNameMapping.add() makes DomainNameMapping look like it's safe to call add() anytime, and this is never true. It's probably better deprecate add() and introduce DomainNameMappingBuilder. Modifications: Made an immutable implementation of DomainNameMapping; Added Builder for immutable DomainNameMapping; Replaced regex pattern with String::startsWith check; Replaced HashMap with two arrays in ImmutableDomainNameMapping; Deprecated mutable API; Estimation for StringBuilder initial size in ImmutableDomainNameMapping#toString() Added StringUtil#commonSuffixOfLength Replaced unnecessary substrings creation in DomainNameMapping#matches with regionMatches Result: Clients will be able to create immutable instances of DomainNameMapping with builder API.
This commit is contained in:
parent
f750d6e36c
commit
7c1602125a
191
common/src/main/java/io/netty/util/DomainMappingBuilder.java
Normal file
191
common/src/main/java/io/netty/util/DomainMappingBuilder.java
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project 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:
|
||||||
|
*
|
||||||
|
* 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 io.netty.util;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for immutable {@link DomainNameMapping} instances.
|
||||||
|
*
|
||||||
|
* @param <V> concrete type of value objects
|
||||||
|
*/
|
||||||
|
public final class DomainMappingBuilder<V> {
|
||||||
|
|
||||||
|
private final V defaultValue;
|
||||||
|
private final Map<String, V> map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with default initial capacity of the map holding the mappings
|
||||||
|
*
|
||||||
|
* @param defaultValue the default value for {@link DomainNameMapping#map(String)} to return
|
||||||
|
* when nothing matches the input
|
||||||
|
*/
|
||||||
|
public DomainMappingBuilder(V defaultValue) {
|
||||||
|
this(4, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with initial capacity of the map holding the mappings
|
||||||
|
*
|
||||||
|
* @param initialCapacity initial capacity for the internal map
|
||||||
|
* @param defaultValue the default value for {@link DomainNameMapping#map(String)} to return
|
||||||
|
* when nothing matches the input
|
||||||
|
*/
|
||||||
|
public DomainMappingBuilder(int initialCapacity, V defaultValue) {
|
||||||
|
this.defaultValue = checkNotNull(defaultValue, "defaultValue");
|
||||||
|
this.map = new LinkedHashMap<String, V>(initialCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a mapping that maps the specified (optionally wildcard) host name to the specified output value.
|
||||||
|
* Null values are forbidden for both hostnames and values.
|
||||||
|
* <p>
|
||||||
|
* <a href="http://en.wikipedia.org/wiki/Wildcard_DNS_record">DNS wildcard</a> is supported as hostname.
|
||||||
|
* For example, you can use {@code *.netty.io} to match {@code netty.io} and {@code downloads.netty.io}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param hostname the host name (optionally wildcard)
|
||||||
|
* @param output the output value that will be returned by {@link DomainNameMapping#map(String)}
|
||||||
|
* when the specified host name matches the specified input host name
|
||||||
|
*/
|
||||||
|
public DomainMappingBuilder<V> add(String hostname, V output) {
|
||||||
|
map.put(checkNotNull(hostname, "hostname"), checkNotNull(output, "output"));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of immutable {@link DomainNameMapping}
|
||||||
|
* Attempts to add new mappings to the result object will cause {@link UnsupportedOperationException} to be thrown
|
||||||
|
*
|
||||||
|
* @return new {@link DomainNameMapping} instance
|
||||||
|
*/
|
||||||
|
public DomainNameMapping<V> build() {
|
||||||
|
return new ImmutableDomainNameMapping<V>(this.defaultValue, this.map);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable mapping from domain name pattern to its associated value object.
|
||||||
|
* Mapping is represented by two arrays: keys and values. Key domainNamePatterns[i] is associated with values[i].
|
||||||
|
*
|
||||||
|
* @param <V> concrete type of value objects
|
||||||
|
*/
|
||||||
|
private static final class ImmutableDomainNameMapping<V> extends DomainNameMapping<V> {
|
||||||
|
private static final String REPR_HEADER = "ImmutableDomainNameMapping(default: ";
|
||||||
|
private static final String REPR_MAP_OPENING = ", map: {";
|
||||||
|
private static final String REPR_MAP_CLOSING = "})";
|
||||||
|
private static final int REPR_CONST_PART_LENGTH =
|
||||||
|
REPR_HEADER.length() + REPR_MAP_OPENING.length() + REPR_MAP_CLOSING.length();
|
||||||
|
|
||||||
|
private final String[] domainNamePatterns;
|
||||||
|
private final V[] values;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private ImmutableDomainNameMapping(V defaultValue, Map<String, V> map) {
|
||||||
|
super(null, defaultValue);
|
||||||
|
|
||||||
|
Set<Map.Entry<String, V>> mappings = map.entrySet();
|
||||||
|
int numberOfMappings = mappings.size();
|
||||||
|
domainNamePatterns = new String[numberOfMappings];
|
||||||
|
values = (V[]) new Object[numberOfMappings];
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
for (Map.Entry<String, V> mapping : mappings) {
|
||||||
|
domainNamePatterns[index] = normalizeHostname(mapping.getKey());
|
||||||
|
values[index] = mapping.getValue();
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public DomainNameMapping<V> add(String hostname, V output) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"Immutable DomainNameMapping does not support modification after initial creation");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V map(String hostname) {
|
||||||
|
if (hostname != null) {
|
||||||
|
hostname = normalizeHostname(hostname);
|
||||||
|
|
||||||
|
int length = domainNamePatterns.length;
|
||||||
|
for (int index = 0; index < length; ++index) {
|
||||||
|
if (matches(domainNamePatterns[index], hostname)) {
|
||||||
|
return values[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String defaultValueStr = defaultValue.toString();
|
||||||
|
|
||||||
|
int numberOfMappings = domainNamePatterns.length;
|
||||||
|
if (numberOfMappings == 0) {
|
||||||
|
return REPR_HEADER + defaultValueStr + REPR_MAP_OPENING + REPR_MAP_CLOSING;
|
||||||
|
}
|
||||||
|
|
||||||
|
String pattern0 = domainNamePatterns[0];
|
||||||
|
String value0 = values[0].toString();
|
||||||
|
int oneMappingLength = pattern0.length() + value0.length() + 3; // 2 for separator ", " and 1 for '='
|
||||||
|
int estimatedBufferSize = estimateBufferSize(defaultValueStr.length(), numberOfMappings, oneMappingLength);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(estimatedBufferSize)
|
||||||
|
.append(REPR_HEADER).append(defaultValueStr).append(REPR_MAP_OPENING);
|
||||||
|
|
||||||
|
appendMapping(sb, pattern0, value0);
|
||||||
|
for (int index = 1; index < numberOfMappings; ++index) {
|
||||||
|
sb.append(", ");
|
||||||
|
appendMapping(sb, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.append(REPR_MAP_CLOSING).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimates the length of string representation of the given instance:
|
||||||
|
* est = lengthOfConstantComponents + defaultValueLength + (estimatedMappingLength * numOfMappings) * 1.10
|
||||||
|
*
|
||||||
|
* @param defaultValueLength length of string representation of {@link #defaultValue}
|
||||||
|
* @param numberOfMappings number of mappings the given instance holds,
|
||||||
|
* e.g. {@link #domainNamePatterns#length}
|
||||||
|
* @param estimatedMappingLength estimated size taken by one mapping
|
||||||
|
* @return estimated length of string returned by {@link #toString()}
|
||||||
|
*/
|
||||||
|
private static int estimateBufferSize(int defaultValueLength,
|
||||||
|
int numberOfMappings,
|
||||||
|
int estimatedMappingLength) {
|
||||||
|
return REPR_CONST_PART_LENGTH + defaultValueLength
|
||||||
|
+ (int) (estimatedMappingLength * numberOfMappings * 1.10);
|
||||||
|
}
|
||||||
|
|
||||||
|
private StringBuilder appendMapping(StringBuilder sb, int mappingIndex) {
|
||||||
|
return appendMapping(sb, domainNamePatterns[mappingIndex], values[mappingIndex].toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringBuilder appendMapping(StringBuilder sb, String domainNamePattern, String value) {
|
||||||
|
return sb.append(domainNamePattern).append('=').append(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,9 @@ import java.net.IDN;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
|
import static io.netty.util.internal.StringUtil.commonSuffixOfLength;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps a domain name to its associated value object.
|
* Maps a domain name to its associated value object.
|
||||||
@ -33,18 +35,17 @@ import java.util.regex.Pattern;
|
|||||||
*/
|
*/
|
||||||
public class DomainNameMapping<V> implements Mapping<String, V> {
|
public class DomainNameMapping<V> implements Mapping<String, V> {
|
||||||
|
|
||||||
private static final Pattern DNS_WILDCARD_PATTERN = Pattern.compile("^\\*\\..*");
|
final V defaultValue;
|
||||||
|
|
||||||
private final Map<String, V> map;
|
private final Map<String, V> map;
|
||||||
|
|
||||||
private final V defaultValue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a default, order-sensitive mapping. If your hostnames are in conflict, the mapping
|
* Creates a default, order-sensitive mapping. If your hostnames are in conflict, the mapping
|
||||||
* will choose the one you add first.
|
* will choose the one you add first.
|
||||||
*
|
*
|
||||||
* @param defaultValue the default value for {@link #map(String)} to return when nothing matches the input
|
* @param defaultValue the default value for {@link #map(String)} to return when nothing matches the input
|
||||||
|
* @deprecated use {@link DomainMappingBuilder} to create and fill the mapping instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public DomainNameMapping(V defaultValue) {
|
public DomainNameMapping(V defaultValue) {
|
||||||
this(4, defaultValue);
|
this(4, defaultValue);
|
||||||
}
|
}
|
||||||
@ -54,14 +55,17 @@ public class DomainNameMapping<V> implements Mapping<String, V> {
|
|||||||
* will choose the one you add first.
|
* will choose the one you add first.
|
||||||
*
|
*
|
||||||
* @param initialCapacity initial capacity for the internal map
|
* @param initialCapacity initial capacity for the internal map
|
||||||
* @param defaultValue the default value for {@link #map(String)} to return when nothing matches the input
|
* @param defaultValue the default value for {@link #map(String)} to return when nothing matches the input
|
||||||
|
* @deprecated use {@link DomainMappingBuilder} to create and fill the mapping instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public DomainNameMapping(int initialCapacity, V defaultValue) {
|
public DomainNameMapping(int initialCapacity, V defaultValue) {
|
||||||
if (defaultValue == null) {
|
this(new LinkedHashMap<String, V>(initialCapacity), defaultValue);
|
||||||
throw new NullPointerException("defaultValue");
|
}
|
||||||
}
|
|
||||||
map = new LinkedHashMap<String, V>(initialCapacity);
|
DomainNameMapping(Map<String, V> map, V defaultValue) {
|
||||||
this.defaultValue = defaultValue;
|
this.defaultValue = checkNotNull(defaultValue, "defaultValue");
|
||||||
|
this.map = map;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,39 +76,31 @@ public class DomainNameMapping<V> implements Mapping<String, V> {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param hostname the host name (optionally wildcard)
|
* @param hostname the host name (optionally wildcard)
|
||||||
* @param output the output value that will be returned by {@link #map(String)} when the specified host name
|
* @param output the output value that will be returned by {@link #map(String)} when the specified host name
|
||||||
* matches the specified input host name
|
* matches the specified input host name
|
||||||
|
* @deprecated use {@link DomainMappingBuilder} to create and fill the mapping instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public DomainNameMapping<V> add(String hostname, V output) {
|
public DomainNameMapping<V> add(String hostname, V output) {
|
||||||
if (hostname == null) {
|
map.put(normalizeHostname(checkNotNull(hostname, "hostname")), checkNotNull(output, "output"));
|
||||||
throw new NullPointerException("input");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output == null) {
|
|
||||||
throw new NullPointerException("output");
|
|
||||||
}
|
|
||||||
|
|
||||||
map.put(normalizeHostname(hostname), output);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple function to match <a href="http://en.wikipedia.org/wiki/Wildcard_DNS_record">DNS wildcard</a>.
|
* Simple function to match <a href="http://en.wikipedia.org/wiki/Wildcard_DNS_record">DNS wildcard</a>.
|
||||||
*/
|
*/
|
||||||
private static boolean matches(String hostNameTemplate, String hostName) {
|
static boolean matches(String template, String hostName) {
|
||||||
// note that inputs are converted and lowercased already
|
if (template.startsWith("*.")) {
|
||||||
if (DNS_WILDCARD_PATTERN.matcher(hostNameTemplate).matches()) {
|
return template.regionMatches(2, hostName, 0, hostName.length())
|
||||||
return hostNameTemplate.substring(2).equals(hostName) ||
|
|| commonSuffixOfLength(hostName, template, template.length() - 1);
|
||||||
hostName.endsWith(hostNameTemplate.substring(1));
|
|
||||||
} else {
|
|
||||||
return hostNameTemplate.equals(hostName);
|
|
||||||
}
|
}
|
||||||
|
return template.equals(hostName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IDNA ASCII conversion and case normalization
|
* IDNA ASCII conversion and case normalization
|
||||||
*/
|
*/
|
||||||
private static String normalizeHostname(String hostname) {
|
static String normalizeHostname(String hostname) {
|
||||||
if (needsNormalization(hostname)) {
|
if (needsNormalization(hostname)) {
|
||||||
hostname = IDN.toASCII(hostname, IDN.ALLOW_UNASSIGNED);
|
hostname = IDN.toASCII(hostname, IDN.ALLOW_UNASSIGNED);
|
||||||
}
|
}
|
||||||
@ -113,7 +109,7 @@ public class DomainNameMapping<V> implements Mapping<String, V> {
|
|||||||
|
|
||||||
private static boolean needsNormalization(String hostname) {
|
private static boolean needsNormalization(String hostname) {
|
||||||
final int length = hostname.length();
|
final int length = hostname.length();
|
||||||
for (int i = 0; i < length; i ++) {
|
for (int i = 0; i < length; i++) {
|
||||||
int c = hostname.charAt(i);
|
int c = hostname.charAt(i);
|
||||||
if (c > 0x7F) {
|
if (c > 0x7F) {
|
||||||
return true;
|
return true;
|
||||||
@ -123,17 +119,16 @@ public class DomainNameMapping<V> implements Mapping<String, V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public V map(String input) {
|
public V map(String hostname) {
|
||||||
if (input != null) {
|
if (hostname != null) {
|
||||||
input = normalizeHostname(input);
|
hostname = normalizeHostname(hostname);
|
||||||
|
|
||||||
for (Map.Entry<String, V> entry : map.entrySet()) {
|
for (Map.Entry<String, V> entry : map.entrySet()) {
|
||||||
if (matches(entry.getKey(), input)) {
|
if (matches(entry.getKey(), hostname)) {
|
||||||
return entry.getValue();
|
return entry.getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,6 +186,18 @@ public final class StringUtil {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if two strings have the same suffix of specified length
|
||||||
|
*
|
||||||
|
* @param s string
|
||||||
|
* @param p string
|
||||||
|
* @param len length of the common suffix
|
||||||
|
* @return true if both s and p are not null and both have the same suffix. Otherwise - false
|
||||||
|
*/
|
||||||
|
public static boolean commonSuffixOfLength(String s, String p, int len) {
|
||||||
|
return s != null && p != null && len >= 0 && s.regionMatches(s.length() - len, p, p.length() - len, len);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the specified byte value into a 2-digit hexadecimal integer.
|
* Converts the specified byte value into a 2-digit hexadecimal integer.
|
||||||
*/
|
*/
|
||||||
|
184
common/src/test/java/io/netty/util/DomainNameMappingTest.java
Normal file
184
common/src/test/java/io/netty/util/DomainNameMappingTest.java
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project 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:
|
||||||
|
*
|
||||||
|
* 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 io.netty.util;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public class DomainNameMappingTest {
|
||||||
|
|
||||||
|
// Deprecated API
|
||||||
|
|
||||||
|
@Test(expected = NullPointerException.class)
|
||||||
|
public void testNullDefaultValueInDeprecatedApi() {
|
||||||
|
new DomainNameMapping<String>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = NullPointerException.class)
|
||||||
|
public void testNullDomainNamePatternsAreForbiddenInDeprecatedApi() {
|
||||||
|
new DomainNameMapping<String>("NotFound").add(null, "Some value");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = NullPointerException.class)
|
||||||
|
public void testNullValuesAreForbiddenInDeprecatedApi() {
|
||||||
|
new DomainNameMapping<String>("NotFound").add("Some key", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultValueInDeprecatedApi() {
|
||||||
|
DomainNameMapping<String> mapping = new DomainNameMapping<String>("NotFound");
|
||||||
|
|
||||||
|
assertEquals("NotFound", mapping.map("not-existing"));
|
||||||
|
|
||||||
|
mapping.add("*.netty.io", "Netty");
|
||||||
|
|
||||||
|
assertEquals("NotFound", mapping.map("not-existing"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStrictEqualityInDeprecatedApi() {
|
||||||
|
DomainNameMapping<String> mapping = new DomainNameMapping<String>("NotFound")
|
||||||
|
.add("netty.io", "Netty")
|
||||||
|
.add("downloads.netty.io", "Netty-Downloads");
|
||||||
|
|
||||||
|
assertEquals("Netty", mapping.map("netty.io"));
|
||||||
|
assertEquals("Netty-Downloads", mapping.map("downloads.netty.io"));
|
||||||
|
|
||||||
|
assertEquals("NotFound", mapping.map("x.y.z.netty.io"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWildcardMatchesAnyPrefixInDeprecatedApi() {
|
||||||
|
DomainNameMapping<String> mapping = new DomainNameMapping<String>("NotFound")
|
||||||
|
.add("*.netty.io", "Netty");
|
||||||
|
|
||||||
|
assertEquals("Netty", mapping.map("netty.io"));
|
||||||
|
assertEquals("Netty", mapping.map("downloads.netty.io"));
|
||||||
|
assertEquals("Netty", mapping.map("x.y.z.netty.io"));
|
||||||
|
|
||||||
|
assertEquals("NotFound", mapping.map("netty.io.x"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFirstMatchWinsInDeprecatedApi() {
|
||||||
|
assertEquals("Netty",
|
||||||
|
new DomainNameMapping<String>("NotFound")
|
||||||
|
.add("*.netty.io", "Netty")
|
||||||
|
.add("downloads.netty.io", "Netty-Downloads")
|
||||||
|
.map("downloads.netty.io"));
|
||||||
|
|
||||||
|
assertEquals("Netty-Downloads",
|
||||||
|
new DomainNameMapping<String>("NotFound")
|
||||||
|
.add("downloads.netty.io", "Netty-Downloads")
|
||||||
|
.add("*.netty.io", "Netty")
|
||||||
|
.map("downloads.netty.io"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToStringInDeprecatedApi() {
|
||||||
|
DomainNameMapping<String> mapping = new DomainNameMapping<String>("NotFound")
|
||||||
|
.add("*.netty.io", "Netty")
|
||||||
|
.add("downloads.netty.io", "Netty-Downloads");
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"DomainNameMapping(default: NotFound, map: {*.netty.io=Netty, downloads.netty.io=Netty-Downloads})",
|
||||||
|
mapping.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Immutable DomainNameMapping Builder API
|
||||||
|
|
||||||
|
@Test(expected = NullPointerException.class)
|
||||||
|
public void testNullDefaultValue() {
|
||||||
|
new DomainMappingBuilder<String>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = NullPointerException.class)
|
||||||
|
public void testNullDomainNamePatternsAreForbidden() {
|
||||||
|
new DomainMappingBuilder<String>("NotFound").add(null, "Some value");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = NullPointerException.class)
|
||||||
|
public void testNullValuesAreForbidden() {
|
||||||
|
new DomainMappingBuilder<String>("NotFound").add("Some key", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultValue() {
|
||||||
|
DomainNameMapping<String> mapping = new DomainMappingBuilder<String>("NotFound")
|
||||||
|
.add("*.netty.io", "Netty")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertEquals("NotFound", mapping.map("not-existing"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStrictEquality() {
|
||||||
|
DomainNameMapping<String> mapping = new DomainMappingBuilder<String>("NotFound")
|
||||||
|
.add("netty.io", "Netty")
|
||||||
|
.add("downloads.netty.io", "Netty-Downloads")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertEquals("Netty", mapping.map("netty.io"));
|
||||||
|
assertEquals("Netty-Downloads", mapping.map("downloads.netty.io"));
|
||||||
|
|
||||||
|
assertEquals("NotFound", mapping.map("x.y.z.netty.io"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWildcardMatchesAnyPrefix() {
|
||||||
|
DomainNameMapping<String> mapping = new DomainMappingBuilder<String>("NotFound")
|
||||||
|
.add("*.netty.io", "Netty")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertEquals("Netty", mapping.map("netty.io"));
|
||||||
|
assertEquals("Netty", mapping.map("downloads.netty.io"));
|
||||||
|
assertEquals("Netty", mapping.map("x.y.z.netty.io"));
|
||||||
|
|
||||||
|
assertEquals("NotFound", mapping.map("netty.io.x"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFirstMatchWins() {
|
||||||
|
assertEquals("Netty",
|
||||||
|
new DomainMappingBuilder<String>("NotFound")
|
||||||
|
.add("*.netty.io", "Netty")
|
||||||
|
.add("downloads.netty.io", "Netty-Downloads")
|
||||||
|
.build()
|
||||||
|
.map("downloads.netty.io"));
|
||||||
|
|
||||||
|
assertEquals("Netty-Downloads",
|
||||||
|
new DomainMappingBuilder<String>("NotFound")
|
||||||
|
.add("downloads.netty.io", "Netty-Downloads")
|
||||||
|
.add("*.netty.io", "Netty")
|
||||||
|
.build()
|
||||||
|
.map("downloads.netty.io"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToString() {
|
||||||
|
DomainNameMapping<String> mapping = new DomainMappingBuilder<String>("NotFound")
|
||||||
|
.add("*.netty.io", "Netty")
|
||||||
|
.add("downloads.netty.io", "Netty-Download")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"ImmutableDomainNameMapping(default: NotFound, map: {*.netty.io=Netty, downloads.netty.io=Netty-Download})",
|
||||||
|
mapping.toString());
|
||||||
|
}
|
||||||
|
}
|
@ -82,6 +82,50 @@ public class StringUtilTest {
|
|||||||
assertEquals("bar:bar2", substringAfter("foo:bar:bar2", ':'));
|
assertEquals("bar:bar2", substringAfter("foo:bar:bar2", ':'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void commonSuffixOfLengthTest() {
|
||||||
|
// negative length suffixes are never common
|
||||||
|
checkNotCommonSuffix("abc", "abc", -1);
|
||||||
|
|
||||||
|
// null has no suffix
|
||||||
|
checkNotCommonSuffix("abc", null, 0);
|
||||||
|
checkNotCommonSuffix(null, null, 0);
|
||||||
|
|
||||||
|
// any non-null string has 0-length suffix
|
||||||
|
checkCommonSuffix("abc", "xx", 0);
|
||||||
|
|
||||||
|
checkCommonSuffix("abc", "abc", 0);
|
||||||
|
checkCommonSuffix("abc", "abc", 1);
|
||||||
|
checkCommonSuffix("abc", "abc", 2);
|
||||||
|
checkCommonSuffix("abc", "abc", 3);
|
||||||
|
checkNotCommonSuffix("abc", "abc", 4);
|
||||||
|
|
||||||
|
checkCommonSuffix("abcd", "cd", 1);
|
||||||
|
checkCommonSuffix("abcd", "cd", 2);
|
||||||
|
checkNotCommonSuffix("abcd", "cd", 3);
|
||||||
|
|
||||||
|
checkCommonSuffix("abcd", "axcd", 1);
|
||||||
|
checkCommonSuffix("abcd", "axcd", 2);
|
||||||
|
checkNotCommonSuffix("abcd", "axcd", 3);
|
||||||
|
|
||||||
|
checkNotCommonSuffix("abcx", "abcy", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkNotCommonSuffix(String s, String p, int len) {
|
||||||
|
assertFalse(checkCommonSuffixSymmetric(s, p, len));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkCommonSuffix(String s, String p, int len) {
|
||||||
|
assertTrue(checkCommonSuffixSymmetric(s, p, len));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean checkCommonSuffixSymmetric(String s, String p, int len) {
|
||||||
|
boolean sp = commonSuffixOfLength(s, p, len);
|
||||||
|
boolean ps = commonSuffixOfLength(p, s, len);
|
||||||
|
assertEquals(sp, ps);
|
||||||
|
return sp;
|
||||||
|
}
|
||||||
|
|
||||||
@Test (expected = NullPointerException.class)
|
@Test (expected = NullPointerException.class)
|
||||||
public void escapeCsvNull() {
|
public void escapeCsvNull() {
|
||||||
StringUtil.escapeCsv(null);
|
StringUtil.escapeCsv(null);
|
||||||
|
Loading…
Reference in New Issue
Block a user