File: UniqueList.java

package info (click to toggle)
libglazedlists-java 1.9.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 3,024 kB
  • ctags: 4,252
  • sloc: java: 22,561; xml: 818; sh: 51; makefile: 5
file content (328 lines) | stat: -rw-r--r-- 13,188 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists;

import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.impl.Grouper;
import ca.odell.glazedlists.impl.adt.BarcodeIterator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * An {@link EventList} that shows the unique elements from its source
 * {@link EventList}. For example, the source list {A, A, B, C, C, C, D} would
 * be simplified to {A, B, C, D} by this UniqueList.
 *
 * <p><strong><font color="#FF0000">Warning:</font></strong> This class breaks
 * the contract required by {@link List}. See {@link EventList} for an example.
 *
 * <p><strong><font color="#FF0000">Warning:</font></strong> This class is
 * thread ready but not thread safe. See {@link EventList} for an example
 * of thread safe code.
 *
 * <p><table border="1" width="100%" cellpadding="3" cellspacing="0">
 * <tr class="TableHeadingColor"><td colspan=2><font size="+2"><b>EventList Overview</b></font></td></tr>
 * <tr><td class="TableSubHeadingColor"><b>Writable:</b></td><td>yes</td></tr>
 * <tr><td class="TableSubHeadingColor"><b>Concurrency:</b></td><td>thread ready, not thread safe</td></tr>
 * <tr><td class="TableSubHeadingColor"><b>Performance:</b></td><td></td></tr>
 * <tr><td class="TableSubHeadingColor"><b>Memory:</b></td><td>N/A</td></tr>
 * <tr><td class="TableSubHeadingColor"><b>Unit Tests:</b></td><td>N/A</td></tr>
 * <tr><td class="TableSubHeadingColor"><b>Issues:</b></td><td>
 *   <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=27">27</a>
 *   <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=34">34</a>
 *   <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=35">35</a>
 *   <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=45">45</a>
 *   <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=46">46</a>
 *   <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=55">55</a>
 *   <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=58">58</a>
 *   <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=114">114</a>
 * </td></tr>
 * </table>
 *
 * @author <a href="mailto:kevin@swank.ca">Kevin Maltby</a>
 * @author James Lemieux
 * @author <a href="mailto:jesse@swank.ca">Jesse Wilson</a>
 */
public final class UniqueList<E> extends TransformedList<E, E> {

    /** the grouping service manages collapsing out duplicates */
    private final Grouper<E> grouper;

    /**
     * Creates a {@link UniqueList} that determines uniqueness via the
     * {@link Comparable} interface. All elements of the source {@link EventList}
     * must implement {@link Comparable}.
     *
     * @param source the {@link EventList} containing duplicates to remove
     */
    public static <E extends Comparable<? super E>> UniqueList<E> create(EventList<E> source) {
        return new UniqueList<E>(source);
    }

    /**
     * Creates a {@link UniqueList} that determines uniqueness via the
     * {@link Comparable} interface. All elements of the source {@link EventList}
     * must implement {@link Comparable}.
     * <p>Usage of factory method {@link #create(EventList)} is preferable.
     *
     * @param source the {@link EventList} containing duplicates to remove
     */
    public UniqueList(EventList<E> source) {
        this(source, (Comparator<E>) GlazedLists.comparableComparator());
    }

    /**
     * Creates a {@link UniqueList} that determines uniqueness using the
     * specified {@link Comparator}.
     *
     * @param source the {@link EventList} containing duplicates to remove
     * @param comparator the {@link Comparator} used to determine equality
     */
    public UniqueList(EventList<E> source, Comparator<? super E> comparator) {
        this(new SortedList<E>(source, comparator), (Void) null);
    }

