source: trunk/autoquest-core-tasktrees/src/main/java/de/ugoe/cs/autoquest/tasktrees/temporalrelation/TaskSymbolBucketedMap.java @ 2146

Last change on this file since 2146 was 2129, checked in by pharms, 8 years ago
  • improved readability of bucket handling stuff and bugfixed the unallowed sorting of the search order arrays
File size: 31.8 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.tasktrees.temporalrelation;
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
28import de.ugoe.cs.autoquest.eventcore.Event;
29import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask;
30import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance;
31import de.ugoe.cs.autoquest.tasktrees.treeifc.IIteration;
32import de.ugoe.cs.autoquest.tasktrees.treeifc.IOptional;
33import de.ugoe.cs.autoquest.tasktrees.treeifc.ISelection;
34import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence;
35import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
36import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
37import de.ugoe.cs.autoquest.usageprofiles.SymbolMap;
38
39/**
40 * <p>
41 * This class is a data structure for holding task instances in form of a symbol map. It is more
42 * efficient than a simple list. This data structure can be used with a comparator to adapt the
43 * effective list behavior and to define the equals strategy for comparing task instances. After a
44 * certain size ({@link #MAX_LIST_SIZE}), the map creates an index consisting of buckets of similar
45 * task instances. This allows searching for task instances in a more efficient order as the search
46 * can start in the most appropriate of the internal buckets.
47 * </p>
48 * <p>
49 * The class is called a map, although it is not. It may contain the same task instances as
50 * separate keys. This implementation is done for performance improvements. If it is required to
51 * really assure, that a key exists only once, then each call to the
52 * {@link #addSymbol(Object, Object)} method should be done only, if the
53 * {@link #containsSymbol(Object)} method for the same symbol returns false.
54 * </p>
55 *
56 * @see SymbolComparator
57 *
58 * @author Patrick Harms
59 * @param <V>
60 */
61class TaskSymbolBucketedMap<V> implements SymbolMap<ITask, V>, Serializable {
62
63    /**
64     * <p>
65     * default serial version UID
66     * </p>
67     */
68    private static final long serialVersionUID = 1L;
69
70    /**
71     * <p>
72     * the maximum number of task instances in this map which is still only treated as list
73     * instead of using buckets.
74     * </p>
75     */
76    private static final int MAX_LIST_SIZE = 15;
77   
78    /**
79     * <p>
80     * the id of the default event bucket
81     * </p>
82     */
83    private static final int DEFAULT_EVENT_BUCKET_ID = 4;
84   
85    /**
86     * <p>
87     * the id of the default sequence bucket
88     * </p>
89     */
90    private static final int DEFAULT_SEQUENCE_BUCKET_ID = 0;
91   
92    /**
93     * <p>
94     * the id of the default selection bucket
95     * </p>
96     */
97    private static final int DEFAULT_SELECTION_BUCKET_ID = 1;
98   
99    /**
100     * <p>
101     * the id of the default iteration bucket
102     * </p>
103     */
104    private static final int DEFAULT_ITERATION_BUCKET_ID = 2;
105   
106    /**
107     * <p>
108     * the id of the default optional bucket
109     * </p>
110     */
111    private static final int DEFAULT_OPTIONAL_BUCKET_ID = 3;
112   
113    /**
114     * <p>
115     * the default bucket search order for events
116     * </p>
117     */
118    private static final int[] DEFAULT_EVENT_BUCKET_SEARCH_ORDER = new int[]
119        { DEFAULT_EVENT_BUCKET_ID, DEFAULT_ITERATION_BUCKET_ID, DEFAULT_OPTIONAL_BUCKET_ID,
120          DEFAULT_SELECTION_BUCKET_ID };
121   
122    /**
123     * <p>
124     * the default bucket search order for sequences
125     * </p>
126     */
127    private static final int[] DEFAULT_SEQUENCE_BUCKET_SEARCH_ORDER = new int[]
128        { DEFAULT_SEQUENCE_BUCKET_ID, DEFAULT_ITERATION_BUCKET_ID, DEFAULT_OPTIONAL_BUCKET_ID,
129          DEFAULT_SELECTION_BUCKET_ID };
130   
131    /**
132     * <p>
133     * the default bucket search order for selection
134     * </p>
135     */
136    private static final int[] DEFAULT_SELECTION_BUCKET_SEARCH_ORDER = new int[]
137        { DEFAULT_SELECTION_BUCKET_ID, DEFAULT_EVENT_BUCKET_ID, DEFAULT_ITERATION_BUCKET_ID,
138          DEFAULT_OPTIONAL_BUCKET_ID, DEFAULT_SEQUENCE_BUCKET_ID };
139   
140    /**
141     * <p>
142     * the default bucket search order for iterations
143     * </p>
144     */
145    private static final int[] DEFAULT_ITERATION_BUCKET_SEARCH_ORDER = new int[]
146        { DEFAULT_ITERATION_BUCKET_ID, DEFAULT_OPTIONAL_BUCKET_ID, DEFAULT_SELECTION_BUCKET_ID,
147          DEFAULT_EVENT_BUCKET_ID, DEFAULT_SEQUENCE_BUCKET_ID };
148   
149    /**
150     * <p>
151     * the default bucket search order for optional
152     * </p>
153     */
154    private static final int[] DEFAULT_OPTIONAL_BUCKET_SEARCH_ORDER = new int[]
155        { DEFAULT_OPTIONAL_BUCKET_ID, DEFAULT_EVENT_BUCKET_ID, DEFAULT_ITERATION_BUCKET_ID,
156          DEFAULT_SELECTION_BUCKET_ID, DEFAULT_SEQUENCE_BUCKET_ID };
157   
158    /**
159     * <p>
160     * Comparator to be used for comparing the task instances with each other
161     * </p>
162     */
163    private TaskComparator comparator;
164
165    /**
166     * <p>
167     * Internally maintained plain list of task instances and associated values
168     * </p>
169     */
170    private List<Map.Entry<ITask, V>> symbolList;
171
172    /**
173     * <p>
174     * If the size of the map exceeds {@link #MAX_LIST_SIZE}, this is the index using buckets
175     * for optimizing the search order.
176     * </p>
177     */
178    private Map<Integer, List<Map.Entry<ITask, V>>> symbolBuckets;
179   
180    /**
181     * <p>
182     * When using buckets, not any task instance may be associated a correct bucket. Therefore, we
183     * set a default bucket for all such task instances. This may change if the same bucket for a
184     * specific symbol is selected.
185     * </p>
186     */
187    private int defaultBucket = 0;
188   
189    /**
190     * <p>
191     * the adjustable bucket search order for events. This array is initialized for reuse as
192     * otherwise for any lookup of a bucket search order for an individual event, this array would
193     * be recreated.
194     * </p>
195     */
196    private final int[] ADJUSTABLE_EVENT_SPECIFIC_BUCKET_SEARCH_ORDER = new int[]
197        { 0, 0, DEFAULT_ITERATION_BUCKET_ID, DEFAULT_OPTIONAL_BUCKET_ID, DEFAULT_EVENT_BUCKET_ID,
198          DEFAULT_SELECTION_BUCKET_ID };
199   
200    /**
201     * <p>
202     * Instantiates a task instance map with a comparator
203     * </p>
204     *
205     * @param comparator the comparator to use for comparing task instances
206     *
207     * @throws IllegalArgumentException if the provided comparator is null
208     */
209    public TaskSymbolBucketedMap(TaskComparator comparator) {
210        if (comparator == null) {
211            throw new IllegalArgumentException("comparator must not be null");
212        }
213       
214        this.comparator = comparator;
215        this.symbolList = new ArrayList<Map.Entry<ITask, V>>();
216    }
217
218    /**
219     * <p>
220     * Copy constructor
221     * </p>
222     *
223     * @param otherMap the other map to be copied including its comparator
224     *
225     * @throws IllegalArgumentException if the provided other map is null
226     */
227    public TaskSymbolBucketedMap(TaskSymbolBucketedMap<V> otherMap) {
228        if (otherMap == null) {
229            throw new IllegalArgumentException("otherMap must not be null");
230        }
231
232        this.comparator = otherMap.comparator;
233        this.symbolList = new ArrayList<Map.Entry<ITask, V>>(otherMap.symbolList);
234       
235        if (this.symbolList.size() > MAX_LIST_SIZE) {
236            createSymbolBuckets();
237        }
238    }
239
240    /**
241     * <p>
242     * Returns the size of the map, i.e. the number of task instance entries
243     * </p>
244     *
245     * @return as described
246     */
247    public int size() {
248        return symbolList.size();
249    }
250
251    /**
252     * <p>
253     * Returns true if this map is empty, i.e. if {@link #size()} returns 0
254     * </p>
255     *
256     * @return as described
257     */
258    public boolean isEmpty() {
259        return symbolList.isEmpty();
260    }
261
262    /**
263     * <p>
264     * Returns true if the provided task instance was stored in this map.
265     * </p>
266     *
267     * @param symbol the task instance to check if it was stored in this map
268     *
269     * @return as described
270     *
271     * @throws IllegalArgumentException if the provided task instance is null
272     */
273    public boolean containsSymbol(ITask symbol) {
274        if (symbol == null) {
275            throw new IllegalArgumentException("symbol must not be null");
276        }
277       
278        return getEntry(symbol) != null;
279    }
280
281    /**
282     * <p>
283     * Returns the value associated to the provided task instance in this map. If there is no value
284     * associated to the given task instance or if the task instance is not stored in this map,
285     * the method returns null.
286     * </p>
287     *
288     * @param symbol the task instance to return the value for
289     *
290     * @return as described
291     *
292     * @throws IllegalArgumentException if the provided task instance is null
293     */
294    public V getValue(ITask symbol) {
295        if (symbol == null) {
296            throw new IllegalArgumentException("symbol must not be null");
297        }
298       
299        Map.Entry<ITask, V> entry = getEntry(symbol);
300       
301        if (entry != null) {
302            return entry.getValue();
303        }
304        else {
305            return null;
306        }
307    }
308
309    /**
310     * <p>
311     * Adds a task instance and an associated value to the map. If the value is null, the task
312     * instance is added, anyway and {@link #containsSymbol(Object)} will return true for that
313     * task instance. Adding the same task instance twice will produce two entries. This is
314     * contradictory to typical map implementations. To prevent this, the
315     * {@link #containsSymbol(Object)} and {@link #removeSymbol(Object)} methods should be used to
316     * ensure map behavior.
317     * </p>
318     *
319     * @param symbol the task instance to add to the map
320     * @param value  the value to associate to the task instance in this map
321     *
322     * @return as described
323     *
324     * @throws IllegalArgumentException if the provided task instance is null
325     */
326    public void addSymbol(ITask symbol, V value) {
327        if (symbol == null) {
328            throw new IllegalArgumentException("symbol must not be null");
329        }
330       
331        Map.Entry<ITask, V> entry = new SymbolMapEntry(symbol, value);
332       
333        symbolList.add(entry);
334           
335        if (symbolList.size() > MAX_LIST_SIZE) {
336            if (symbolBuckets == null) {
337                createSymbolBuckets();
338            }
339            else {
340                addToSymbolBucket(entry);
341            }
342        }
343    }
344
345    /**
346     * <p>
347     * Removes a task instance and its associated value from the map. If the task instance is
348     * stored several times, only the first of its occurrences is removed.
349     * </p>
350     *
351     * @param symbol the task instance to be removed from the map
352     *
353     * @return as described
354     *
355     * @throws IllegalArgumentException if the provided task instance is null
356     */
357    public V removeSymbol(ITask symbol) {
358        if (symbol == null) {
359            throw new IllegalArgumentException("symbol must not be null");
360        }
361       
362        for (int i = 0; i < symbolList.size(); i++) {
363            if (comparator.equals(symbolList.get(i).getKey(), symbol)) {
364                // found the symbol. Remove it from the list, and if required, also from the map.
365                V value = symbolList.remove(i).getValue();
366               
367                if (symbolList.size() > MAX_LIST_SIZE) {
368                    removeFromSymbolBuckets(symbol);
369                }
370               
371                return value;
372            }
373        }
374       
375        return null;
376    }
377
378    /**
379     * <p>
380     * Returns a collection of all task instances in this map.
381     * </p>
382     *
383     * @return as described
384     */
385    public Collection<ITask> getSymbols() {
386        return new ReadOnlyCollectionFacade<ITask>(symbolList, new SymbolFacade());
387    }
388   
389    /**
390     * <p>
391     * Returns a collection of all values associated to task instances in this map. May contain
392     * null values, if some of the task instances are mapped to null. The length of the returned
393     * collection is in any case the same as the size of the map.
394     * </p>
395     *
396     * @return as described
397     */
398    public Collection<V> getValues() {
399        return new ReadOnlyCollectionFacade<V>(symbolList, new ValueFacade());
400    }
401   
402    /**
403     * <p>
404     * Removes all task instances and associated values from the map.
405     * </p>
406     */
407    public void clear() {
408        symbolList.clear();
409        symbolBuckets = null;
410    }
411
412    /* (non-Javadoc)
413     * @see java.lang.Object#hashCode()
414     */
415    @Override
416    public int hashCode() {
417        return symbolList.size();
418    }
419
420    /* (non-Javadoc)
421     * @see java.lang.Object#equals(java.lang.Object)
422     */
423    @SuppressWarnings("unchecked")
424    @Override
425    public boolean equals(Object obj) {
426        if (this == obj) {
427            return true;
428        }
429        else if (this.getClass().isInstance(obj)) {
430            TaskSymbolBucketedMap<V> other = (TaskSymbolBucketedMap<V>) obj;
431           
432            return (symbolList.size() == other.symbolList.size()) &&
433                   (symbolList.containsAll(other.symbolList));
434        }
435        else {
436            return false;
437        }
438    }
439
440    /**
441     * <p>
442     * Internally used to create task instance buckets in case the number of stored task instances
443     * increased above {@link #MAX_LIST_SIZE}.
444     * </p>
445     */
446    private void createSymbolBuckets() {
447        symbolBuckets = new HashMap<Integer, List<Map.Entry<ITask, V>>>();
448       
449        for (Map.Entry<ITask, V> symbol : symbolList) {
450            addToSymbolBucket(symbol);
451        }
452    }
453
454    /**
455     * <p>
456     * Adds a task instance and its value to its corresponding bucket. The corresponding bucket is
457     * the first element of the array returned by the method
458     * {@link #getBucketSearchOrder(ITaskInstance)}. If no search order for a task instance is
459     * defined, the entry is added to the default bucket. If the bucket id identical to the default
460     * bucket id, the default bucket id is shifted to another value.
461     * </p>
462     */
463    private void addToSymbolBucket(Map.Entry<ITask, V> symbolEntry) {
464        int bucketId = defaultBucket;
465        int[] bucketSearchOrder = getBucketSearchOrder(symbolEntry.getKey());
466       
467        if ((bucketSearchOrder != null) && (bucketSearchOrder.length > 0)) {
468            bucketId = bucketSearchOrder[0];
469           
470            if (bucketId == defaultBucket) {
471                setNewDefaultBucketId();
472            }
473        }
474       
475        List<Map.Entry<ITask, V>> list = symbolBuckets.get(bucketId);
476       
477        if (list == null) {
478            list = new LinkedList<Map.Entry<ITask, V>>();
479            symbolBuckets.put(bucketId, list);
480        }
481       
482        list.add(symbolEntry);
483    }
484   
485    /**
486     * <p>
487     * returns the search order for a task instance which is different for event task instances,
488     * sequence instances, selection instances, and iteration instances
489     * </p>
490     */
491    private int[] getBucketSearchOrder(ITask task) {
492        // 0 = sequence; 1 = selection; 2 = iteration; 3 = optional; 4 = event task in general;
493        // other = hashCode of name of event type
494       
495        if (task instanceof IEventTask) {
496            // event tasks are most likely equal to those of the same type happening on the same
497            // target. Afterwards, they should be equal to those of the event type with the same
498            // name. Afterwards, they may be equal to iterations, optionals, other event tasks,
499            // selections, and finally the rest.
500           
501            Collection<ITaskInstance> instances = task.getInstances();
502           
503            if (instances.size() > 0) {
504                Event event = ((IEventTaskInstance) instances.iterator().next()).getEvent();
505               
506                ADJUSTABLE_EVENT_SPECIFIC_BUCKET_SEARCH_ORDER[0] =
507                    event.getTarget().hashCode() + event.getType().getName().hashCode();
508                ADJUSTABLE_EVENT_SPECIFIC_BUCKET_SEARCH_ORDER[1] =
509                    event.getType().getName().hashCode();
510               
511                return ADJUSTABLE_EVENT_SPECIFIC_BUCKET_SEARCH_ORDER;
512            }
513            else {
514                return DEFAULT_EVENT_BUCKET_SEARCH_ORDER;
515            }
516        }
517        else if (task instanceof ISequence) {
518            return DEFAULT_SEQUENCE_BUCKET_SEARCH_ORDER;
519        }
520        else if (task instanceof ISelection) {
521            return DEFAULT_SELECTION_BUCKET_SEARCH_ORDER;
522        }
523        else if (task instanceof IIteration) {
524            return DEFAULT_ITERATION_BUCKET_SEARCH_ORDER;
525        }
526        else if (task instanceof IOptional) {
527            return DEFAULT_OPTIONAL_BUCKET_SEARCH_ORDER;
528        }
529       
530        return null;
531    }
532
533    /**
534     * <p>
535     * Removes the entry for a given task instance from the buckets. It uses the bucket search order
536     * defined to find the task instance as fast as possible.
537     * </p>
538     */
539    private Map.Entry<ITask, V> removeFromSymbolBuckets(ITask symbol) {
540        int bucketId = defaultBucket;
541        int[] bucketSearchOrder = getBucketSearchOrder(symbol);
542       
543        if ((bucketSearchOrder != null) && (bucketSearchOrder.length > 0)) {
544            bucketId = bucketSearchOrder[0];
545        }
546       
547        List<Map.Entry<ITask, V>> list = symbolBuckets.get(bucketId);
548        Map.Entry<ITask, V> result = null;
549       
550        if (list != null) {
551            for (int i = 0; i < list.size(); i++) {
552                if (comparator.equals(list.get(i).getKey(), symbol)) {
553                    result = list.remove(i);
554                    break;
555                }
556            }
557           
558            if (list.isEmpty()) {
559                symbolBuckets.remove(bucketId);
560            }
561        }
562       
563        return result;
564    }
565
566    /**
567     * <p>
568     * Updates the default bucket id to a new one
569     * </p>
570     */
571    private void setNewDefaultBucketId() {
572        int oldDefaultBucket = defaultBucket;
573        do {
574            defaultBucket += 1;
575        }
576        while (symbolBuckets.containsKey(defaultBucket));
577       
578        symbolBuckets.put(defaultBucket, symbolBuckets.remove(oldDefaultBucket));
579    }
580
581    /**
582     * <p>
583     * searches for the entry belonging to the given task instance. The method either uses the
584     * list if buckets are not used yet, or it uses the buckets and searches them in the order
585     * defined by the method {@link #getBucketSearchOrder(ITaskInstance)}. If the task instances
586     * isn't found and the bucket search order does not refer all buckets, then also the other
587     * buckets are searched for the task instance.
588     * </p>
589     */
590    private Map.Entry<ITask, V> getEntry(ITask symbol) {
591        Map.Entry<ITask, V> entry = null;
592        if (symbolBuckets == null) {
593            entry = lookup(symbol, symbolList);
594        }
595        else {
596            int[] bucketSearchOrder = getBucketSearchOrder(symbol);
597            for (int bucketId : bucketSearchOrder) {
598                List<Map.Entry<ITask, V>> list = symbolBuckets.get(bucketId);
599                if (list != null) {
600                    entry = lookup(symbol, list);
601                    if (entry != null) {
602                        break;
603                    }
604                }
605            }
606           
607            // try to search the other buckets (may be required, if bucket separation given by
608            // bucket ids is different from logical bucket separation of comparator
609            if (entry == null) {
610                // create a copy of the search order to be able to sort and afterwards binary
611                // search it. The original version MUST NOT BE SORTED.
612                int[] bucketSearchOrderCopy =
613                    Arrays.copyOf(bucketSearchOrder, bucketSearchOrder.length);
614               
615                Arrays.sort(bucketSearchOrderCopy);
616                for (Map.Entry<Integer, List<Map.Entry<ITask, V>>> bucket : symbolBuckets.entrySet()) {
617                    if (Arrays.binarySearch(bucketSearchOrderCopy, bucket.getKey()) < 0) {
618                        List<Map.Entry<ITask, V>> list = bucket.getValue();
619                        if (list != null) {
620                            entry = lookup(symbol, list);
621                            if (entry != null) {
622                                break;
623                            }
624                        }
625                    }
626                }
627            }
628        }
629       
630        return entry;
631    }
632
633    /**
634     * <p>
635     * Convenience method to look up a symbol in a list of entries using the comparator.
636     * </p>
637     */
638    private Map.Entry<ITask, V> lookup(ITask symbol, List<Map.Entry<ITask, V>> list) {
639        for (Map.Entry<ITask, V> candidate : list) {
640            if (comparator.equals(candidate.getKey(), symbol)) {
641                return candidate;
642            }
643        }
644       
645        return null;
646    }
647
648    /**
649     * <p>
650     * Internally used data structure for storing task instance - value pairs
651     * </p>
652     *
653     * @author Patrick Harms
654     */
655    private class SymbolMapEntry implements Map.Entry<ITask, V> {
656       
657        /**
658         * the task instance to map to a value
659         */
660        private ITask symbol;
661       
662        /**
663         * the value associated with the symbol
664         */
665        private V value;
666
667        /**
668         * <p>
669         * Simple constructor for initializing the entry with a task instance and its associated
670         * value.
671         * </p>
672         */
673        private SymbolMapEntry(ITask symbol, V value) {
674            super();
675            this.symbol = symbol;
676            this.value = value;
677        }
678
679        /* (non-Javadoc)
680         * @see java.util.Map.Entry#getKey()
681         */
682        @Override
683        public ITask getKey() {
684            return symbol;
685        }
686
687        /* (non-Javadoc)
688         * @see java.util.Map.Entry#getValue()
689         */
690        @Override
691        public V getValue() {
692            return value;
693        }
694
695        /* (non-Javadoc)
696         * @see java.util.Map.Entry#setValue(java.lang.Object)
697         */
698        @Override
699        public V setValue(V value) {
700            V oldValue = this.value;
701            this.value = value;
702            return oldValue;
703        }
704
705        /* (non-Javadoc)
706         * @see java.lang.Object#hashCode()
707         */
708        @Override
709        public int hashCode() {
710            return symbol.hashCode();
711        }
712
713        /* (non-Javadoc)
714         * @see java.lang.Object#equals(java.lang.Object)
715         */
716        @SuppressWarnings("unchecked")
717        @Override
718        public boolean equals(Object obj) {
719            if (this == obj) {
720                return true;
721            }
722            else if (this.getClass().isInstance(obj)) {
723                SymbolMapEntry other = (SymbolMapEntry) obj;
724                return (symbol.equals(other.symbol) &&
725                        (value == null ? other.value == null : value.equals(other.value)));
726            }
727            else {
728                return false;
729            }
730        }
731
732        /* (non-Javadoc)
733         * @see java.lang.Object#toString()
734         */
735        @Override
736        public String toString() {
737            return symbol + "=" + value;
738        }
739
740    }
741
742    /**
743     * <p>
744     * Used to create an efficient facade for accessing the internal list of entries either only
745     * for the task instances or only for the values. It is a default implementation of the
746     * collection interface. The entry facade provided to the constructor decides, if either the
747     * list accesses only the task instances or only the values.
748     * </p>
749     *
750     * @author Patrick Harms
751     */
752    private class ReadOnlyCollectionFacade<TYPE> implements Collection<TYPE> {
753       
754        /**
755         * the list facaded by this facade
756         */
757        private List<Map.Entry<ITask, V>> list;
758       
759        /**
760         * the facade to be used for the entries
761         */
762        private EntryFacade<TYPE> entryFacade;
763       
764        /**
765         * <p>
766         * Initializes the facade with the facaded list and the facade to be used for the entries
767         * </p>
768         */
769        private ReadOnlyCollectionFacade(List<Map.Entry<ITask, V>> list,
770                                         EntryFacade<TYPE>         entryFacade)
771        {
772            this.list = list;
773            this.entryFacade = entryFacade;
774        }
775
776        /* (non-Javadoc)
777         * @see java.util.Collection#size()
778         */
779        @Override
780        public int size() {
781            return list.size();
782        }
783
784        /* (non-Javadoc)
785         * @see java.util.Collection#isEmpty()
786         */
787        @Override
788        public boolean isEmpty() {
789            return list.isEmpty();
790        }
791
792        /* (non-Javadoc)
793         * @see java.util.Collection#contains(java.lang.Object)
794         */
795        @Override
796        public boolean contains(Object o) {
797            if (o == null) {
798                for (Map.Entry<ITask, V> entry : list) {
799                    if (entryFacade.getFacadedElement(entry) == null) {
800                        return true;
801                    }
802                }
803            }
804            else {
805                for (Map.Entry<ITask, V> entry : list) {
806                    if (o.equals(entryFacade.getFacadedElement(entry))) {
807                        return true;
808                    }
809                }
810            }
811           
812            return false;
813        }
814
815        /* (non-Javadoc)
816         * @see java.util.Collection#toArray()
817         */
818        @Override
819        public Object[] toArray() {
820            Object[] result = new Object[list.size()];
821           
822            for (int i = 0; i < list.size(); i++) {
823                result[i] = entryFacade.getFacadedElement(list.get(i));
824            }
825           
826            return result;
827        }
828
829        /* (non-Javadoc)
830         * @see java.util.Collection#toArray(T[])
831         */
832        @SuppressWarnings("unchecked")
833        @Override
834        public <T> T[] toArray(T[] a) {
835            T[] result = a;
836           
837            for (int i = 0; i < list.size(); i++) {
838                result[i] = (T) entryFacade.getFacadedElement(list.get(i));
839            }
840           
841            return result;
842        }
843
844        /* (non-Javadoc)
845         * @see java.util.Collection#add(java.lang.Object)
846         */
847        @Override
848        public boolean add(TYPE e) {
849            throw new UnsupportedOperationException("this collection is read only");
850        }
851
852        /* (non-Javadoc)
853         * @see java.util.Collection#remove(java.lang.Object)
854         */
855        @Override
856        public boolean remove(Object o) {
857            throw new UnsupportedOperationException("this collection is read only");
858        }
859
860        /* (non-Javadoc)
861         * @see java.util.Collection#containsAll(java.util.Collection)
862         */
863        @Override
864        public boolean containsAll(Collection<?> c) {
865            for (Object candidate : c) {
866                if (!contains(candidate)) {
867                    return false;
868                }
869            }
870           
871            return true;
872        }
873
874        /* (non-Javadoc)
875         * @see java.util.Collection#addAll(java.util.Collection)
876         */
877        @Override
878        public boolean addAll(Collection<? extends TYPE> c) {
879            throw new UnsupportedOperationException("this collection is read only");
880        }
881
882        /* (non-Javadoc)
883         * @see java.util.Collection#removeAll(java.util.Collection)
884         */
885        @Override
886        public boolean removeAll(Collection<?> c) {
887            throw new UnsupportedOperationException("this collection is read only");
888        }
889
890        /* (non-Javadoc)
891         * @see java.util.Collection#retainAll(java.util.Collection)
892         */
893        @Override
894        public boolean retainAll(Collection<?> c) {
895            throw new UnsupportedOperationException("this collection is read only");
896        }
897
898        /* (non-Javadoc)
899         * @see java.util.Collection#clear()
900         */
901        @Override
902        public void clear() {
903            throw new UnsupportedOperationException("this collection is read only");
904        }
905
906        /* (non-Javadoc)
907         * @see java.util.Collection#iterator()
908         */
909        @Override
910        public Iterator<TYPE> iterator() {
911            return new ReadOnlyCollectionIteratorFacade<TYPE>(list.iterator(), entryFacade);
912        }
913       
914    }
915
916    /**
917     * <p>
918     * Implementation of an iterator to facade an iterator on the internal list of task instance
919     * entries.
920     * </p>
921     *
922     * @author Patrick Harms
923     */
924    private class ReadOnlyCollectionIteratorFacade<TYPE> implements Iterator<TYPE> {
925       
926        /**
927         * the facaded iterator
928         */
929        private Iterator<Map.Entry<ITask, V>> iterator;
930       
931        /**
932         * the facade for the entries provided by the facaded iterator
933         */
934        private EntryFacade<TYPE> entryFacade;
935       
936        /**
937         * <p>
938         * initialized this facade with the facaded iterator and the entry facade to be used for
939         * the entries.
940         * </p>
941         */
942        private ReadOnlyCollectionIteratorFacade(Iterator<Map.Entry<ITask, V>> iterator,
943                                                 EntryFacade<TYPE>             entryFacade)
944        {
945            this.iterator = iterator;
946            this.entryFacade = entryFacade;
947        }
948
949        /* (non-Javadoc)
950         * @see java.util.Iterator#hasNext()
951         */
952        @Override
953        public boolean hasNext() {
954            return iterator.hasNext();
955        }
956
957        /* (non-Javadoc)
958         * @see java.util.Iterator#next()
959         */
960        @Override
961        public TYPE next() {
962            return entryFacade.getFacadedElement(iterator.next());
963        }
964
965        /* (non-Javadoc)
966         * @see java.util.Iterator#remove()
967         */
968        @Override
969        public void remove() {
970            throw new UnsupportedOperationException("this iterator is read only");
971        }
972       
973    }
974       
975    /**
976     * <p>
977     * Used to facade task instance entries and to return only this part of an entry, that is
978     * relevant.
979     * </p>
980     *
981     * @author Patrick Harms
982     */
983    private abstract class EntryFacade<T> {
984       
985        /**
986         * <p>
987         * Returns only the part of an entry that is relevant or required.
988         * </p>
989         *
990         * @param entry of which the part shall be returned
991         *
992         * @return the part of the entry to be returned
993         */
994        protected abstract T getFacadedElement(Entry<ITask, V> entry);
995       
996    }
997   
998    /**
999     * <p>
1000     * Implementation of the entry facade returning the entries key, i.e. the symbol.
1001     * </p>
1002     *
1003     * @author Patrick Harms
1004     */
1005    private class SymbolFacade extends EntryFacade<ITask> {
1006
1007        /* (non-Javadoc)
1008         * @see ReadOnlyCollectionIteratorFacade#getFacadedElement(Entry)
1009         */
1010        @Override
1011        protected ITask getFacadedElement(Entry<ITask, V> entry) {
1012            return entry.getKey();
1013        }
1014    }
1015   
1016    /**
1017     * <p>
1018     * Implementation of the entry facade returning the entries value, i.e. the value associated to
1019     * the symbol.
1020     * </p>
1021     *
1022     * @author Patrick Harms
1023     */
1024    private class ValueFacade extends EntryFacade<V> {
1025
1026        /* (non-Javadoc)
1027         * @see ReadOnlyCollectionIteratorFacade#getFacadedElement(Entry)
1028         */
1029        @Override
1030        protected V getFacadedElement(Entry<ITask, V> entry) {
1031            return entry.getValue();
1032        }
1033    }
1034
1035}
Note: See TracBrowser for help on using the repository browser.