+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Internet address map to object
+ * <p>
+ * Internet addresses may be specified as absolute address or as a combination of
+ * four octet wildcard specifications (a.b.c.d) that are defined as follows.
+ * </p>
+ * <pre>
+ * nnn - an absolute value (0-255)
+ * mmm-nnn - an inclusive range of absolute values,
+ * with following shorthand notations:
+ * nnn- => nnn-255
+ * -nnn => 0-nnn
+ * - => 0-255
+ * a,b,... - a list of wildcard specifications
+ * </pre>
+ */
+@SuppressWarnings("serial")
+public class IPAddressMap<TYPE> extends HashMap<String, TYPE>
+{
+ private final HashMap<String,IPAddrPattern> _patterns = new HashMap<String,IPAddrPattern>();
+
+ /* --------------------------------------------------------------- */
+ /** Construct empty IPAddressMap.
+ */
+ public IPAddressMap()
+ {
+ super(11);
+ }
+
+ /* --------------------------------------------------------------- */
+ /** Construct empty IPAddressMap.
+ *
+ * @param capacity initial capacity
+ */
+ public IPAddressMap(int capacity)
+ {
+ super (capacity);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Insert a new internet address into map
+ *
+ * @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
+ */
+ @Override
+ public TYPE put(String addrSpec, TYPE object)
+ throws IllegalArgumentException
+ {
+ if (addrSpec == null || addrSpec.trim().length() == 0)
+ throw new IllegalArgumentException("Invalid IP address pattern: "+addrSpec);
+
+ String spec = addrSpec.trim();
+ if (_patterns.get(spec) == null)
+ _patterns.put(spec,new IPAddrPattern(spec));
+
+ return super.put(spec, object);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve the object mapped to the specified internet address literal
+ *
+ * @see java.util.HashMap#get(java.lang.Object)
+ */
+ @Override
+ public TYPE get(Object key)
+ {
+ return super.get(key);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve the first object that is associated with the specified
+ * internet address by taking into account the wildcard specifications.
+ *
+ * @param addr internet address
+ * @return associated object
+ */
+ public TYPE match(String addr)
+ {
+ Map.Entry<String, TYPE> entry = getMatch(addr);
+ return entry==null ? null : entry.getValue();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve the first map entry that is associated with the specified
+ * internet address by taking into account the wildcard specifications.
+ *
+ * @param addr internet address
+ * @return map entry associated
+ */
+ public Map.Entry<String, TYPE> getMatch(String addr)
+ {
+ if (addr != null)
+ {
+ for(Map.Entry<String, TYPE> entry: super.entrySet())
+ {
+ if (_patterns.get(entry.getKey()).match(addr))
+ {
+ return entry;
+ }
+ }
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve a lazy list of map entries associated with specified
+ * internet address by taking into account the wildcard specifications.
+ *
+ * @param addr internet address
+ * @return lazy list of map entries
+ */
+ public Object getLazyMatches(String addr)
+ {
+ if (addr == null)
+ return LazyList.getList(super.entrySet());
+
+ Object entries = null;
+ for(Map.Entry<String, TYPE> entry: super.entrySet())
+ {
+ if (_patterns.get(entry.getKey()).match(addr))
+ {
+ entries = LazyList.add(entries,entry);
+ }
+ }
+ return entries;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * IPAddrPattern
+ *
+ * Represents internet address wildcard.
+ * Matches the wildcard to provided internet address.
+ */
+ private static class IPAddrPattern
+ {
+ private final OctetPattern[] _octets = new OctetPattern[4];
+ /* ------------------------------------------------------------ */
+ /**
+ * Create new IPAddrPattern
+ *
+ * @param value internet address wildcard specification
+ * @throws IllegalArgumentException if wildcard specification is invalid
+ */
+ public IPAddrPattern(String value)
+ throws IllegalArgumentException
+ {
+ if (value == null || value.trim().length() == 0)
+ throw new IllegalArgumentException("Invalid IP address pattern: "+value);
+
+ try
+ {
+ StringTokenizer parts = new StringTokenizer(value, ".");
+
+ String part;
+ for (int idx=0; idx<4; idx++)
+ {
+ part = parts.hasMoreTokens() ? parts.nextToken().trim() : "0-255";
+
+ int len = part.length();
+ if (len == 0 && parts.hasMoreTokens())
+ throw new IllegalArgumentException("Invalid IP address pattern: "+value);
+
+ _octets[idx] = new OctetPattern(len==0 ? "0-255" : part);
+ }
+ }
+ catch (IllegalArgumentException ex)
+ {
+ throw new IllegalArgumentException("Invalid IP address pattern: "+value, ex);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Match the specified internet address against the wildcard
+ *
+ * @param value internet address
+ * @return true if specified internet address matches wildcard specification
+ *
+ * @throws IllegalArgumentException if specified internet address is invalid
+ */
+ public boolean match(String value)
+ throws IllegalArgumentException
+ {
+ if (value == null || value.trim().length() == 0)
+ throw new IllegalArgumentException("Invalid IP address: "+value);
+
+ try
+ {
+ StringTokenizer parts = new StringTokenizer(value, ".");
+
+ boolean result = true;
+ for (int idx=0; idx<4; idx++)
+ {
+ if (!parts.hasMoreTokens())
+ throw new IllegalArgumentException("Invalid IP address: "+value);
+
+ if (!(result &= _octets[idx].match(parts.nextToken())))
+ break;
+ }
+ return result;
+ }
+ catch (IllegalArgumentException ex)
+ {
+ throw new IllegalArgumentException("Invalid IP address: "+value, ex);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * OctetPattern
+ *
+ * Represents a single octet wildcard.
+ * Matches the wildcard to the specified octet value.
+ */
+ private static class OctetPattern extends BitSet
+ {
+ private final BitSet _mask = new BitSet(256);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create new OctetPattern
+ *
+ * @param octetSpec octet wildcard specification
+ * @throws IllegalArgumentException if wildcard specification is invalid
+ */
+ public OctetPattern(String octetSpec)
+ throws IllegalArgumentException
+ {
+ try
+ {
+ if (octetSpec != null)
+ {
+ String spec = octetSpec.trim();
+ if(spec.length() == 0)
+ {
+ _mask.set(0,255);
+ }
+ else
+ {
+ StringTokenizer parts = new StringTokenizer(spec,",");
+ while (parts.hasMoreTokens())
+ {
+ String part = parts.nextToken().trim();
+ if (part.length() > 0)
+ {
+ if (part.indexOf('-') < 0)
+ {
+ Integer value = Integer.valueOf(part);
+ _mask.set(value);
+ }
+ else
+ {
+ int low = 0, high = 255;
+
+ String[] bounds = part.split("-",-2);
+ if (bounds.length != 2)
+ {
+ throw new IllegalArgumentException("Invalid octet spec: "+octetSpec);
+ }
+
+ if (bounds[0].length() > 0)
+ {
+ low = Integer.parseInt(bounds[0]);
+ }
+ if (bounds[1].length() > 0)
+ {
+ high = Integer.parseInt(bounds[1]);
+ }
+
+ if (low > high)
+ {
+ throw new IllegalArgumentException("Invalid octet spec: "+octetSpec);
+ }
+
+ _mask.set(low, high+1);
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (NumberFormatException ex)
+ {
+ throw new IllegalArgumentException("Invalid octet spec: "+octetSpec, ex);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Match specified octet value against the wildcard
+ *
+ * @param value octet value
+ * @return true if specified octet value matches the wildcard
+ * @throws IllegalArgumentException if specified octet value is invalid
+ */
+ public boolean match(String value)
+ throws IllegalArgumentException
+ {
+ if (value == null || value.trim().length() == 0)
+ throw new IllegalArgumentException("Invalid octet: "+value);
+
+ try
+ {
+ int number = Integer.parseInt(value);
+ return match(number);
+ }
+ catch (NumberFormatException ex)
+ {
+ throw new IllegalArgumentException("Invalid octet: "+value);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Match specified octet value against the wildcard
+ *
+ * @param number octet value
+ * @return true if specified octet value matches the wildcard
+ * @throws IllegalArgumentException if specified octet value is invalid
+ */
+ public boolean match(int number)
+ throws IllegalArgumentException
+ {
+ if (number < 0 || number > 255)
+ throw new IllegalArgumentException("Invalid octet: "+number);
+
+ return _mask.get(number);
+ }
+ }
+}