    /**
     * A private constructor which allows us to use the {@link SortedList} as
     * the main decorated {@link EventList}.
     *
     * <p>The current implementation of {@link UniqueList} uses the {@link Grouper}
     * service to manage collapsing duplicates, which is more efficient than the
     * previous implementation that required a longer pipeline of {@link GroupingList}s.
     *
     * <p>UniqueList exposes a few extra querying methods like {@link #getCount(int)}
     * and {@link #getAll(int)} which require us to query the {@link Grouper}s
     * barcode, which retains state on groups.
     *
     * @param source a private {@link SortedList} whose {@link Comparator} never
     *      changes, this is used to keep track of uniqueness.
     * @param dummyParameter dummy parameter to differentiate between the different
     *      {@link GroupingList} constructors.
     */
    private UniqueList(SortedList<E> source, Void dummyParameter) {
        super(source);

        // the grouper handles changes to the SortedList
        this.grouper = new Grouper<E>(source, new GrouperClient());

        source.addListEventListener(this);
    }

    /**
     * Handle changes to the grouper's groups.
     */
    private class GrouperClient implements Grouper.Client<E> {
        public void groupChanged(int index, int groupIndex, int groupChangeType, boolean primary, int elementChangeType, E oldValue, E newValue) {
            switch (groupChangeType) {
                case ListEvent.INSERT: updates.elementInserted(groupIndex, newValue); break;
                case ListEvent.UPDATE: updates.elementUpdated(groupIndex, oldValue, newValue); break;
                case ListEvent.DELETE: updates.elementDeleted(groupIndex, oldValue); break;
                default: throw new IllegalStateException("Unrecognized groupChangeType: " + groupChangeType);
            }
        }
    }

    /**
     * Change the {@link Comparator} which determines the unique elements
     * of this List.
     *
     * @param comparator the {@link Comparator} used to determine groupings;
     *      <tt>null</tt> will be treated as {@link GlazedLists#comparableComparator()}
     */
    public void setComparator(Comparator<? super E> comparator) {
        if (comparator == null)
            comparator = (Comparator) GlazedLists.comparableComparator();
        ((SortedList<E>) this.source).setComparator(comparator);
    }

    /** {@inheritDoc} */
    @Override
    public int size() {
        return grouper.getBarcode().colourSize(Grouper.UNIQUE);
    }

    /** {@inheritDoc} */
    @Override
    protected int getSourceIndex(int index) {
        if(index == size()) return source.size();
        return grouper.getBarcode().getIndex(index, Grouper.UNIQUE);
    }

    /**
     * Get the first element that's not a duplicate of the element at the specified
     * index. This is useful for things like {@link #getCount(int)} because we can
     * find the full range of a value quickly.
     */
    private int getEndIndex(int index) {
        if(index == (size() - 1)) return source.size();
        else return getSourceIndex(index + 1);
    }

    /** {@inheritDoc} */
    @Override
    public E remove(int index) {
        if(index < 0 || index >= size()) throw new IndexOutOfBoundsException("Cannot remove at " + index + " on list of size " + size());

        updates.beginEvent(true);

        // remember the first duplicate
        E result = get(index);

        // remove all duplicates at this index
        int startIndex = getSourceIndex(index);
        int endIndex = getEndIndex(index);
        ((SortedList)source).subList(startIndex, endIndex).clear();

        updates.commitEvent();

        return result;
    }

    /** {@inheritDoc} */
    @Override
    public E set(int index, E value) {
        if(index < 0 || index >= size()) throw new IndexOutOfBoundsException("Cannot set at " + index + " on list of size " + size());

        updates.beginEvent(true);

        // remove all duplicates of this value first
        int startIndex = getSourceIndex(index) + 1;
        int endIndex = getEndIndex(index);
        if(endIndex > startIndex) {
            ((SortedList)source).subList(startIndex, endIndex).clear();
        }

        // now do the set
        E result = super.set(index, value);

        updates.commitEvent();

        return result;
    }

