Changeset 1282 for trunk/autoquest-core-usageprofiles/src/main/java/de/ugoe/cs/autoquest/usageprofiles/SymbolMap.java
- Timestamp:
- 07/30/13 09:38:43 (11 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/autoquest-core-usageprofiles/src/main/java/de/ugoe/cs/autoquest/usageprofiles/SymbolMap.java
r1257 r1282 16 16 17 17 import java.io.Serializable; 18 import java.util.ArrayList;19 import java.util.Arrays;20 18 import java.util.Collection; 21 import java.util.HashMap;22 import java.util.Iterator;23 import java.util.LinkedList;24 import java.util.List;25 import java.util.Map;26 import java.util.Map.Entry;27 19 28 20 /** 29 21 * <p> 30 * This class is a data structure for holding symbols which is more efficient than a simple list. 31 * This data structure can be used with a comparator to adapt the effective list behavior and to 32 * define the equals strategy for comparing objects. After a certain size ({@link #MAX_LIST_SIZE}), 33 * the symbol map creates a symbol index consisting of buckets. This allows searching for symbols 34 * in a more efficient order as the search can start in the most appropriate of the internal 35 * buckets. 22 * This interface defines a data structure for holding symbols. Its implementations can be used to 23 * improve the performance of a trie by tuning its internal symbol storage management. 36 24 * </p> 37 25 * <p> 38 * The class is called a map, although it is not. It may contain the same element as separate keys. 39 * This implementation is done for performance improvements. If it is required to really assure, 40 * that a key exists only once, then each call to the {@link #addSymbol(Object, Object)} method 41 * should be done only, if the {@link #containsSymbol(Object)} method for the same symbol returns 42 * false. 26 * The interface is called a map, although it is not necessarily. It may contain the same element 27 * as separate keys. This is allowed for performance improvements. If it is required to really 28 * assure, that a key exists only once, then each call to the {@link #addSymbol(Object, Object)} 29 * method should be done only, if the {@link #containsSymbol(Object)} method for the same symbol 30 * returns false. 31 * </p> 32 * <p> 33 * Mapping a symbol to the value null is possible. In this case {@link #getValue(Object)} returns 34 * null for the symbol. But {@link #containsSymbol(Object)} returns true. 43 35 * </p> 44 36 * 45 * @ see SymbolComparator37 * @author Patrick Harms 46 38 * 47 * @author Patrick Harms 39 * @param <T> 40 * Type of the symbols that are stored 41 * @param <V> 42 * Type of the values associated with the symbols 43 * 44 * @see SymbolStrategy 48 45 */ 49 public class SymbolMap<K, V> implements Serializable { 50 51 /** 52 * <p> 53 * default serial version UID 54 * </p> 55 */ 56 private static final long serialVersionUID = 1L; 57 58 /** 59 * <p> 60 * the maximum number of symbols in this map which is still only treated as list instead of 61 * using buckets. 62 * </p> 63 */ 64 private static final int MAX_LIST_SIZE = 15; 65 66 /** 67 * <p> 68 * Comparator to be used for comparing the symbols with each other and to determine a bucket 69 * search order 70 * </p> 71 */ 72 private SymbolComparator<K> comparator; 73 74 /** 75 * <p> 76 * Internally maintained plain list of symbols and associated values 77 * </p> 78 */ 79 private List<Map.Entry<K, V>> symbolList; 80 81 /** 82 * <p> 83 * If the size of the map exceeds {@link #MAX_LIST_SIZE}, this is the symbol index using buckets 84 * for optimizing the search order. 85 * </p> 86 */ 87 private Map<Integer, List<Map.Entry<K, V>>> symbolBuckets; 88 89 /** 90 * <p> 91 * When using buckets, not any symbol may be associated a correct bucket by the used 92 * comparator. Therefore, we set a default bucket for all such symbols. This may change 93 * if the comparator defines the same bucket for a specific symbol. 94 * </p> 95 */ 96 private int defaultBucket = 0; 97 98 /** 99 * <p> 100 * Instantiates a symbol map with a comparator 101 * </p> 102 * 103 * @param comparator the comparator to use for comparing symbols and for determining bucket 104 * search orders 105 * 106 * @throws IllegalArgumentException if the provided comparator is null 107 */ 108 public SymbolMap(SymbolComparator<K> comparator) { 109 if (comparator == null) { 110 throw new IllegalArgumentException("comparator must not be null"); 111 } 112 113 this.comparator = comparator; 114 this.symbolList = new ArrayList<Map.Entry<K, V>>(); 115 } 116 117 /** 118 * <p> 119 * Copy constructure 120 * </p> 121 * 122 * @param otherMap the other map to be copied including its comparator 123 * 124 * @throws IllegalArgumentException if the provided other map is null 125 */ 126 public SymbolMap(SymbolMap<K, V> otherMap) { 127 if (otherMap == null) { 128 throw new IllegalArgumentException("otherMap must not be null"); 129 } 130 131 this.comparator = otherMap.comparator; 132 this.symbolList = new ArrayList<Map.Entry<K, V>>(otherMap.symbolList); 133 134 if (this.symbolList.size() > MAX_LIST_SIZE) { 135 createSymbolBuckets(); 136 } 137 } 46 public interface SymbolMap<K, V> extends Serializable { 138 47 139 48 /** … … 144 53 * @return as described 145 54 */ 146 public int size() { 147 return symbolList.size(); 148 } 55 public int size(); 149 56 150 57 /** … … 155 62 * @return as described 156 63 */ 157 public boolean isEmpty() { 158 return symbolList.isEmpty(); 159 } 64 public boolean isEmpty(); 160 65 161 66 /** … … 170 75 * @throws IllegalArgumentException if the provided symbol is null 171 76 */ 172 public boolean containsSymbol(K symbol) { 173 if (symbol == null) { 174 throw new IllegalArgumentException("symbol must not be null"); 175 } 176 177 return getEntry(symbol) != null; 178 } 77 public boolean containsSymbol(K symbol); 179 78 180 79 /** … … 191 90 * @throws IllegalArgumentException if the provided symbol is null 192 91 */ 193 public V getValue(K symbol) { 194 if (symbol == null) { 195 throw new IllegalArgumentException("symbol must not be null"); 196 } 197 198 Map.Entry<K, V> entry = getEntry(symbol); 199 200 if (entry != null) { 201 return entry.getValue(); 202 } 203 else { 204 return null; 205 } 206 } 92 public V getValue(K symbol); 207 93 208 94 /** … … 210 96 * Adds a symbol and an associated value to the map. If the value is null, the symbol is added, 211 97 * anyway and {@link #containsSymbol(Object)} will return true for that symbol. Adding the 212 * same symbol twice willproduce two entries. This is contradictory to typical map98 * same symbol twice may produce two entries. This is contradictory to typical map 213 99 * implementations. To prevent this, the {@link #containsSymbol(Object)} and 214 100 * {@link #removeSymbol(Object)} methods should be used to ensure map behavior. … … 222 108 * @throws IllegalArgumentException if the provided symbol is null 223 109 */ 224 public void addSymbol(K symbol, V value) { 225 if (symbol == null) { 226 throw new IllegalArgumentException("symbol must not be null"); 227 } 228 229 Map.Entry<K, V> entry = new SymbolMapEntry(symbol, value); 230 231 symbolList.add(entry); 232 233 if (symbolList.size() > MAX_LIST_SIZE) { 234 if (symbolBuckets == null) { 235 createSymbolBuckets(); 236 } 237 else { 238 addToSymbolBucket(entry); 239 } 240 } 241 } 110 public void addSymbol(K symbol, V value); 242 111 243 112 /** … … 253 122 * @throws IllegalArgumentException if the provided symbol is null 254 123 */ 255 public V removeSymbol(K symbol) { 256 if (symbol == null) { 257 throw new IllegalArgumentException("symbol must not be null"); 258 } 259 260 for (int i = 0; i < symbolList.size(); i++) { 261 if (comparator.equals(symbolList.get(i).getKey(), symbol)) { 262 // found the symbol. Remove it from the list, and if required, also from the map. 263 V value = symbolList.remove(i).getValue(); 264 265 if (symbolList.size() > MAX_LIST_SIZE) { 266 removeFromSymbolBuckets(symbol); 267 } 268 269 return value; 270 } 271 } 272 273 return null; 274 } 124 public V removeSymbol(K symbol); 275 125 276 126 /** … … 281 131 * @return as described 282 132 */ 283 public Collection<K> getSymbols() { 284 return new ReadOnlyCollectionFacade<K>(symbolList, new SymbolFacade()); 285 } 133 public Collection<K> getSymbols(); 286 134 287 135 /** … … 294 142 * @return as described 295 143 */ 296 public Collection<V> getValues() { 297 return new ReadOnlyCollectionFacade<V>(symbolList, new ValueFacade()); 298 } 144 public Collection<V> getValues(); 299 145 300 146 /** … … 303 149 * </p> 304 150 */ 305 public void clear() { 306 symbolList.clear(); 307 symbolBuckets = null; 308 } 151 public void clear(); 309 152 310 153 /* (non-Javadoc) … … 312 155 */ 313 156 @Override 314 public int hashCode() { 315 return symbolList.size(); 316 } 157 public int hashCode(); 317 158 318 159 /* (non-Javadoc) 319 160 * @see java.lang.Object#equals(java.lang.Object) 320 161 */ 321 @SuppressWarnings("unchecked")322 162 @Override 323 public boolean equals(Object obj) { 324 if (this == obj) { 325 return true; 326 } 327 else if (this.getClass().isInstance(obj)) { 328 SymbolMap<K, V> other = (SymbolMap<K, V>) obj; 329 330 return (symbolList.size() == other.symbolList.size()) && 331 (symbolList.containsAll(other.symbolList)); 332 } 333 else { 334 return false; 335 } 336 } 337 338 /** 339 * <p> 340 * Internally used to create symbol buckets in case the number of stored symbols increased 341 * above {@link #MAX_LIST_SIZE}. 342 * </p> 343 */ 344 private void createSymbolBuckets() { 345 //System.out.println("creating symbol buckets"); 346 symbolBuckets = new HashMap<Integer, List<Map.Entry<K, V>>>(); 347 348 for (Map.Entry<K, V> symbol : symbolList) { 349 addToSymbolBucket(symbol); 350 } 351 } 352 353 /** 354 * <p> 355 * Adds a symbol and its value to its corresponding bucket. The corresponding bucket is 356 * retrieved from the symbol comparator. It is the first element of the search order returned 357 * by the symbol comparator. If the comparator does not define a search order for the symbol 358 * the entry is added to the default bucket. If the comparator defines a bucket id 359 * identical to the default bucket id, the default bucket id is shifted to another value. 360 * </p> 361 */ 362 private void addToSymbolBucket(Map.Entry<K, V> symbolEntry) { 363 int bucketId = defaultBucket; 364 int[] bucketSearchOrder = comparator.getBucketSearchOrder(symbolEntry.getKey()); 365 366 if ((bucketSearchOrder != null) && (bucketSearchOrder.length > 0)) { 367 bucketId = bucketSearchOrder[0]; 368 369 if (bucketId == defaultBucket) { 370 setNewDefaultBucketId(); 371 } 372 } 373 374 List<Map.Entry<K, V>> list = symbolBuckets.get(bucketId); 375 376 if (list == null) { 377 list = new LinkedList<Map.Entry<K, V>>(); 378 symbolBuckets.put(bucketId, list); 379 } 380 381 list.add(symbolEntry); 382 } 383 384 /** 385 * <p> 386 * Removes the entry for a given symbol from the buckets. It uses the bucket search order 387 * defined by the symbol comparator to find the symbol as fast as possible. 388 * </p> 389 */ 390 private Map.Entry<K, V> removeFromSymbolBuckets(K symbol) { 391 int bucketId = defaultBucket; 392 int[] bucketSearchOrder = comparator.getBucketSearchOrder(symbol); 393 394 if ((bucketSearchOrder != null) && (bucketSearchOrder.length > 0)) { 395 bucketId = bucketSearchOrder[0]; 396 } 397 398 List<Map.Entry<K, V>> list = symbolBuckets.get(bucketId); 399 Map.Entry<K, V> result = null; 400 401 if (list != null) { 402 for (int i = 0; i < list.size(); i++) { 403 if (comparator.equals(list.get(i).getKey(), symbol)) { 404 result = list.remove(i); 405 break; 406 } 407 } 408 409 if (list.isEmpty()) { 410 symbolBuckets.remove(bucketId); 411 } 412 } 413 414 return result; 415 } 416 417 /** 418 * <p> 419 * Updates the default bucket id to a new one 420 * </p> 421 */ 422 private void setNewDefaultBucketId() { 423 int oldDefaultBucket = defaultBucket; 424 do { 425 defaultBucket += 1; 426 } 427 while (symbolBuckets.containsKey(defaultBucket)); 428 429 symbolBuckets.put(defaultBucket, symbolBuckets.get(oldDefaultBucket)); 430 } 431 432 /** 433 * <p> 434 * searches for the entry belonging to the given symbol. The method either uses the list if 435 * buckets are not used yet, or it uses the buckets and searches them in the order defined 436 * by the comparator. If the symbol isn't found and the comparator does not refer all buckets, 437 * then also the other buckets are searched for the symbol. 438 * </p> 439 */ 440 private Map.Entry<K, V> getEntry(K symbol) { 441 Map.Entry<K, V> entry = null; 442 if (symbolBuckets == null) { 443 entry = lookup(symbol, symbolList); 444 } 445 else { 446 int[] bucketSearchOrder = comparator.getBucketSearchOrder(symbol); 447 for (int bucketId : bucketSearchOrder) { 448 List<Map.Entry<K, V>> list = symbolBuckets.get(bucketId); 449 if (list != null) { 450 entry = lookup(symbol, list); 451 if (entry != null) { 452 break; 453 } 454 } 455 } 456 457 // try to search the other buckets 458 if (entry == null) { 459 Arrays.sort(bucketSearchOrder); 460 for (Map.Entry<Integer, List<Map.Entry<K, V>>> bucket : symbolBuckets.entrySet()) { 461 if (Arrays.binarySearch(bucketSearchOrder, bucket.getKey()) < 0) { 462 List<Map.Entry<K, V>> list = bucket.getValue(); 463 if (list != null) { 464 entry = lookup(symbol, list); 465 if (entry != null) { 466 break; 467 } 468 } 469 } 470 } 471 } 472 } 473 474 return entry; 475 } 476 477 /** 478 * <p> 479 * Convenience method to look up a symbol in a list of entries using the comparator. 480 * </p> 481 */ 482 private Map.Entry<K, V> lookup(K symbol, List<Map.Entry<K, V>> list) { 483 for (Map.Entry<K, V> candidate : list) { 484 if (comparator.equals(candidate.getKey(), symbol)) { 485 return candidate; 486 } 487 } 488 489 return null; 490 } 491 492 /** 493 * <p> 494 * Internally used data structure for storing symbol value pairs 495 * </p> 496 * 497 * @author Patrick Harms 498 */ 499 private class SymbolMapEntry implements Map.Entry<K, V> { 500 501 /** 502 * the symbol to map to a value 503 */ 504 private K symbol; 505 506 /** 507 * the value associated with the symbol 508 */ 509 private V value; 510 511 /** 512 * <p> 513 * Simple constructor for initializing the entry with a symbol and its associated value. 514 * </p> 515 */ 516 private SymbolMapEntry(K symbol, V value) { 517 super(); 518 this.symbol = symbol; 519 this.value = value; 520 } 521 522 /* (non-Javadoc) 523 * @see java.util.Map.Entry#getKey() 524 */ 525 @Override 526 public K getKey() { 527 return symbol; 528 } 529 530 /* (non-Javadoc) 531 * @see java.util.Map.Entry#getValue() 532 */ 533 @Override 534 public V getValue() { 535 return value; 536 } 537 538 /* (non-Javadoc) 539 * @see java.util.Map.Entry#setValue(java.lang.Object) 540 */ 541 @Override 542 public V setValue(V value) { 543 V oldValue = this.value; 544 this.value = value; 545 return oldValue; 546 } 547 548 /* (non-Javadoc) 549 * @see java.lang.Object#hashCode() 550 */ 551 @Override 552 public int hashCode() { 553 return symbol.hashCode(); 554 } 555 556 /* (non-Javadoc) 557 * @see java.lang.Object#equals(java.lang.Object) 558 */ 559 @SuppressWarnings("unchecked") 560 @Override 561 public boolean equals(Object obj) { 562 if (this == obj) { 563 return true; 564 } 565 else if (this.getClass().isInstance(obj)) { 566 SymbolMapEntry other = (SymbolMapEntry) obj; 567 return (symbol.equals(other.symbol) && 568 (value == null ? other.value == null : value.equals(other.value))); 569 } 570 else { 571 return false; 572 } 573 } 574 575 /* (non-Javadoc) 576 * @see java.lang.Object#toString() 577 */ 578 @Override 579 public String toString() { 580 return symbol + "=" + value; 581 } 582 583 } 584 585 /** 586 * <p> 587 * Used to create an efficient facade for accessing the internal list of entries either only 588 * for the symbols or only for the values. It is a default implementation of the collection 589 * interface. The entry facade provided to the constructor decides, if either the list 590 * accesses only the symbols or only the values. 591 * </p> 592 * 593 * @author Patrick Harms 594 */ 595 private class ReadOnlyCollectionFacade<TYPE> implements Collection<TYPE> { 596 597 /** 598 * the list facaded by this facade 599 */ 600 private List<Map.Entry<K, V>> list; 601 602 /** 603 * the facade to be used for the entries 604 */ 605 private EntryFacade<TYPE> entryFacade; 606 607 /** 608 * <p> 609 * Initializes the facade with the facaded list and the facade to be used for the entries 610 * </p> 611 */ 612 private ReadOnlyCollectionFacade(List<Map.Entry<K, V>> list, EntryFacade<TYPE> entryFacade) 613 { 614 this.list = list; 615 this.entryFacade = entryFacade; 616 } 617 618 /* (non-Javadoc) 619 * @see java.util.Collection#size() 620 */ 621 @Override 622 public int size() { 623 return list.size(); 624 } 625 626 /* (non-Javadoc) 627 * @see java.util.Collection#isEmpty() 628 */ 629 @Override 630 public boolean isEmpty() { 631 return list.isEmpty(); 632 } 633 634 /* (non-Javadoc) 635 * @see java.util.Collection#contains(java.lang.Object) 636 */ 637 @Override 638 public boolean contains(Object o) { 639 if (o == null) { 640 for (Map.Entry<K, V> entry : list) { 641 if (entryFacade.getFacadedElement(entry) == null) { 642 return true; 643 } 644 } 645 } 646 else { 647 for (Map.Entry<K, V> entry : list) { 648 if (o.equals(entryFacade.getFacadedElement(entry))) { 649 return true; 650 } 651 } 652 } 653 654 return false; 655 } 656 657 /* (non-Javadoc) 658 * @see java.util.Collection#toArray() 659 */ 660 @Override 661 public Object[] toArray() { 662 Object[] result = new Object[list.size()]; 663 664 for (int i = 0; i < list.size(); i++) { 665 result[i] = entryFacade.getFacadedElement(list.get(i)); 666 } 667 668 return result; 669 } 670 671 /* (non-Javadoc) 672 * @see java.util.Collection#toArray(T[]) 673 */ 674 @SuppressWarnings("unchecked") 675 @Override 676 public <T> T[] toArray(T[] a) { 677 T[] result = a; 678 679 for (int i = 0; i < list.size(); i++) { 680 result[i] = (T) entryFacade.getFacadedElement(list.get(i)); 681 } 682 683 return result; 684 } 685 686 /* (non-Javadoc) 687 * @see java.util.Collection#add(java.lang.Object) 688 */ 689 @Override 690 public boolean add(TYPE e) { 691 throw new UnsupportedOperationException("this collection is read only"); 692 } 693 694 /* (non-Javadoc) 695 * @see java.util.Collection#remove(java.lang.Object) 696 */ 697 @Override 698 public boolean remove(Object o) { 699 throw new UnsupportedOperationException("this collection is read only"); 700 } 701 702 /* (non-Javadoc) 703 * @see java.util.Collection#containsAll(java.util.Collection) 704 */ 705 @Override 706 public boolean containsAll(Collection<?> c) { 707 for (Object candidate : c) { 708 if (!contains(candidate)) { 709 return false; 710 } 711 } 712 713 return true; 714 } 715 716 /* (non-Javadoc) 717 * @see java.util.Collection#addAll(java.util.Collection) 718 */ 719 @Override 720 public boolean addAll(Collection<? extends TYPE> c) { 721 throw new UnsupportedOperationException("this collection is read only"); 722 } 723 724 /* (non-Javadoc) 725 * @see java.util.Collection#removeAll(java.util.Collection) 726 */ 727 @Override 728 public boolean removeAll(Collection<?> c) { 729 throw new UnsupportedOperationException("this collection is read only"); 730 } 731 732 /* (non-Javadoc) 733 * @see java.util.Collection#retainAll(java.util.Collection) 734 */ 735 @Override 736 public boolean retainAll(Collection<?> c) { 737 throw new UnsupportedOperationException("this collection is read only"); 738 } 739 740 /* (non-Javadoc) 741 * @see java.util.Collection#clear() 742 */ 743 @Override 744 public void clear() { 745 throw new UnsupportedOperationException("this collection is read only"); 746 } 747 748 /* (non-Javadoc) 749 * @see java.util.Collection#iterator() 750 */ 751 @Override 752 public Iterator<TYPE> iterator() { 753 return new ReadOnlyCollectionIteratorFacade<TYPE>(list.iterator(), entryFacade); 754 } 755 756 } 757 758 /** 759 * <p> 760 * Implementation of an iterator to facade an iterator on the internal list of symbol entries. 761 * </p> 762 * 763 * @author Patrick Harms 764 */ 765 private class ReadOnlyCollectionIteratorFacade<TYPE> implements Iterator<TYPE> { 766 767 /** 768 * the facaded iterator 769 */ 770 private Iterator<Map.Entry<K, V>> iterator; 771 772 /** 773 * the facade for the entries provided by the facaded iterator 774 */ 775 private EntryFacade<TYPE> entryFacade; 776 777 /** 778 * <p> 779 * initialized this facade with the facaded iterator and the entry facade to be used for 780 * the entries. 781 * </p> 782 */ 783 private ReadOnlyCollectionIteratorFacade(Iterator<Map.Entry<K, V>> iterator, 784 EntryFacade<TYPE> entryFacade) 785 { 786 this.iterator = iterator; 787 this.entryFacade = entryFacade; 788 } 789 790 /* (non-Javadoc) 791 * @see java.util.Iterator#hasNext() 792 */ 793 @Override 794 public boolean hasNext() { 795 return iterator.hasNext(); 796 } 797 798 /* (non-Javadoc) 799 * @see java.util.Iterator#next() 800 */ 801 @Override 802 public TYPE next() { 803 return entryFacade.getFacadedElement(iterator.next()); 804 } 805 806 /* (non-Javadoc) 807 * @see java.util.Iterator#remove() 808 */ 809 @Override 810 public void remove() { 811 throw new UnsupportedOperationException("this iterator is read only"); 812 } 813 814 } 815 816 /** 817 * <p> 818 * Used to facade symbol entries and to return only this part of an entry, that is relevant. 819 * </p> 820 * 821 * @author Patrick Harms 822 */ 823 private abstract class EntryFacade<T> { 824 825 /** 826 * <p> 827 * Returns only the part of an entry that is relevant or required. 828 * </p> 829 * 830 * @param entry of which the part shall be returned 831 * 832 * @return the part of the entry to be returned 833 */ 834 protected abstract T getFacadedElement(Entry<K, V> entry); 835 836 } 837 838 /** 839 * <p> 840 * Implementation of the entry facade returning the entries key, i.e. the symbol. 841 * </p> 842 * 843 * @author Patrick Harms 844 */ 845 private class SymbolFacade extends EntryFacade<K> { 846 847 /* (non-Javadoc) 848 * @see ReadOnlyCollectionIteratorFacade#getFacadedElement(Entry) 849 */ 850 @Override 851 protected K getFacadedElement(Entry<K, V> entry) { 852 return entry.getKey(); 853 } 854 } 855 856 /** 857 * <p> 858 * Implementation of the entry facade returning the entries value, i.e. the value associated to 859 * the symbol. 860 * </p> 861 * 862 * @author Patrick Harms 863 */ 864 private class ValueFacade extends EntryFacade<V> { 865 866 /* (non-Javadoc) 867 * @see ReadOnlyCollectionIteratorFacade#getFacadedElement(Entry) 868 */ 869 @Override 870 protected V getFacadedElement(Entry<K, V> entry) { 871 return entry.getValue(); 872 } 873 } 163 public boolean equals(Object obj); 874 164 875 165 }
Note: See TracChangeset
for help on using the changeset viewer.