diff --git a/common/src/main/java/io/netty/util/DomainMappingBuilder.java b/common/src/main/java/io/netty/util/DomainMappingBuilder.java index de7f36686c..e0dcf7dafb 100644 --- a/common/src/main/java/io/netty/util/DomainMappingBuilder.java +++ b/common/src/main/java/io/netty/util/DomainMappingBuilder.java @@ -16,22 +16,16 @@ package io.netty.util; -import java.util.Collections; -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 concrete type of value objects + * @deprecated Use {@link DomainNameMappingBuilder} instead. */ +@Deprecated public final class DomainMappingBuilder { - private final V defaultValue; - private final Map map; + private final DomainNameMappingBuilder builder; /** * Constructor with default initial capacity of the map holding the mappings @@ -40,7 +34,7 @@ public final class DomainMappingBuilder { * when nothing matches the input */ public DomainMappingBuilder(V defaultValue) { - this(4, defaultValue); + builder = new DomainNameMappingBuilder(defaultValue); } /** @@ -51,8 +45,7 @@ public final class DomainMappingBuilder { * when nothing matches the input */ public DomainMappingBuilder(int initialCapacity, V defaultValue) { - this.defaultValue = checkNotNull(defaultValue, "defaultValue"); - map = new LinkedHashMap(initialCapacity); + builder = new DomainNameMappingBuilder(initialCapacity, defaultValue); } /** @@ -68,7 +61,7 @@ public final class DomainMappingBuilder { * when the specified host name matches the specified input host name */ public DomainMappingBuilder add(String hostname, V output) { - map.put(checkNotNull(hostname, "hostname"), checkNotNull(output, "output")); + builder.add(hostname, output); return this; } @@ -79,126 +72,6 @@ public final class DomainMappingBuilder { * @return new {@link DomainNameMapping} instance */ public DomainNameMapping build() { - return new ImmutableDomainNameMapping(defaultValue, 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 concrete type of value objects - */ - private static final class ImmutableDomainNameMapping extends DomainNameMapping { - 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; - private final Map map; - - @SuppressWarnings("unchecked") - private ImmutableDomainNameMapping(V defaultValue, Map map) { - super(null, defaultValue); - - Set> mappings = map.entrySet(); - int numberOfMappings = mappings.size(); - domainNamePatterns = new String[numberOfMappings]; - values = (V[]) new Object[numberOfMappings]; - - final Map mapCopy = new LinkedHashMap(map.size()); - int index = 0; - for (Map.Entry mapping : mappings) { - final String hostname = normalizeHostname(mapping.getKey()); - final V value = mapping.getValue(); - domainNamePatterns[index] = hostname; - values[index] = value; - mapCopy.put(hostname, value); - ++index; - } - - this.map = Collections.unmodifiableMap(mapCopy); - } - - @Override - @Deprecated - public DomainNameMapping 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 Map asMap() { - return map; - } - - @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); - } + return builder.build(); } } diff --git a/common/src/main/java/io/netty/util/DomainNameMapping.java b/common/src/main/java/io/netty/util/DomainNameMapping.java index 610446563c..b15a0ae67f 100644 --- a/common/src/main/java/io/netty/util/DomainNameMapping.java +++ b/common/src/main/java/io/netty/util/DomainNameMapping.java @@ -23,7 +23,6 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; -import java.util.Set; import static io.netty.util.internal.ObjectUtil.checkNotNull; import static io.netty.util.internal.StringUtil.commonSuffixOfLength; @@ -39,13 +38,14 @@ public class DomainNameMapping implements Mapping { final V defaultValue; private final Map map; + private final Map unmodifiableMap; /** * Creates a default, order-sensitive mapping. If your hostnames are in conflict, the mapping * will choose the one you add first. * * @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 use {@link DomainNameMappingBuilder} to create and fill the mapping instead */ @Deprecated public DomainNameMapping(V defaultValue) { @@ -58,7 +58,7 @@ public class DomainNameMapping implements Mapping { * * @param initialCapacity initial capacity for the internal map * @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 use {@link DomainNameMappingBuilder} to create and fill the mapping instead */ @Deprecated public DomainNameMapping(int initialCapacity, V defaultValue) { @@ -68,6 +68,7 @@ public class DomainNameMapping implements Mapping { DomainNameMapping(Map map, V defaultValue) { this.defaultValue = checkNotNull(defaultValue, "defaultValue"); this.map = map; + unmodifiableMap = Collections.unmodifiableMap(map); } /** @@ -80,7 +81,7 @@ public class DomainNameMapping implements Mapping { * @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 * matches the specified input host name - * @deprecated use {@link DomainMappingBuilder} to create and fill the mapping instead + * @deprecated use {@link DomainNameMappingBuilder} to create and fill the mapping instead */ @Deprecated public DomainNameMapping add(String hostname, V output) { @@ -135,10 +136,10 @@ public class DomainNameMapping implements Mapping { } /** - * Returns a read-only {@link Set} of the domain mapping patterns and their associated value objects. + * Returns a read-only {@link Map} of the domain mapping patterns and their associated value objects. */ public Map asMap() { - return Collections.unmodifiableMap(map); + return unmodifiableMap; } @Override diff --git a/common/src/main/java/io/netty/util/DomainNameMappingBuilder.java b/common/src/main/java/io/netty/util/DomainNameMappingBuilder.java new file mode 100644 index 0000000000..4bf360c5fa --- /dev/null +++ b/common/src/main/java/io/netty/util/DomainNameMappingBuilder.java @@ -0,0 +1,204 @@ +/* + * Copyright 2016 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.Collections; +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 concrete type of value objects + */ +public final class DomainNameMappingBuilder { + + private final V defaultValue; + private final Map 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 DomainNameMappingBuilder(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 DomainNameMappingBuilder(int initialCapacity, V defaultValue) { + this.defaultValue = checkNotNull(defaultValue, "defaultValue"); + map = new LinkedHashMap(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. + *

+ * DNS wildcard is supported as hostname. + * For example, you can use {@code *.netty.io} to match {@code netty.io} and {@code downloads.netty.io}. + *

+ * + * @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 DomainNameMappingBuilder 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 build() { + return new ImmutableDomainNameMapping(defaultValue, 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 concrete type of value objects + */ + private static final class ImmutableDomainNameMapping extends DomainNameMapping { + 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; + private final Map map; + + @SuppressWarnings("unchecked") + private ImmutableDomainNameMapping(V defaultValue, Map map) { + super(null, defaultValue); + + Set> mappings = map.entrySet(); + int numberOfMappings = mappings.size(); + domainNamePatterns = new String[numberOfMappings]; + values = (V[]) new Object[numberOfMappings]; + + final Map mapCopy = new LinkedHashMap(map.size()); + int index = 0; + for (Map.Entry mapping : mappings) { + final String hostname = normalizeHostname(mapping.getKey()); + final V value = mapping.getValue(); + domainNamePatterns[index] = hostname; + values[index] = value; + mapCopy.put(hostname, value); + ++index; + } + + this.map = Collections.unmodifiableMap(mapCopy); + } + + @Override + @Deprecated + public DomainNameMapping 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 Map asMap() { + return map; + } + + @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); + } + } +} diff --git a/common/src/test/java/io/netty/util/DomainNameMappingTest.java b/common/src/test/java/io/netty/util/DomainNameMappingTest.java index 7fdbf96ba8..b8447964cd 100644 --- a/common/src/test/java/io/netty/util/DomainNameMappingTest.java +++ b/common/src/test/java/io/netty/util/DomainNameMappingTest.java @@ -107,22 +107,22 @@ public class DomainNameMappingTest { @Test(expected = NullPointerException.class) public void testNullDefaultValue() { - new DomainMappingBuilder(null); + new DomainNameMappingBuilder(null); } @Test(expected = NullPointerException.class) public void testNullDomainNamePatternsAreForbidden() { - new DomainMappingBuilder("NotFound").add(null, "Some value"); + new DomainNameMappingBuilder("NotFound").add(null, "Some value"); } @Test(expected = NullPointerException.class) public void testNullValuesAreForbidden() { - new DomainMappingBuilder("NotFound").add("Some key", null); + new DomainNameMappingBuilder("NotFound").add("Some key", null); } @Test public void testDefaultValue() { - DomainNameMapping mapping = new DomainMappingBuilder("NotFound") + DomainNameMapping mapping = new DomainNameMappingBuilder("NotFound") .add("*.netty.io", "Netty") .build(); @@ -131,7 +131,7 @@ public class DomainNameMappingTest { @Test public void testStrictEquality() { - DomainNameMapping mapping = new DomainMappingBuilder("NotFound") + DomainNameMapping mapping = new DomainNameMappingBuilder("NotFound") .add("netty.io", "Netty") .add("downloads.netty.io", "Netty-Downloads") .build(); @@ -144,7 +144,7 @@ public class DomainNameMappingTest { @Test public void testWildcardMatchesAnyPrefix() { - DomainNameMapping mapping = new DomainMappingBuilder("NotFound") + DomainNameMapping mapping = new DomainNameMappingBuilder("NotFound") .add("*.netty.io", "Netty") .build(); @@ -158,14 +158,14 @@ public class DomainNameMappingTest { @Test public void testFirstMatchWins() { assertEquals("Netty", - new DomainMappingBuilder("NotFound") + new DomainNameMappingBuilder("NotFound") .add("*.netty.io", "Netty") .add("downloads.netty.io", "Netty-Downloads") .build() .map("downloads.netty.io")); assertEquals("Netty-Downloads", - new DomainMappingBuilder("NotFound") + new DomainNameMappingBuilder("NotFound") .add("downloads.netty.io", "Netty-Downloads") .add("*.netty.io", "Netty") .build() @@ -174,7 +174,7 @@ public class DomainNameMappingTest { @Test public void testToString() { - DomainNameMapping mapping = new DomainMappingBuilder("NotFound") + DomainNameMapping mapping = new DomainNameMappingBuilder("NotFound") .add("*.netty.io", "Netty") .add("downloads.netty.io", "Netty-Download") .build(); @@ -199,7 +199,7 @@ public class DomainNameMappingTest { @Test public void testAsMapWithImmutableDomainNameMapping() { - DomainNameMapping mapping = new DomainMappingBuilder("NotFound") + DomainNameMapping mapping = new DomainNameMappingBuilder("NotFound") .add("netty.io", "Netty") .add("downloads.netty.io", "Netty-Downloads") .build(); diff --git a/handler/src/test/java/io/netty/handler/ssl/SniHandlerTest.java b/handler/src/test/java/io/netty/handler/ssl/SniHandlerTest.java index 1f46cdc868..8170376f38 100644 --- a/handler/src/test/java/io/netty/handler/ssl/SniHandlerTest.java +++ b/handler/src/test/java/io/netty/handler/ssl/SniHandlerTest.java @@ -18,7 +18,6 @@ package io.netty.handler.ssl; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; -import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -31,18 +30,17 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.DecoderException; -import io.netty.util.DomainMappingBuilder; import io.netty.util.DomainNameMapping; +import io.netty.util.DomainNameMappingBuilder; import io.netty.util.ReferenceCountUtil; import org.junit.Test; +import javax.xml.bind.DatatypeConverter; import java.io.File; import java.net.InetSocketAddress; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import javax.xml.bind.DatatypeConverter; - import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; @@ -80,15 +78,14 @@ public class SniHandlerTest { SslContext leanContext = makeSslContext(); SslContext leanContext2 = makeSslContext(); - DomainNameMapping mapping = new DomainNameMapping(nettyContext); - mapping.add("*.netty.io", nettyContext); - - // input with custom cases - mapping.add("*.LEANCLOUD.CN", leanContext); - - // a hostname conflict with previous one, since we are using order-sensitive config, the engine won't - // be used with the handler. - mapping.add("chat4.leancloud.cn", leanContext2); + DomainNameMapping mapping = new DomainNameMappingBuilder(nettyContext) + .add("*.netty.io", nettyContext) + // input with custom cases + .add("*.LEANCLOUD.CN", leanContext) + // a hostname conflict with previous one, since we are using order-sensitive config, the engine won't + // be used with the handler. + .add("chat4.leancloud.cn", leanContext2) + .build(); SniHandler handler = new SniHandler(mapping); EmbeddedChannel ch = new EmbeddedChannel(handler); @@ -132,15 +129,14 @@ public class SniHandlerTest { SslContext leanContext = makeSslContext(); SslContext leanContext2 = makeSslContext(); - DomainNameMapping mapping = new DomainNameMapping(nettyContext); - mapping.add("*.netty.io", nettyContext); - - // input with custom cases - mapping.add("*.LEANCLOUD.CN", leanContext); - - // a hostname conflict with previous one, since we are using order-sensitive config, the engine won't - // be used with the handler. - mapping.add("chat4.leancloud.cn", leanContext2); + DomainNameMapping mapping = new DomainNameMappingBuilder(nettyContext) + .add("*.netty.io", nettyContext) + // input with custom cases + .add("*.LEANCLOUD.CN", leanContext) + // a hostname conflict with previous one, since we are using order-sensitive config, the engine won't + // be used with the handler. + .add("chat4.leancloud.cn", leanContext2) + .build(); SniHandler handler = new SniHandler(mapping); EmbeddedChannel ch = new EmbeddedChannel(handler); @@ -168,7 +164,7 @@ public class SniHandlerTest { final CountDownLatch serverApnDoneLatch = new CountDownLatch(1); final CountDownLatch clientApnDoneLatch = new CountDownLatch(1); - final DomainNameMapping mapping = new DomainMappingBuilder(nettyContext) + final DomainNameMapping mapping = new DomainNameMappingBuilder(nettyContext) .add("*.netty.io", nettyContext) .add("sni.fake.site", sniContext).build(); final SniHandler handler = new SniHandler(mapping);