    /**
     * Returns the index in this list of the first occurrence of the specified
     * <code>element</code>, or -1 if this list does not contain this
     * <code>element</code>. More formally, returns the lowest index <tt>i</tt>
     * such that <tt>uniqueListComparator.compare(get(i), element) == 0</tt>,
     * or -1 if there is no such index.
     *
     * <p>Note: This is a departure from the contract for {@link List#indexOf}
     * since it does not guarantee that <tt>element.equals(get(i))</tt> where i
     * is a positive index returned from this method.
     *
     * @param element the element to search for.
     * @return the index in this list of the first occurrence of the specified
     *         element, or -1 if this list does not contain this element
     * @throws ClassCastException if the type of the specified element
     *         is incompatible with this list
     */
    @Override
    public int indexOf(Object element) {
        final int index = Collections.binarySearch(this, (E) element, ((SortedList<E>)source).getComparator());

        // if the element is not found (index is negative) then return -1 to indicate the list does not contain it
        return index < 0 ? -1 : index;
    }

    /** {@inheritDoc} */
    @Override
    protected boolean isWritable() {
        return true;
    }

    /** {@inheritDoc} */
    @Override
    public void listChanged(ListEvent<E> listChanges) {
        updates.beginEvent(true);

        // check if this ListEvent was caused due to a change in the
        // Comparator that defines uniqueness
        final SortedList<E> sortedSource = (SortedList<E>) source;
        final Comparator<? super E> sourceComparator = sortedSource.getComparator();
        if (sourceComparator != grouper.getComparator()) {
            if (!listChanges.isReordering()) {
                throw new IllegalStateException("source comparator changed without reordering!");
            }

            // fire delete events for the existing events.
            // use the reorder map to find previous values, since everything's moved
            int[] reorderingMap = listChanges.getReorderMap();
            int[] reverseReorderingMap = new int[reorderingMap.length];
            for (int r = 0; r < reorderingMap.length; r++) {
                reverseReorderingMap[reorderingMap[r]] = r;
            }
            for (BarcodeIterator b = grouper.getBarcode().iterator(); b.hasNextBlack(); ) {
                b.nextBlack();
                int sourceIndex = b.getIndex();
                updates.elementDeleted(0, sortedSource.get(reverseReorderingMap[sourceIndex]));
            }
            grouper.getBarcode().clear();

            // adjust the Comparator used by the Grouper (which will change the barcode)
            grouper.setComparator(sourceComparator);

            // insert all new unique values using the new barcode
            int uniqueIndex = 0;
            for (BarcodeIterator b = grouper.getBarcode().iterator(); b.hasNextBlack(); ) {
                b.nextBlack();
                int sourceIndex = b.getIndex();
                updates.elementInserted(uniqueIndex, sortedSource.get(sourceIndex));
                uniqueIndex++;
            }

        } else {
            grouper.listChanged(listChanges);
        }

        updates.commitEvent();
    }

    /**
     * Returns the number of duplicates of the value found at the specified index.
     */
    public int getCount(int index) {
        int startIndex = getSourceIndex(index);
        int endIndex = getEndIndex(index);
        return endIndex - startIndex;
    }

    /**
     * Returns the number of duplicates of the specified value.
     */
    public int getCount(E value) {
        final int index = this.indexOf(value);
        if(index == -1) return 0;
        else return getCount(index);
    }

    /**
     * Returns a List of all original elements represented by the value at the
     * given <code>index</code> within this {@link UniqueList}.
     */
    public List<E> getAll(int index) {
        int startIndex = getSourceIndex(index);
        int endIndex = getEndIndex(index);
        return new ArrayList<E>(source.subList(startIndex, endIndex));
    }

    /**
     * Returns a List of all original elements represented by the given
     * <code>value</code> within this {@link UniqueList}.
     */
    public List<E> getAll(E value) {
        final int index = this.indexOf(value);
        return index == -1 ? Collections.<E>emptyList() : this.getAll(index);
    }

    /** {@inheritDoc} */
    @Override
    public void dispose() {
        ((SortedList)source).dispose();
        super.dispose();
    }
}