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.wicket.util.collections; 018 019import java.io.Serializable; 020import java.util.AbstractList; 021import java.util.AbstractSet; 022import java.util.Collection; 023import java.util.Iterator; 024import java.util.Map; 025import java.util.NoSuchElementException; 026import java.util.Set; 027 028/** 029 * A fixed size map implementation. Holds an array of keys and array of values which correspond by 030 * index. Null key entries are available for use. This means that null is not a valid key. 031 * 032 * @author Jonathan Locke 033 * @param <K> 034 * Key type 035 * @param <V> 036 * Value type 037 */ 038public class MiniMap<K, V> implements Map<K, V>, Serializable 039{ 040 private static final long serialVersionUID = 1L; 041 042 /** The array of keys. Keys that are null are not used. */ 043 private final K[] keys; 044 045 /** The array of values which correspond by index with the keys array. */ 046 private final V[] values; 047 048 /** The number of valid entries */ 049 private int size; 050 051 /** The last search index. This makes putting and getting more efficient. */ 052 private int lastSearchIndex; 053 054 /** 055 * Constructor 056 * 057 * @param maxEntries 058 * The maximum number of entries this map can hold 059 */ 060 @SuppressWarnings("unchecked") 061 public MiniMap(final int maxEntries) 062 { 063 keys = (K[])new Object[maxEntries]; 064 values = (V[])new Object[maxEntries]; 065 } 066 067 /** 068 * Constructor 069 * 070 * @param map 071 * The map 072 * @param maxEntries 073 * The maximum number of entries this map can hold 074 */ 075 public MiniMap(final Map<? extends K, ? extends V> map, final int maxEntries) 076 { 077 this(maxEntries); 078 putAll(map); 079 } 080 081 /** 082 * @return True if this MiniMap is full 083 */ 084 public boolean isFull() 085 { 086 return size == keys.length; 087 } 088 089 /** 090 * @see java.util.Map#size() 091 */ 092 @Override 093 public int size() 094 { 095 return size; 096 } 097 098 /** 099 * @see java.util.Map#isEmpty() 100 */ 101 @Override 102 public boolean isEmpty() 103 { 104 return size == 0; 105 } 106 107 /** 108 * @see java.util.Map#containsKey(java.lang.Object) 109 */ 110 @Override 111 public boolean containsKey(final Object key) 112 { 113 return findKey(0, key) != -1; 114 } 115 116 /** 117 * @see java.util.Map#containsValue(java.lang.Object) 118 */ 119 @Override 120 public boolean containsValue(final Object value) 121 { 122 return findValue(0, value) != -1; 123 } 124 125 /** 126 * @see java.util.Map#get(java.lang.Object) 127 */ 128 @Override 129 public V get(final Object key) 130 { 131 // Search for key 132 final int index = findKey(key); 133 134 if (index != -1) 135 { 136 // Return value 137 return values[index]; 138 } 139 140 // Failed to find key 141 return null; 142 } 143 144 /** 145 * @see java.util.Map#put(java.lang.Object, java.lang.Object) 146 */ 147 @Override 148 public V put(final K key, final V value) 149 { 150 // Search for key 151 final int index = findKey(key); 152 153 if (index != -1) 154 { 155 // Replace existing value 156 final V oldValue = values[index]; 157 values[index] = value; 158 return oldValue; 159 } 160 161 // Is there room for a new entry? 162 if (size < keys.length) 163 { 164 // Store at first null index and continue searching after null index 165 // next time 166 final int nullIndex = nextNullKey(lastSearchIndex); 167 lastSearchIndex = nextIndex(nullIndex); 168 keys[nullIndex] = key; 169 values[nullIndex] = value; 170 size++; 171 172 return null; 173 } 174 else 175 { 176 throw new IllegalStateException("Map full"); 177 } 178 } 179 180 /** 181 * @see java.util.Map#remove(java.lang.Object) 182 */ 183 @Override 184 public V remove(final Object key) 185 { 186 // Search for key 187 final int index = findKey(key); 188 189 if (index != -1) 190 { 191 // Store value 192 final V oldValue = values[index]; 193 194 keys[index] = null; 195 values[index] = null; 196 size--; 197 198 return oldValue; 199 } 200 201 return null; 202 } 203 204 /** 205 * @see java.util.Map#putAll(java.util.Map) 206 */ 207 @Override 208 public void putAll(final Map<? extends K, ? extends V> map) 209 { 210 for (final Entry<? extends K, ? extends V> entry : map.entrySet()) 211 { 212 put(entry.getKey(), entry.getValue()); 213 } 214 } 215 216 /** 217 * @see java.util.Map#clear() 218 */ 219 @Override 220 public void clear() 221 { 222 for (int i = 0; i < keys.length; i++) 223 { 224 keys[i] = null; 225 values[i] = null; 226 } 227 228 size = 0; 229 } 230 231 /** 232 * @see java.util.Map#keySet() 233 */ 234 @Override 235 public Set<K> keySet() 236 { 237 return new AbstractSet<K>() 238 { 239 @Override 240 public Iterator<K> iterator() 241 { 242 return new Iterator<K>() 243 { 244 @Override 245 public boolean hasNext() 246 { 247 return i < size - 1; 248 } 249 250 @Override 251 public K next() 252 { 253 // Just in case... (WICKET-428) 254 if (!hasNext()) 255 { 256 throw new NoSuchElementException(); 257 } 258 259 // Find next key 260 i = nextKey(nextIndex(i)); 261 262 // Get key 263 return keys[i]; 264 } 265 266 @Override 267 public void remove() 268 { 269 keys[i] = null; 270 values[i] = null; 271 size--; 272 } 273 274 int i = -1; 275 }; 276 } 277 278 @Override 279 public int size() 280 { 281 return size; 282 } 283 }; 284 } 285 286 /** 287 * @see java.util.Map#values() 288 */ 289 @Override 290 public Collection<V> values() 291 { 292 return new AbstractList<V>() 293 { 294 @Override 295 public V get(final int index) 296 { 297 if (index > size - 1) 298 { 299 throw new IndexOutOfBoundsException(); 300 } 301 int keyIndex = nextKey(0); 302 303 for (int i = 0; i < index; i++) 304 { 305 keyIndex = nextKey(keyIndex + 1); 306 } 307 308 return values[keyIndex]; 309 } 310 311 @Override 312 public int size() 313 { 314 return size; 315 } 316 }; 317 } 318 319 /** 320 * @see java.util.Map#entrySet() 321 */ 322 @Override 323 public Set<Entry<K, V>> entrySet() 324 { 325 return new AbstractSet<Entry<K, V>>() 326 { 327 @Override 328 public Iterator<Entry<K, V>> iterator() 329 { 330 return new Iterator<Entry<K, V>>() 331 { 332 @Override 333 public boolean hasNext() 334 { 335 return index < size; 336 } 337 338 @Override 339 public Entry<K, V> next() 340 { 341 if (!hasNext()) 342 { 343 throw new NoSuchElementException(); 344 } 345 346 keyIndex = nextKey(nextIndex(keyIndex)); 347 348 index++; 349 350 return new Map.Entry<K, V>() 351 { 352 @Override 353 public K getKey() 354 { 355 return keys[keyIndex]; 356 } 357 358 @Override 359 public V getValue() 360 { 361 return values[keyIndex]; 362 } 363 364 @Override 365 public V setValue(final V value) 366 { 367 final V oldValue = values[keyIndex]; 368 369 values[keyIndex] = value; 370 371 return oldValue; 372 } 373 }; 374 } 375 376 @Override 377 public void remove() 378 { 379 keys[keyIndex] = null; 380 values[keyIndex] = null; 381 } 382 383 int keyIndex = -1; 384 385 int index = 0; 386 }; 387 } 388 389 @Override 390 public int size() 391 { 392 return size; 393 } 394 }; 395 } 396 397 /** 398 * Computes the next index in the key or value array (both are the same length) 399 * 400 * @param index 401 * The index 402 * @return The next index, taking into account wraparound 403 */ 404 private int nextIndex(final int index) 405 { 406 return (index + 1) % keys.length; 407 } 408 409 /** 410 * Finds the index of the next non-null key. If the map is empty, -1 will be returned. 411 * 412 * @param start 413 * Index to start at 414 * @return Index of next non-null key 415 */ 416 private int nextKey(final int start) 417 { 418 int i = start; 419 420 do 421 { 422 if (keys[i] != null) 423 { 424 return i; 425 } 426 427 i = nextIndex(i); 428 } 429 while (i != start); 430 431 return -1; 432 } 433 434 /** 435 * Finds the index of the next null key. If no null key can be found, the map is full and -1 436 * will be returned. 437 * 438 * @param start 439 * Index to start at 440 * @return Index of next null key 441 */ 442 private int nextNullKey(final int start) 443 { 444 int i = start; 445 446 do 447 { 448 if (keys[i] == null) 449 { 450 return i; 451 } 452 453 i = nextIndex(i); 454 } 455 while (i != start); 456 457 return -1; 458 } 459 460 /** 461 * Finds a key by starting at lastSearchIndex and searching from there. If the key is found, 462 * lastSearchIndex is advanced so the next key search can find the next key in the array, which 463 * is the most likely to be retrieved. 464 * 465 * @param key 466 * Key to find in map 467 * @return Index of matching key or -1 if not found 468 */ 469 private int findKey(final Object key) 470 { 471 if (size > 0) 472 { 473 // Find key starting at search index 474 final int index = findKey(lastSearchIndex, key); 475 476 // Found match? 477 if (index != -1) 478 { 479 // Start search at the next index next time 480 lastSearchIndex = nextIndex(index); 481 482 // Return index of key 483 return index; 484 } 485 } 486 487 return -1; 488 } 489 490 /** 491 * Searches for a key from a given starting index. 492 * 493 * @param key 494 * The key to find in this map 495 * @param start 496 * Index to start at 497 * @return Index of matching key or -1 if not found 498 */ 499 private int findKey(final int start, final Object key) 500 { 501 int i = start; 502 503 do 504 { 505 if (key.equals(keys[i])) 506 { 507 return i; 508 } 509 510 i = nextIndex(i); 511 } 512 while (i != start); 513 514 return -1; 515 } 516 517 /** 518 * Searches for a value from a given starting index. 519 * 520 * @param start 521 * Index to start at 522 * @param value 523 * The value to find in this map 524 * @return Index of matching value or -1 if not found 525 */ 526 private int findValue(final int start, final Object value) 527 { 528 int i = start; 529 530 do 531 { 532 if (value.equals(values[i])) 533 { 534 return i; 535 } 536 537 i = nextIndex(i); 538 } 539 while (i != start); 540 541 return -1; 542 } 543}