View Javadoc
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.netty5.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 java.util.Objects.requireNonNull;
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 = requireNonNull(defaultValue, "defaultValue");
57          map = new LinkedHashMap<>(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(requireNonNull(hostname, "hostname"), requireNonNull(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<>(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<>(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 }