001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.util;
018
019import java.util.function.Predicate;
020
021import org.apache.camel.util.function.TriConsumer;
022
023@SuppressWarnings("unchecked")
024public class DoubleMap<K1, K2, V> {
025
026    private static final double MAX_LOAD_FACTOR = 1.2;
027    private static final int MAX_TABLE_SIZE = 32768;
028    private static final int C1 = 0xcc9e2d51;
029    private static final int C2 = 0x1b873593;
030
031    static class Entry {
032        Object k1;
033        Object k2;
034        Object v;
035        Entry next;
036    }
037
038    private Entry[] table;
039    private int mask;
040
041    public DoubleMap(int size) {
042        table = new Entry[closedTableSize(size)];
043        mask = table.length - 1;
044    }
045
046    public V get(K1 k1, K2 k2) {
047        Entry[] table = this.table;
048        int mask = this.mask;
049        int index = smear(k1.hashCode() * 31 + k2.hashCode()) & mask;
050        for (Entry entry = table[index]; entry != null; entry = entry.next) {
051            if (k1 == entry.k1 && k2 == entry.k2) {
052                return (V) entry.v;
053            }
054        }
055        return null;
056    }
057
058    public void forEach(TriConsumer<K1, K2, V> consumer) {
059        Entry[] table = this.table;
060        for (Entry entry : table) {
061            while (entry != null) {
062                consumer.accept((K1) entry.k1, (K2) entry.k2, (V) entry.v);
063                entry = entry.next;
064            }
065        }
066    }
067
068    public boolean containsKey(K1 k1, K2 k2) {
069        Entry[] table = this.table;
070        int mask = this.mask;
071        int index = smear(k1.hashCode() * 31 + k2.hashCode()) & mask;
072        for (Entry entry = table[index]; entry != null; entry = entry.next) {
073            if (k1 == entry.k1 && k2 == entry.k2) {
074                return true;
075            }
076        }
077        return false;
078    }
079
080    public synchronized void put(K1 k1, K2 k2, V v) {
081        Entry[] table = this.table;
082        int size = size() + 1;
083        int realSize = closedTableSize(size);
084        if (realSize <= table.length) {
085            realSize = table.length;
086            int index = smear(k1.hashCode() * 31 + k2.hashCode()) & (realSize - 1);
087            for (Entry oldEntry = table[index]; oldEntry != null; oldEntry = oldEntry.next) {
088                if (oldEntry.k1 == k1 && oldEntry.k2 == k2) {
089                    oldEntry.v = v;
090                    return;
091                }
092            }
093            Entry entry = new Entry();
094            entry.k1 = k1;
095            entry.k2 = k2;
096            entry.v = v;
097            entry.next = table[index];
098            table[index] = entry;
099        } else {
100            Entry[] newT = new Entry[realSize];
101            int index = smear(k1.hashCode() * 31 + k2.hashCode()) & (realSize - 1);
102            Entry entry = new Entry();
103            newT[index] = entry;
104            entry.k1 = k1;
105            entry.k2 = k2;
106            entry.v = v;
107            for (Entry oldEntry : table) {
108                while (oldEntry != null) {
109                    if (k1 != oldEntry.k1 || k2 != oldEntry.k2) {
110                        index = smear(oldEntry.k1.hashCode() * 31 + oldEntry.k2.hashCode()) & (realSize - 1);
111                        Entry newEntry = new Entry();
112                        newEntry.k1 = oldEntry.k1;
113                        newEntry.k2 = oldEntry.k2;
114                        newEntry.v = oldEntry.v;
115                        newEntry.next = newT[index];
116                        newT[index] = newEntry;
117                    }
118                    oldEntry = oldEntry.next;
119                }
120            }
121            this.table = newT;
122            this.mask = realSize - 1;
123        }
124    }
125
126    public synchronized boolean remove(K1 k1, K2 k2) {
127        Entry[] table = this.table;
128        int mask = this.mask;
129        int index = smear(k1.hashCode() * 31 + k2.hashCode()) & mask;
130        Entry prevEntry = null;
131        for (Entry oldEntry = table[index]; oldEntry != null; prevEntry = oldEntry, oldEntry = oldEntry.next) {
132            if (oldEntry.k1 == k1 && oldEntry.k2 == k2) {
133                if (prevEntry == null) {
134                    table[index] = oldEntry.next;
135                } else {
136                    prevEntry.next = oldEntry.next;
137                }
138                return true;
139            }
140        }
141        return false;
142    }
143
144    public V getFirst(Predicate<K1> p1, Predicate<K2> p2) {
145        for (Entry entry : table) {
146            while (entry != null) {
147                if (p1.test((K1) entry.k1) && p2.test((K2) entry.k2)) {
148                    return (V) entry.v;
149                }
150                entry = entry.next;
151            }
152        }
153        return null;
154    }
155
156    public int size() {
157        Entry[] table = this.table;
158        int n = 0;
159        if (table != null) {
160            for (Entry e : table) {
161                for (Entry c = e; c != null; c = c.next) {
162                    n++;
163                }
164            }
165        }
166        return n;
167    }
168
169    public synchronized void clear() {
170        this.table = new Entry[table.length];
171    }
172
173    static int smear(int hashCode) {
174        return C2 * Integer.rotateLeft(hashCode * C1, 15);
175    }
176
177    static int closedTableSize(int expectedEntries) {
178        // Get the recommended table size.
179        // Round down to the nearest power of 2.
180        expectedEntries = Math.max(expectedEntries, 2);
181        int tableSize = Integer.highestOneBit(expectedEntries);
182        // Check to make sure that we will not exceed the maximum load factor.
183        if (expectedEntries > (int) (MAX_LOAD_FACTOR * tableSize)) {
184            tableSize <<= 1;
185            return (tableSize > 0) ? tableSize : MAX_TABLE_SIZE;
186        }
187        return tableSize;
188    }
189
190}