View Javadoc

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