]> WPIA git - gigi.git/blobdiff - lib/jetty/org/eclipse/jetty/util/ArrayTernaryTrie.java
Importing upstream Jetty jetty-9.2.1.v20140609
[gigi.git] / lib / jetty / org / eclipse / jetty / util / ArrayTernaryTrie.java
diff --git a/lib/jetty/org/eclipse/jetty/util/ArrayTernaryTrie.java b/lib/jetty/org/eclipse/jetty/util/ArrayTernaryTrie.java
new file mode 100644 (file)
index 0000000..fdca4ce
--- /dev/null
@@ -0,0 +1,473 @@
+//
+//  ========================================================================
+//  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.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+
+/* ------------------------------------------------------------ */
+/** A Ternary Trie String lookup data structure.
+ * This Trie is of a fixed size and cannot grow (which can be a good thing with regards to DOS when used as a cache).
+ * <p>
+ * The Trie is stored in 3 arrays:<dl>
+ * <dt>char[] _tree</dt><dd>This is semantically 2 dimensional array flattened into a 1 dimensional char array. The second dimension
+ * is that every 4 sequential elements represents a row of: character; hi index; eq index; low index, used to build a
+ * ternary trie of key strings.</dd>
+ * <dt>String[] _key<dt><dd>An array of key values where each element matches a row in the _tree array. A non zero key element 
+ * indicates that the _tree row is a complete key rather than an intermediate character of a longer key.</dd>
+ * <dt>V[] _value</dt><dd>An array of values corresponding to the _key array</dd>
+ * </dl>
+ * <p>The lookup of a value will iterate through the _tree array matching characters. If the equal tree branch is followed,
+ * then the _key array is looked up to see if this is a complete match.  If a match is found then the _value array is looked up
+ * to return the matching value.
+ * </p>
+ * @param <V>
+ */
+public class ArrayTernaryTrie<V> extends AbstractTrie<V>
+{
+    private static int LO=1;
+    private static int EQ=2;
+    private static int HI=3;
+    
+    /**
+     * The Size of a Trie row is the char, and the low, equal and high
+     * child pointers
+     */
+    private static final int ROW_SIZE = 4;
+    
+    /**
+     * The Trie rows in a single array which allows a lookup of row,character
+     * to the next row in the Trie.  This is actually a 2 dimensional
+     * array that has been flattened to achieve locality of reference.
+     */
+    private final char[] _tree;
+    
+    /**
+     * The key (if any) for a Trie row. 
+     * A row may be a leaf, a node or both in the Trie tree.
+     */
+    private final String[] _key;
+    
+    /**
+     * The value (if any) for a Trie row. 
+     * A row may be a leaf, a node or both in the Trie tree.
+     */
+    private final Object[] _value;
+    
+    /**
+     * The number of rows allocated
+     */
+    private char _rows;
+
+    public ArrayTernaryTrie()
+    {
+        this(128);
+    }
+    
+    public ArrayTernaryTrie(boolean insensitive)
+    {
+        this(insensitive,128);
+    }
+
+    public ArrayTernaryTrie(int capacityInNodes)
+    {
+        this(true,capacityInNodes);
+    }
+    
+    public ArrayTernaryTrie(boolean insensitive, int capacityInNodes)
+    {
+        super(insensitive);
+        _value=new Object[capacityInNodes];
+        _tree=new char[capacityInNodes*ROW_SIZE];
+        _key=new String[capacityInNodes];
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Copy Trie and change capacity by a factor
+     * @param trie
+     * @param factor
+     */
+    public ArrayTernaryTrie(ArrayTernaryTrie<V> trie, double factor)
+    {
+        super(trie.isCaseInsensitive());
+        int capacity=(int)(trie._value.length*factor);
+        _rows=trie._rows;
+        _value=Arrays.copyOf(trie._value, capacity);
+        _tree=Arrays.copyOf(trie._tree, capacity*ROW_SIZE);
+        _key=Arrays.copyOf(trie._key, capacity);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean put(String s, V v)
+    {
+        int t=0;
+        int limit = s.length();
+        int last=0;
+        for(int k=0; k < limit; k++)
+        {
+            char c=s.charAt(k);
+            if(isCaseInsensitive() && c<128)
+                c=StringUtil.lowercases[c];
+            
+            while (true)
+            {
+                int row=ROW_SIZE*t;
+                
+                // Do we need to create the new row?
+                if (t==_rows)
+                {
+                    _rows++;
+                    if (_rows>=_key.length)
+                    {
+                        _rows--;
+                        return false;
+                    }
+                    _tree[row]=c;
+                }
+
+                char n=_tree[row];
+                int diff=n-c;
+                if (diff==0)
+                    t=_tree[last=(row+EQ)];
+                else if (diff<0)
+                    t=_tree[last=(row+LO)];
+                else
+                    t=_tree[last=(row+HI)];
+                
+                // do we need a new row?
+                if (t==0)
+                {
+                    t=_rows;
+                    _tree[last]=(char)t;
+                }
+                
+                if (diff==0)
+                    break;
+            }
+        }
+
+        // Do we need to create the new row?
+        if (t==_rows)
+        {
+            _rows++;
+            if (_rows>=_key.length)
+            {
+                _rows--;
+                return false;
+            }
+        }
+
+        // Put the key and value
+        _key[t]=v==null?null:s;
+        _value[t] = v;
+                
+        return true;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V get(String s,int offset, int len)
+    {
+        int t = 0;
+        for(int i=0; i < len;)
+        {
+            char c=s.charAt(offset+i++);
+            if(isCaseInsensitive() && c<128)
+                c=StringUtil.lowercases[c];
+            
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        return null;
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    return null;
+            }
+        }
+        
+        return (V)_value[t];
+    }
+
+    
+    @Override
+    public V get(ByteBuffer b, int offset, int len)
+    {
+        int t = 0;
+        offset+=b.position();
+        
+        for(int i=0; i < len;)
+        {
+            byte c=(byte)(b.get(offset+i++)&0x7f);
+            if(isCaseInsensitive())
+                c=(byte)StringUtil.lowercases[c];
+            
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        return null;
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    return null;
+            }
+        }
+
+        return (V)_value[t];
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(String s)
+    {
+        return getBest(0,s,0,s.length());
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(String s, int offset, int length)
+    {
+        return getBest(0,s,offset,length);
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,String s,int offset,int len)
+    {
+        int node=t;
+        loop: for(int i=0; i<len; i++)
+        {
+            char c=s.charAt(offset+i);
+            if(isCaseInsensitive() && c<128)
+                c=StringUtil.lowercases[c];
+
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        break loop;
+                    
+                    // if this node is a match, recurse to remember 
+                    if (_key[t]!=null)
+                    {
+                        node=t;
+                        V best=getBest(t,s,offset+i+1,len-i-1);
+                        if (best!=null)
+                            return best;
+                    }
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    break loop;
+            }
+        }
+        return (V)_value[node];
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(ByteBuffer b, int offset, int len)
+    {
+        if (b.hasArray())
+            return getBest(0,b.array(),b.arrayOffset()+b.position()+offset,len);
+        return getBest(0,b,offset,len);
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,byte[] b, int offset, int len)
+    {
+        int node=t;
+        loop: for(int i=0; i<len; i++)
+        {
+            byte c=(byte)(b[offset+i]&0x7f);
+            if(isCaseInsensitive())
+                c=(byte)StringUtil.lowercases[c];
+
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        break loop;
+                    
+                    // if this node is a match, recurse to remember 
+                    if (_key[t]!=null)
+                    {
+                        node=t;
+                        V best=getBest(t,b,offset+i+1,len-i-1);
+                        if (best!=null)
+                            return best;
+                    }
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    break loop;
+            }
+        }
+        return (V)_value[node];
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,ByteBuffer b, int offset, int len)
+    {
+        int node=t;
+        int o= offset+b.position();
+        
+        loop: for(int i=0; i<len; i++)
+        {
+            byte c=(byte)(b.get(o+i)&0x7f);
+            if(isCaseInsensitive())
+                c=(byte)StringUtil.lowercases[c];
+
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        break loop;
+                    
+                    // if this node is a match, recurse to remember 
+                    if (_key[t]!=null)
+                    {
+                        node=t;
+                        V best=getBest(t,b,offset+i+1,len-i-1);
+                        if (best!=null)
+                            return best;
+                    }
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    break loop;
+            }
+        }
+        return (V)_value[node];
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        for (int r=0;r<=_rows;r++)
+        {
+            if (_key[r]!=null && _value[r]!=null)
+            {
+                buf.append(',');
+                buf.append(_key[r]);
+                buf.append('=');
+                buf.append(_value[r].toString());
+            }
+        }
+        if (buf.length()==0)
+            return "{}";
+        
+        buf.setCharAt(0,'{');
+        buf.append('}');
+        return buf.toString();
+    }
+
+
+
+    @Override
+    public Set<String> keySet()
+    {
+        Set<String> keys = new HashSet<>();
+
+        for (int r=0;r<=_rows;r++)
+        {
+            if (_key[r]!=null && _value[r]!=null)
+                keys.add(_key[r]);
+        }
+        return keys;
+    }
+
+    @Override
+    public boolean isFull()
+    {
+        return _rows+1==_key.length;
+    }
+    
+    public static int hilo(int diff)
+    {
+        // branchless equivalent to return ((diff<0)?LO:HI);
+        // return 3+2*((diff&Integer.MIN_VALUE)>>Integer.SIZE-1);
+        return 1+(diff|Integer.MAX_VALUE)/(Integer.MAX_VALUE/2);
+    }
+    
+    public void dump()
+    {
+        for (int r=0;r<_rows;r++)
+        {
+            char c=_tree[r*ROW_SIZE+0];
+            System.err.printf("%4d [%s,%d,%d,%d] '%s':%s%n",
+                r,
+                (c<' '||c>127)?(""+(int)c):"'"+c+"'",
+                (int)_tree[r*ROW_SIZE+LO],
+                (int)_tree[r*ROW_SIZE+EQ],
+                (int)_tree[r*ROW_SIZE+HI],
+                _key[r],
+                _value[r]);
+        }
+        
+    }
+}