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}