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