1 /*
2 * Copyright 2016 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16
17 package io.netty.util;
18
19 import java.util.Collections;
20 import java.util.LinkedHashMap;
21 import java.util.Map;
22 import java.util.Set;
23
24 import static io.netty.util.internal.ObjectUtil.checkNotNull;
25
26 /**
27 * Builder for immutable {@link DomainNameMapping} instances.
28 *
29 * @param <V> concrete type of value objects
30 * @deprecated Use {@link DomainWildcardMappingBuilder}
31 */
32 @Deprecated
33 public final class DomainNameMappingBuilder<V> {
34
35 private final V defaultValue;
36 private final Map<String, V> map;
37
38 /**
39 * Constructor with default initial capacity of the map holding the mappings
40 *
41 * @param defaultValue the default value for {@link DomainNameMapping#map(String)} to return
42 * when nothing matches the input
43 */
44 public DomainNameMappingBuilder(V defaultValue) {
45 this(4, defaultValue);
46 }
47
48 /**
49 * Constructor with initial capacity of the map holding the mappings
50 *
51 * @param initialCapacity initial capacity for the internal map
52 * @param defaultValue the default value for {@link DomainNameMapping#map(String)} to return
53 * when nothing matches the input
54 */
55 public DomainNameMappingBuilder(int initialCapacity, V defaultValue) {
56 this.defaultValue = checkNotNull(defaultValue, "defaultValue");
57 map = new LinkedHashMap<String, V>(initialCapacity);
58 }
59
60 /**
61 * Adds a mapping that maps the specified (optionally wildcard) host name to the specified output value.
62 * Null values are forbidden for both hostnames and values.
63 * <p>
64 * <a href="https://en.wikipedia.org/wiki/Wildcard_DNS_record">DNS wildcard</a> is supported as hostname.
65 * For example, you can use {@code *.netty.io} to match {@code netty.io} and {@code downloads.netty.io}.
66 * </p>
67 *
68 * @param hostname the host name (optionally wildcard)
69 * @param output the output value that will be returned by {@link DomainNameMapping#map(String)}
70 * when the specified host name matches the specified input host name
71 */
72 public DomainNameMappingBuilder<V> add(String hostname, V output) {
73 map.put(checkNotNull(hostname, "hostname"), checkNotNull(output, "output"));
74 return this;
75 }
76
77 /**
78 * Creates a new instance of immutable {@link DomainNameMapping}
79 * Attempts to add new mappings to the result object will cause {@link UnsupportedOperationException} to be thrown
80 *
81 * @return new {@link DomainNameMapping} instance
82 */
83 public DomainNameMapping<V> build() {
84 return new ImmutableDomainNameMapping<V>(defaultValue, map);
85 }
86
87 /**
88 * Immutable mapping from domain name pattern to its associated value object.
89 * Mapping is represented by two arrays: keys and values. Key domainNamePatterns[i] is associated with values[i].
90 *
91 * @param <V> concrete type of value objects
92 */
93 private static final class ImmutableDomainNameMapping<V> extends DomainNameMapping<V> {
94 private static final String REPR_HEADER = "ImmutableDomainNameMapping(default: ";
95 private static final String REPR_MAP_OPENING = ", map: {";
96 private static final String REPR_MAP_CLOSING = "})";
97 private static final int REPR_CONST_PART_LENGTH =
98 REPR_HEADER.length() + REPR_MAP_OPENING.length() + REPR_MAP_CLOSING.length();
99
100 private final String[] domainNamePatterns;
101 private final V[] values;
102 private final Map<String, V> map;
103
104 @SuppressWarnings("unchecked")
105 private ImmutableDomainNameMapping(V defaultValue, Map<String, V> map) {
106 super(null, defaultValue);
107
108 Set<Map.Entry<String, V>> mappings = map.entrySet();
109 int numberOfMappings = mappings.size();
110 domainNamePatterns = new String[numberOfMappings];
111 values = (V[]) new Object[numberOfMappings];
112
113 final Map<String, V> mapCopy = new LinkedHashMap<String, V>(map.size());
114 int index = 0;
115 for (Map.Entry<String, V> mapping : mappings) {
116 final String hostname = normalizeHostname(mapping.getKey());
117 final V value = mapping.getValue();
118 domainNamePatterns[index] = hostname;
119 values[index] = value;
120 mapCopy.put(hostname, value);
121 ++index;
122 }
123
124 this.map = Collections.unmodifiableMap(mapCopy);
125 }
126
127 @Override
128 @Deprecated
129 public DomainNameMapping<V> add(String hostname, V output) {
130 throw new UnsupportedOperationException(
131 "Immutable DomainNameMapping does not support modification after initial creation");
132 }
133
134 @Override
135 public V map(String hostname) {
136 if (hostname != null) {
137 hostname = normalizeHostname(hostname);
138
139 int length = domainNamePatterns.length;
140 for (int index = 0; index < length; ++index) {
141 if (matches(domainNamePatterns[index], hostname)) {
142 return values[index];
143 }
144 }
145 }
146
147 return defaultValue;
148 }
149
150 @Override
151 public Map<String, V> asMap() {
152 return map;
153 }
154
155 @Override
156 public String toString() {
157 String defaultValueStr = defaultValue.toString();
158
159 int numberOfMappings = domainNamePatterns.length;
160 if (numberOfMappings == 0) {
161 return REPR_HEADER + defaultValueStr + REPR_MAP_OPENING + REPR_MAP_CLOSING;
162 }
163
164 String pattern0 = domainNamePatterns[0];
165 String value0 = values[0].toString();
166 int oneMappingLength = pattern0.length() + value0.length() + 3; // 2 for separator ", " and 1 for '='
167 int estimatedBufferSize = estimateBufferSize(defaultValueStr.length(), numberOfMappings, oneMappingLength);
168
169 StringBuilder sb = new StringBuilder(estimatedBufferSize)
170 .append(REPR_HEADER).append(defaultValueStr).append(REPR_MAP_OPENING);
171
172 appendMapping(sb, pattern0, value0);
173 for (int index = 1; index < numberOfMappings; ++index) {
174 sb.append(", ");
175 appendMapping(sb, index);
176 }
177
178 return sb.append(REPR_MAP_CLOSING).toString();
179 }
180
181 /**
182 * Estimates the length of string representation of the given instance:
183 * est = lengthOfConstantComponents + defaultValueLength + (estimatedMappingLength * numOfMappings) * 1.10
184 *
185 * @param defaultValueLength length of string representation of {@link #defaultValue}
186 * @param numberOfMappings number of mappings the given instance holds,
187 * e.g. {@link #domainNamePatterns#length}
188 * @param estimatedMappingLength estimated size taken by one mapping
189 * @return estimated length of string returned by {@link #toString()}
190 */
191 private static int estimateBufferSize(int defaultValueLength,
192 int numberOfMappings,
193 int estimatedMappingLength) {
194 return REPR_CONST_PART_LENGTH + defaultValueLength
195 + (int) (estimatedMappingLength * numberOfMappings * 1.10);
196 }
197
198 private StringBuilder appendMapping(StringBuilder sb, int mappingIndex) {
199 return appendMapping(sb, domainNamePatterns[mappingIndex], values[mappingIndex].toString());
200 }
201
202 private static StringBuilder appendMapping(StringBuilder sb, String domainNamePattern, String value) {
203 return sb.append(domainNamePattern).append('=').append(value);
204 }
205 }
206 }