blob: 5d1334ea9c2f95d162bf92539a98cd152bb7222d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.util;
import java.util.function.Predicate;
import org.apache.camel.util.function.TriConsumer;
@SuppressWarnings("unchecked")
public class DoubleMap<K1, K2, V> {
private static final double MAX_LOAD_FACTOR = 1.2;
private static final int MAX_TABLE_SIZE = 32768;
private static final int C1 = 0xcc9e2d51;
private static final int C2 = 0x1b873593;
static class Entry {
Object k1;
Object k2;
Object v;
Entry next;
}
private Entry[] table;
private int mask;
public DoubleMap(int size) {
table = new Entry[closedTableSize(size)];
mask = table.length - 1;
}
public V get(K1 k1, K2 k2) {
Entry[] table = this.table;
int mask = this.mask;
int index = smear(k1.hashCode() * 31 + k2.hashCode()) & mask;
for (Entry entry = table[index]; entry != null; entry = entry.next) {
if (k1 == entry.k1 && k2 == entry.k2) {
return (V) entry.v;
}
}
return null;
}
public void forEach(TriConsumer<K1, K2, V> consumer) {
Entry[] table = this.table;
for (Entry entry : table) {
while (entry != null) {
consumer.accept((K1) entry.k1, (K2) entry.k2, (V) entry.v);
entry = entry.next;
}
}
}
public boolean containsKey(K1 k1, K2 k2) {
Entry[] table = this.table;
int mask = this.mask;
int index = smear(k1.hashCode() * 31 + k2.hashCode()) & mask;
for (Entry entry = table[index]; entry != null; entry = entry.next) {
if (k1 == entry.k1 && k2 == entry.k2) {
return true;
}
}
return false;
}
public synchronized void put(K1 k1, K2 k2, V v) {
Entry[] table = this.table;
int size = size() + 1;
int realSize = closedTableSize(size);
if (realSize <= table.length) {
realSize = table.length;
int index = smear(k1.hashCode() * 31 + k2.hashCode()) & (realSize - 1);
for (Entry oldEntry = table[index]; oldEntry != null; oldEntry = oldEntry.next) {
if (oldEntry.k1 == k1 && oldEntry.k2 == k2) {
oldEntry.v = v;
return;
}
}
Entry entry = new Entry();
entry.k1 = k1;
entry.k2 = k2;
entry.v = v;
entry.next = table[index];
table[index] = entry;
} else {
Entry[] newT = new Entry[realSize];
int index = smear(k1.hashCode() * 31 + k2.hashCode()) & (realSize - 1);
Entry entry = new Entry();
newT[index] = entry;
entry.k1 = k1;
entry.k2 = k2;
entry.v = v;
for (Entry oldEntry : table) {
while (oldEntry != null) {
if (k1 != oldEntry.k1 || k2 != oldEntry.k2) {
index = smear(oldEntry.k1.hashCode() * 31 + oldEntry.k2.hashCode()) & (realSize - 1);
Entry newEntry = new Entry();
newEntry.k1 = oldEntry.k1;
newEntry.k2 = oldEntry.k2;
newEntry.v = oldEntry.v;
newEntry.next = newT[index];
newT[index] = newEntry;
}
oldEntry = oldEntry.next;
}
}
this.table = newT;
this.mask = realSize - 1;
}
}
public synchronized boolean remove(K1 k1, K2 k2) {
Entry[] table = this.table;
int mask = this.mask;
int index = smear(k1.hashCode() * 31 + k2.hashCode()) & mask;
Entry prevEntry = null;
for (Entry oldEntry = table[index]; oldEntry != null; prevEntry = oldEntry, oldEntry = oldEntry.next) {
if (oldEntry.k1 == k1 && oldEntry.k2 == k2) {
if (prevEntry == null) {
table[index] = oldEntry.next;
} else {
prevEntry.next = oldEntry.next;
}
return true;
}
}
return false;
}
public V getFirst(Predicate<K1> p1, Predicate<K2> p2) {
for (Entry entry : table) {
while (entry != null) {
if (p1.test((K1) entry.k1) && p2.test((K2) entry.k2)) {
return (V) entry.v;
}
entry = entry.next;
}
}
return null;
}
public int size() {
Entry[] table = this.table;
int n = 0;
if (table != null) {
for (Entry e : table) {
for (Entry c = e; c != null; c = c.next) {
n++;
}
}
}
return n;
}
public synchronized void clear() {
this.table = new Entry[table.length];
}
static int smear(int hashCode) {
return C2 * Integer.rotateLeft(hashCode * C1, 15);
}
static int closedTableSize(int expectedEntries) {
// Get the recommended table size.
// Round down to the nearest power of 2.
expectedEntries = Math.max(expectedEntries, 2);
int tableSize = Integer.highestOneBit(expectedEntries);
// Check to make sure that we will not exceed the maximum load factor.
if (expectedEntries > (int) (MAX_LOAD_FACTOR * tableSize)) {
tableSize <<= 1;
return (tableSize > 0) ? tableSize : MAX_TABLE_SIZE;
}
return tableSize;
}
}