1 // ========================================================================
2 // Copyright (c) 2010 Mort Bay Consulting Pty. Ltd.
3 // ------------------------------------------------------------------------
4 // All rights reserved. This program and the accompanying materials
5 // are made available under the terms of the Eclipse Public License v1.0
6 // and Apache License v2.0 which accompanies this distribution.
7 // The Eclipse Public License is available at
8 // http://www.eclipse.org/legal/epl-v10.html
9 // The Apache License v2.0 is available at
10 // http://www.opensource.org/licenses/apache2.0.php
11 // You may elect to redistribute this code under either of these licenses.
12 // ========================================================================
13
14 package org.eclipse.jetty.util;
15
16 import java.util.BitSet;
17 import java.util.HashMap;
18 import java.util.Map;
19 import java.util.StringTokenizer;
20
21
22 /* ------------------------------------------------------------ */
23 /**
24 * Internet address map to object
25 * <p>
26 * Internet addresses may be specified as absolute address or as a combination of
27 * four octet wildcard specifications (a.b.c.d) that are defined as follows.
28 * </p>
29 * <pre>
30 * nnn - an absolute value (0-255)
31 * mmm-nnn - an inclusive range of absolute values,
32 * with following shorthand notations:
33 * nnn- => nnn-255
34 * -nnn => 0-nnn
35 * - => 0-255
36 * a,b,... - a list of wildcard specifications
37 * </pre>
38 */
39 public class IPAddressMap<TYPE> extends HashMap<String, TYPE>
40 {
41 private final HashMap<String,IPAddrPattern> _patterns = new HashMap<String,IPAddrPattern>();
42
43 /* --------------------------------------------------------------- */
44 /** Construct empty AddrPatternMap.
45 */
46 public IPAddressMap()
47 {
48 super(11);
49 }
50
51 /* --------------------------------------------------------------- */
52 /** Construct empty AddrPatternMap.
53 *
54 * @param capacity initial capacity
55 */
56 public IPAddressMap(int capacity)
57 {
58 super (capacity);
59 }
60
61 /* ------------------------------------------------------------ */
62 /**
63 * Insert a new internet address into map
64 *
65 * @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
66 */
67 @Override
68 public TYPE put(String addrSpec, TYPE object)
69 throws IllegalArgumentException
70 {
71 if (addrSpec == null || addrSpec.trim().length() == 0)
72 throw new IllegalArgumentException("Invalid IP address pattern: "+addrSpec);
73
74 String spec = addrSpec.trim();
75 if (_patterns.get(spec) == null)
76 _patterns.put(spec,new IPAddrPattern(spec));
77
78 return super.put(spec, object);
79 }
80
81 /* ------------------------------------------------------------ */
82 /**
83 * Retrieve the object mapped to the specified internet address literal
84 *
85 * @see java.util.HashMap#get(java.lang.Object)
86 */
87 /* ------------------------------------------------------------ */
88 /**
89 * @see java.util.HashMap#get(java.lang.Object)
90 */
91 @Override
92 public TYPE get(Object key)
93 {
94 return super.get(key);
95 }
96
97 /* ------------------------------------------------------------ */
98 /**
99 * Retrieve the first object that is associated with the specified
100 * internet address by taking into account the wildcard specifications.
101 *
102 * @param addr internet address
103 * @return associated object
104 */
105 public TYPE match(String addr)
106 {
107 Map.Entry<String, TYPE> entry = getMatch(addr);
108 return entry==null ? null : entry.getValue();
109 }
110
111 /* ------------------------------------------------------------ */
112 /**
113 * Retrieve the first map entry that is associated with the specified
114 * internet address by taking into account the wildcard specifications.
115 *
116 * @param addr internet address
117 * @return map entry associated
118 */
119 public Map.Entry<String, TYPE> getMatch(String addr)
120 {
121 if (addr != null)
122 {
123 for(Map.Entry<String, TYPE> entry: super.entrySet())
124 {
125 if (_patterns.get(entry.getKey()).match(addr))
126 {
127 return entry;
128 }
129 }
130 }
131 return null;
132 }
133
134 /* ------------------------------------------------------------ */
135 /**
136 * Retrieve a lazy list of map entries associated with specified
137 * internet address by taking into account the wildcard specifications.
138 *
139 * @param addr internet address
140 * @return lazy list of map entries
141 */
142 public Object getLazyMatches(String addr)
143 {
144 if (addr == null)
145 return LazyList.getList(super.entrySet());
146
147 Object entries = null;
148 for(Map.Entry<String, TYPE> entry: super.entrySet())
149 {
150 if (_patterns.get(entry.getKey()).match(addr))
151 {
152 entries = LazyList.add(entries,entry);
153 }
154 }
155 return entries;
156 }
157
158 /* ------------------------------------------------------------ */
159 /**
160 * IPAddrPattern
161 *
162 * Represents internet address wildcard.
163 * Matches the wildcard to provided internet address.
164 */
165 private static class IPAddrPattern
166 {
167 private final OctetPattern[] _octets = new OctetPattern[4];
168 /* ------------------------------------------------------------ */
169 /**
170 * Create new IPAddrPattern
171 *
172 * @param value internet address wildcard specification
173 * @throws IllegalArgumentException if wildcard specification is invalid
174 */
175 public IPAddrPattern(String value)
176 throws IllegalArgumentException
177 {
178 if (value == null || value.trim().length() == 0)
179 throw new IllegalArgumentException("Invalid IP address pattern: "+value);
180
181 try
182 {
183 StringTokenizer parts = new StringTokenizer(value, ".");
184
185 String part;
186 for (int idx=0; idx<4; idx++)
187 {
188 part = parts.hasMoreTokens() ? parts.nextToken().trim() : "0-255";
189
190 int len = part.length();
191 if (len == 0 && parts.hasMoreTokens())
192 throw new IllegalArgumentException("Invalid IP address pattern: "+value);
193
194 _octets[idx] = new OctetPattern(len==0 ? "0-255" : part);
195 }
196 }
197 catch (IllegalArgumentException ex)
198 {
199 throw new IllegalArgumentException("Invalid IP address pattern: "+value, ex);
200 }
201 }
202
203 /* ------------------------------------------------------------ */
204 /**
205 * Match the specified internet address against the wildcard
206 *
207 * @param value internet address
208 * @return true if specified internet address matches wildcard specification
209 *
210 * @throws IllegalArgumentException if specified internet address is invalid
211 */
212 public boolean match(String value)
213 throws IllegalArgumentException
214 {
215 if (value == null || value.trim().length() == 0)
216 throw new IllegalArgumentException("Invalid IP address: "+value);
217
218 try
219 {
220 StringTokenizer parts = new StringTokenizer(value, ".");
221
222 boolean result = true;
223 for (int idx=0; idx<4; idx++)
224 {
225 if (!parts.hasMoreTokens())
226 throw new IllegalArgumentException("Invalid IP address: "+value);
227
228 if (!(result &= _octets[idx].match(parts.nextToken())))
229 break;
230 }
231 return result;
232 }
233 catch (IllegalArgumentException ex)
234 {
235 throw new IllegalArgumentException("Invalid IP address: "+value, ex);
236 }
237 }
238 }
239
240 /* ------------------------------------------------------------ */
241 /**
242 * OctetPattern
243 *
244 * Represents a single octet wildcard.
245 * Matches the wildcard to the specified octet value.
246 */
247 private static class OctetPattern extends BitSet
248 {
249 private final BitSet _mask = new BitSet(256);
250
251 /* ------------------------------------------------------------ */
252 /**
253 * Create new OctetPattern
254 *
255 * @param octetSpec octet wildcard specification
256 * @throws IllegalArgumentException if wildcard specification is invalid
257 */
258 public OctetPattern(String octetSpec)
259 throws IllegalArgumentException
260 {
261 try
262 {
263 if (octetSpec != null)
264 {
265 String spec = octetSpec.trim();
266 if(spec.length() == 0)
267 {
268 _mask.set(0,255);
269 }
270 else
271 {
272 StringTokenizer parts = new StringTokenizer(spec,",");
273 while (parts.hasMoreTokens())
274 {
275 String part = parts.nextToken().trim();
276 if (part.length() > 0)
277 {
278 if (part.indexOf('-') < 0)
279 {
280 Integer value = Integer.valueOf(part);
281 _mask.set(value);
282 }
283 else
284 {
285 int low = 0, high = 255;
286
287 String[] bounds = part.split("-",-2);
288 if (bounds.length != 2)
289 {
290 throw new IllegalArgumentException("Invalid octet spec: "+octetSpec);
291 }
292
293 if (bounds[0].length() > 0)
294 {
295 low = Integer.parseInt(bounds[0]);
296 }
297 if (bounds[1].length() > 0)
298 {
299 high = Integer.parseInt(bounds[1]);
300 }
301
302 if (low > high)
303 {
304 throw new IllegalArgumentException("Invalid octet spec: "+octetSpec);
305 }
306
307 _mask.set(low, high+1);
308 }
309 }
310 }
311 }
312 }
313 }
314 catch (NumberFormatException ex)
315 {
316 throw new IllegalArgumentException("Invalid octet spec: "+octetSpec, ex);
317 }
318 }
319
320 /* ------------------------------------------------------------ */
321 /**
322 * Match specified octet value against the wildcard
323 *
324 * @param value octet value
325 * @return true if specified octet value matches the wildcard
326 * @throws IllegalArgumentException if specified octet value is invalid
327 */
328 public boolean match(String value)
329 throws IllegalArgumentException
330 {
331 if (value == null || value.trim().length() == 0)
332 throw new IllegalArgumentException("Invalid octet: "+value);
333
334 try
335 {
336 int number = Integer.parseInt(value);
337 return match(number);
338 }
339 catch (NumberFormatException ex)
340 {
341 throw new IllegalArgumentException("Invalid octet: "+value);
342 }
343 }
344
345 /* ------------------------------------------------------------ */
346 /**
347 * Match specified octet value against the wildcard
348 *
349 * @param value octet value
350 * @return true if specified octet value matches the wildcard
351 * @throws IllegalArgumentException if specified octet value is invalid
352 */
353 public boolean match(int number)
354 throws IllegalArgumentException
355 {
356 if (number < 0 || number > 255)
357 throw new IllegalArgumentException("Invalid octet: "+number);
358
359 return _mask.get(number);
360 }
361 }
362 }