source: trunk/autoquest-core-usageprofiles/src/main/java/de/ugoe/cs/autoquest/usageprofiles/SymbolMap.java @ 1251

Last change on this file since 1251 was 1251, checked in by pharms, 11 years ago
  • improved performance of the trie for large alphabets by using the symbol map. This is an improved list of symbols that allows a more efficient lookup for symbols using buckets of symbol as an initial search order
File size: 25.3 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15package de.ugoe.cs.autoquest.usageprofiles;
16
17import java.io.Serializable;
18import java.util.ArrayList;
19import java.util.Arrays;
20import java.util.Collection;
21import java.util.HashMap;
22import java.util.Iterator;
23import java.util.LinkedList;
24import java.util.List;
25import java.util.Map;
26import java.util.Map.Entry;
27
28/**
29 * <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.
36 * </p>
37 * <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.
43 * </p>
44 *
45 * @see SymbolComparator
46 *
47 * @author Patrick Harms
48 */
49public 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    }
138
139    /**
140     * <p>
141     * Returns the size of the map, i.e. the number of symbol entries
142     * </p>
143     *
144     * @return as described
145     */
146    public int size() {
147        return symbolList.size();
148    }
149
150    /**
151     * <p>
152     * Returns true if this map is empty, i.e. if {@link #size()} returns 0
153     * </p>
154     *
155     * @return as described
156     */
157    public boolean isEmpty() {
158        return symbolList.isEmpty();
159    }
160
161    /**
162     * <p>
163     * Returns true if the provided symbol was stored in this map.
164     * </p>
165     *
166     * @param symbol the symbol to check if it was stored in this map
167     *
168     * @return as described
169     *
170     * @throws IllegalArgumentException if the provided symbol is null
171     */
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    }
179
180    /**
181     * <p>
182     * Returns the value associated to the provided symbol in this map. If there is no value
183     * associated to the given symbol or if the symbol is not stored in this map, the method
184     * returns null.
185     * </p>
186     *
187     * @param symbol the symbol to return the value for
188     *
189     * @return as described
190     *
191     * @throws IllegalArgumentException if the provided symbol is null
192     */
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    }
207
208    /**
209     * <p>
210     * Adds a symbol and an associated value to the map. If the value is null, the symbol is added,
211     * anyway and {@link #containsSymbol(Object)} will return true for that symbol. Adding the
212     * same symbol twice will produce two entries. This is contradictory to typical map
213     * implementations. To prevent this, the {@link #containsSymbol(Object)} and
214     * {@link #removeSymbol(Object)} methods should be used to ensure map behavior.
215     * </p>
216     *
217     * @param symbol the symbol to add to the map
218     * @param value  the value to associate to the symbol in this map
219     *
220     * @return as described
221     *
222     * @throws IllegalArgumentException if the provided symbol is null
223     */
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    }
242
243    /**
244     * <p>
245     * Removes a symbol and its associated value from the map. If the symbol is stored several
246     * times, the first of its occurrences is removed.
247     * </p>
248     *
249     * @param symbol the symbol to be removed from the map
250     *
251     * @return as described
252     *
253     * @throws IllegalArgumentException if the provided symbol is null
254     */
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    }
275
276    /**
277     * <p>
278     * Returns a collection of all symbols in this map.
279     * </p>
280     *
281     * @return as described
282     */
283    public Collection<K> getSymbols() {
284        return new ReadOnlyCollectionFacade<K>(symbolList, new SymbolFacade());
285    }
286   
287    /**
288     * <p>
289     * Returns a collection of all values associated to symbols in this map. May contain null
290     * values, if some of the symbols are mapped to null. The length of the returned collection
291     * is in any case the same as the size of the map.
292     * </p>
293     *
294     * @return as described
295     */
296    public Collection<V> getValues() {
297        return new ReadOnlyCollectionFacade<V>(symbolList, new ValueFacade());
298    }
299   
300    /**
301     * <p>
302     * Removes all symbols and associated values from the map.
303     * </p>
304     */
305    public void clear() {
306        symbolList.clear();
307        symbolBuckets = null;
308    }
309
310    /* (non-Javadoc)
311     * @see java.lang.Object#hashCode()
312     */
313    @Override
314    public int hashCode() {
315        return symbolList.size();
316    }
317
318    /* (non-Javadoc)
319     * @see java.lang.Object#equals(java.lang.Object)
320     */
321    @SuppressWarnings("unchecked")
322    @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    }
576
577    /**
578     * <p>
579     * Used to create an efficient facade for accessing the internal list of entries either only
580     * for the symbols or only for the values. It is a default implementation of the collection
581     * interface. The entry facade provided to the constructor decides, if either the list
582     * accesses only the symbols or only the values.
583     * </p>
584     *
585     * @author Patrick Harms
586     */
587    private class ReadOnlyCollectionFacade<TYPE> implements Collection<TYPE> {
588       
589        /**
590         * the list facaded by this facade
591         */
592        private List<Map.Entry<K, V>> list;
593       
594        /**
595         * the facade to be used for the entries
596         */
597        private EntryFacade<TYPE> entryFacade;
598       
599        /**
600         * <p>
601         * Initializes the facade with the facaded list and the facade to be used for the entries
602         * </p>
603         */
604        private ReadOnlyCollectionFacade(List<Map.Entry<K, V>> list, EntryFacade<TYPE> entryFacade)
605        {
606            this.list = list;
607            this.entryFacade = entryFacade;
608        }
609
610        /* (non-Javadoc)
611         * @see java.util.Collection#size()
612         */
613        @Override
614        public int size() {
615            return list.size();
616        }
617
618        /* (non-Javadoc)
619         * @see java.util.Collection#isEmpty()
620         */
621        @Override
622        public boolean isEmpty() {
623            return list.isEmpty();
624        }
625
626        /* (non-Javadoc)
627         * @see java.util.Collection#contains(java.lang.Object)
628         */
629        @Override
630        public boolean contains(Object o) {
631            if (o == null) {
632                for (Map.Entry<K, V> entry : list) {
633                    if (entryFacade.getFacadedElement(entry) == null) {
634                        return true;
635                    }
636                }
637            }
638            else {
639                for (Map.Entry<K, V> entry : list) {
640                    if (o.equals(entryFacade.getFacadedElement(entry))) {
641                        return true;
642                    }
643                }
644            }
645           
646            return false;
647        }
648
649        /* (non-Javadoc)
650         * @see java.util.Collection#toArray()
651         */
652        @Override
653        public Object[] toArray() {
654            Object[] result = new Object[list.size()];
655           
656            for (int i = 0; i < list.size(); i++) {
657                result[i] = entryFacade.getFacadedElement(list.get(i));
658            }
659           
660            return result;
661        }
662
663        /* (non-Javadoc)
664         * @see java.util.Collection#toArray(T[])
665         */
666        @SuppressWarnings("unchecked")
667        @Override
668        public <T> T[] toArray(T[] a) {
669            T[] result = a;
670           
671            for (int i = 0; i < list.size(); i++) {
672                result[i] = (T) entryFacade.getFacadedElement(list.get(i));
673            }
674           
675            return result;
676        }
677
678        /* (non-Javadoc)
679         * @see java.util.Collection#add(java.lang.Object)
680         */
681        @Override
682        public boolean add(TYPE e) {
683            throw new UnsupportedOperationException("this collection is read only");
684        }
685
686        /* (non-Javadoc)
687         * @see java.util.Collection#remove(java.lang.Object)
688         */
689        @Override
690        public boolean remove(Object o) {
691            throw new UnsupportedOperationException("this collection is read only");
692        }
693
694        /* (non-Javadoc)
695         * @see java.util.Collection#containsAll(java.util.Collection)
696         */
697        @Override
698        public boolean containsAll(Collection<?> c) {
699            for (Object candidate : c) {
700                if (!contains(candidate)) {
701                    return false;
702                }
703            }
704           
705            return true;
706        }
707
708        /* (non-Javadoc)
709         * @see java.util.Collection#addAll(java.util.Collection)
710         */
711        @Override
712        public boolean addAll(Collection<? extends TYPE> c) {
713            throw new UnsupportedOperationException("this collection is read only");
714        }
715
716        /* (non-Javadoc)
717         * @see java.util.Collection#removeAll(java.util.Collection)
718         */
719        @Override
720        public boolean removeAll(Collection<?> c) {
721            throw new UnsupportedOperationException("this collection is read only");
722        }
723
724        /* (non-Javadoc)
725         * @see java.util.Collection#retainAll(java.util.Collection)
726         */
727        @Override
728        public boolean retainAll(Collection<?> c) {
729            throw new UnsupportedOperationException("this collection is read only");
730        }
731
732        /* (non-Javadoc)
733         * @see java.util.Collection#clear()
734         */
735        @Override
736        public void clear() {
737            throw new UnsupportedOperationException("this collection is read only");
738        }
739
740        /* (non-Javadoc)
741         * @see java.util.Collection#iterator()
742         */
743        @Override
744        public Iterator<TYPE> iterator() {
745            return new ReadOnlyCollectionIteratorFacade<TYPE>(list.iterator(), entryFacade);
746        }
747       
748    }
749
750    /**
751     * <p>
752     * Implementation of an iterator to facade an iterator on the internal list of symbol entries.
753     * </p>
754     *
755     * @author Patrick Harms
756     */
757    private class ReadOnlyCollectionIteratorFacade<TYPE> implements Iterator<TYPE> {
758       
759        /**
760         * the facaded iterator
761         */
762        private Iterator<Map.Entry<K, V>> iterator;
763       
764        /**
765         * the facade for the entries provided by the facaded iterator
766         */
767        private EntryFacade<TYPE> entryFacade;
768       
769        /**
770         * <p>
771         * initialized this facade with the facaded iterator and the entry facade to be used for
772         * the entries.
773         * </p>
774         */
775        private ReadOnlyCollectionIteratorFacade(Iterator<Map.Entry<K, V>> iterator,
776                                                 EntryFacade<TYPE>         entryFacade)
777        {
778            this.iterator = iterator;
779            this.entryFacade = entryFacade;
780        }
781
782        /* (non-Javadoc)
783         * @see java.util.Iterator#hasNext()
784         */
785        @Override
786        public boolean hasNext() {
787            return iterator.hasNext();
788        }
789
790        /* (non-Javadoc)
791         * @see java.util.Iterator#next()
792         */
793        @Override
794        public TYPE next() {
795            return entryFacade.getFacadedElement(iterator.next());
796        }
797
798        /* (non-Javadoc)
799         * @see java.util.Iterator#remove()
800         */
801        @Override
802        public void remove() {
803            throw new UnsupportedOperationException("this iterator is read only");
804        }
805       
806    }
807       
808    /**
809     * <p>
810     * Used to facade symbol entries and to return only this part of an entry, that is relevant.
811     * </p>
812     *
813     * @author Patrick Harms
814     */
815    private abstract class EntryFacade<T> {
816       
817        /**
818         * <p>
819         * Returns only the part of an entry that is relevant or required.
820         * </p>
821         *
822         * @param entry of which the part shall be returned
823         *
824         * @return the part of the entry to be returned
825         */
826        protected abstract T getFacadedElement(Entry<K, V> entry);
827       
828    }
829   
830    /**
831     * <p>
832     * Implementation of the entry facade returning the entries key, i.e. the symbol.
833     * </p>
834     *
835     * @author Patrick Harms
836     */
837    private class SymbolFacade extends EntryFacade<K> {
838
839        /* (non-Javadoc)
840         * @see ReadOnlyCollectionIteratorFacade#getFacadedElement(Entry)
841         */
842        @Override
843        protected K getFacadedElement(Entry<K, V> entry) {
844            return entry.getKey();
845        }
846    }
847   
848    /**
849     * <p>
850     * Implementation of the entry facade returning the entries value, i.e. the value associated to
851     * the symbol.
852     * </p>
853     *
854     * @author Patrick Harms
855     */
856    private class ValueFacade extends EntryFacade<V> {
857
858        /* (non-Javadoc)
859         * @see ReadOnlyCollectionIteratorFacade#getFacadedElement(Entry)
860         */
861        @Override
862        protected V getFacadedElement(Entry<K, V> entry) {
863            return entry.getValue();
864        }
865    }
866
867}
Note: See TracBrowser for help on using the repository browser.