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 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 }