View Javadoc

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  @SuppressWarnings("serial")
40  public class IPAddressMap<TYPE> extends HashMap<String, TYPE>
41  {
42      private final HashMap<String,IPAddrPattern> _patterns = new HashMap<String,IPAddrPattern>();
43  
44      /* --------------------------------------------------------------- */
45      /** Construct empty IPAddressMap.
46       */
47      public IPAddressMap()
48      {
49          super(11);
50      }
51     
52      /* --------------------------------------------------------------- */
53      /** Construct empty IPAddressMap.
54       * 
55       * @param capacity initial capacity
56       */
57      public IPAddressMap(int capacity)
58      {
59          super (capacity);
60      }
61  
62      /* ------------------------------------------------------------ */
63      /**
64       * Insert a new internet address into map
65       * 
66       * @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
67       */
68      @Override
69      public TYPE put(String addrSpec, TYPE object)
70          throws IllegalArgumentException
71      {
72          if (addrSpec == null || addrSpec.trim().length() == 0)
73              throw new IllegalArgumentException("Invalid IP address pattern: "+addrSpec);
74          
75          String spec = addrSpec.trim();
76          if (_patterns.get(spec) == null)
77              _patterns.put(spec,new IPAddrPattern(spec));
78          
79          return super.put(spec, object);
80      }
81      
82      /* ------------------------------------------------------------ */
83      /**
84       * Retrieve the object mapped to the specified internet address literal
85       * 
86       * @see java.util.HashMap#get(java.lang.Object)
87       */
88      @Override
89      public TYPE get(Object key)
90      {
91          return super.get(key);
92      }
93      
94      /* ------------------------------------------------------------ */
95      /**
96       * Retrieve the first object that is associated with the specified 
97       * internet address by taking into account the wildcard specifications.
98       * 
99       * @param addr internet address
100      * @return associated object
101      */
102     public TYPE match(String addr)
103     {
104         Map.Entry<String, TYPE> entry = getMatch(addr);
105         return entry==null ? null : entry.getValue();
106     }
107     
108     /* ------------------------------------------------------------ */
109     /**
110      * Retrieve the first map entry that is associated with the specified 
111      * internet address by taking into account the wildcard specifications.
112      * 
113      * @param addr internet address
114      * @return map entry associated
115      */
116     public Map.Entry<String, TYPE> getMatch(String addr)
117     {
118         if (addr != null)
119         {
120             for(Map.Entry<String, TYPE> entry: super.entrySet())
121             {
122                 if (_patterns.get(entry.getKey()).match(addr))
123                 {
124                     return entry;
125                 }
126             }
127         }
128         return null;
129     }
130     
131     /* ------------------------------------------------------------ */
132     /**
133      * Retrieve a lazy list of map entries associated with specified
134      * internet address by taking into account the wildcard specifications.
135      * 
136      * @param addr  internet address
137      * @return lazy list of map entries
138      */
139     public Object getLazyMatches(String addr)
140     {
141         if (addr == null)
142             return LazyList.getList(super.entrySet());
143         
144         Object entries = null;
145         for(Map.Entry<String, TYPE> entry: super.entrySet())
146         {
147             if (_patterns.get(entry.getKey()).match(addr))
148             {
149                 entries = LazyList.add(entries,entry);
150             }
151         }
152         return entries;        
153     }
154     
155     /* ------------------------------------------------------------ */
156     /**
157      * IPAddrPattern
158      * 
159      * Represents internet address wildcard. 
160      * Matches the wildcard to provided internet address.
161      */
162     private static class IPAddrPattern
163     {
164         private final OctetPattern[] _octets = new OctetPattern[4];
165         /* ------------------------------------------------------------ */
166         /**
167          * Create new IPAddrPattern
168          * 
169          * @param value internet address wildcard specification
170          * @throws IllegalArgumentException if wildcard specification is invalid
171          */
172         public IPAddrPattern(String value)
173             throws IllegalArgumentException
174         {
175             if (value == null || value.trim().length() == 0)
176                 throw new IllegalArgumentException("Invalid IP address pattern: "+value);
177                 
178             try
179             {
180                 StringTokenizer parts = new StringTokenizer(value, ".");
181                 
182                 String part;
183                 for (int idx=0; idx<4; idx++)
184                 {
185                     part = parts.hasMoreTokens() ? parts.nextToken().trim() : "0-255";
186                     
187                     int len = part.length();
188                     if (len == 0 && parts.hasMoreTokens())
189                         throw new IllegalArgumentException("Invalid IP address pattern: "+value);
190                     
191                     _octets[idx] = new OctetPattern(len==0 ? "0-255" : part);
192                 }
193             }
194             catch (IllegalArgumentException ex)
195             {
196                 throw new IllegalArgumentException("Invalid IP address pattern: "+value, ex);
197             }
198         }
199         
200         /* ------------------------------------------------------------ */
201         /**
202          * Match the specified internet address against the wildcard
203          * 
204          * @param value internet address
205          * @return true if specified internet address matches wildcard specification
206          * 
207          * @throws IllegalArgumentException if specified internet address is invalid
208          */
209         public boolean match(String value)
210             throws IllegalArgumentException
211         {
212             if (value == null || value.trim().length() == 0)
213                 throw new IllegalArgumentException("Invalid IP address: "+value);
214             
215             try
216             {
217                 StringTokenizer parts = new StringTokenizer(value, ".");
218                 
219                 boolean result = true;
220                 for (int idx=0; idx<4; idx++)
221                 {
222                     if (!parts.hasMoreTokens())
223                         throw new IllegalArgumentException("Invalid IP address: "+value);
224                         
225                     if (!(result &= _octets[idx].match(parts.nextToken())))
226                         break;
227                 }
228                 return result;
229             }
230             catch (IllegalArgumentException ex)
231             {
232                 throw new IllegalArgumentException("Invalid IP address: "+value, ex);
233             }
234         }
235     }
236         
237     /* ------------------------------------------------------------ */
238     /**
239      * OctetPattern
240      * 
241      * Represents a single octet wildcard.
242      * Matches the wildcard to the specified octet value.
243      */
244     private static class OctetPattern extends BitSet
245     {
246         private final BitSet _mask = new BitSet(256);
247         
248         /* ------------------------------------------------------------ */
249         /**
250          * Create new OctetPattern
251          * 
252          * @param octetSpec octet wildcard specification
253          * @throws IllegalArgumentException if wildcard specification is invalid
254          */
255         public OctetPattern(String octetSpec)
256             throws IllegalArgumentException
257         {
258             try
259             {
260                 if (octetSpec != null)
261                 {
262                     String spec = octetSpec.trim();
263                     if(spec.length() == 0)
264                     {
265                         _mask.set(0,255);
266                     }
267                     else
268                     {
269                         StringTokenizer parts = new StringTokenizer(spec,",");
270                         while (parts.hasMoreTokens())
271                         {
272                             String part = parts.nextToken().trim();
273                             if (part.length() > 0)
274                             {
275                                 if (part.indexOf('-') < 0)
276                                 {
277                                     Integer value = Integer.valueOf(part);
278                                     _mask.set(value);
279                                 }
280                                 else
281                                 {
282                                     int low = 0, high = 255;
283                                     
284                                     String[] bounds = part.split("-",-2);
285                                     if (bounds.length != 2)
286                                     {
287                                         throw new IllegalArgumentException("Invalid octet spec: "+octetSpec);
288                                     }
289                                     
290                                     if (bounds[0].length() > 0)
291                                     {
292                                         low = Integer.parseInt(bounds[0]);
293                                     }
294                                     if (bounds[1].length() > 0)
295                                     {
296                                         high = Integer.parseInt(bounds[1]);
297                                     }
298                                     
299                                     if (low > high)
300                                     {
301                                         throw new IllegalArgumentException("Invalid octet spec: "+octetSpec);
302                                     }
303                                     
304                                     _mask.set(low, high+1);
305                                 }
306                             }
307                         }
308                     }
309                 }
310             }
311             catch (NumberFormatException ex)
312             {
313                 throw new IllegalArgumentException("Invalid octet spec: "+octetSpec, ex);
314             }
315         }
316         
317         /* ------------------------------------------------------------ */
318         /**
319          * Match specified octet value against the wildcard
320          * 
321          * @param value octet value
322          * @return true if specified octet value matches the wildcard
323          * @throws IllegalArgumentException if specified octet value is invalid
324          */
325         public boolean match(String value)
326             throws IllegalArgumentException
327         {
328             if (value == null || value.trim().length() == 0)
329                 throw new IllegalArgumentException("Invalid octet: "+value);
330 
331             try
332             {
333                 int number = Integer.parseInt(value);
334                 return match(number);
335             }
336             catch (NumberFormatException ex)
337             {
338                 throw new IllegalArgumentException("Invalid octet: "+value);
339             }
340         }
341         
342         /* ------------------------------------------------------------ */
343         /**
344          * Match specified octet value against the wildcard
345          * 
346          * @param number octet value
347          * @return true if specified octet value matches the wildcard
348          * @throws IllegalArgumentException if specified octet value is invalid
349          */
350         public boolean match(int number)
351             throws IllegalArgumentException
352         {
353             if (number < 0 || number > 255)
354                 throw new IllegalArgumentException("Invalid octet: "+number);
355             
356             return _mask.get(number);
357         }
358     }   
359 }