File: SequenceList.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 (267 lines) | stat: -rw-r--r-- 10,555 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
/* 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 java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.RandomAccess;

/**
 * A SequenceList contains values in adjacent indices which occur at predictable
 * intervals from each other. A simple SequenceList could be:
 * <pre> {-10, -5, 0, 5, 10, 15} </pre>
 *
 * while a more sophisticated example could be:
 * <pre> {Jun 1, Jul 1, Aug 1, Sep 1, Oct 1} </pre>
 *
 * As long as the values can be ordered via a {@link Comparator} and a
 * {@link Sequencer} can be implemented to reliably produce the next or previous
 * value in a sequence using only some value from the source list.
 *
 * <p>SequenceList is a readonly list; calling any write method on this list
 * will produce an {@link UnsupportedOperationException}.
 *
 * <p>The start and end values of the sequence are the smallest sequence values
 * which maintain the invariant that:
 * <code>sequence start &lt;= each value in the source list &lt;= sequence end</code>
 *
 * <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>no</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>reads: O(1)</td></tr>
 * <tr><td class="TableSubHeadingColor"><b>Memory:</b></td><td>O(N)</td></tr>
 * <tr><td class="TableSubHeadingColor"><b>Unit Tests:</b></td><td>SequenceListTest</td></tr>
 * <tr><td class="TableSubHeadingColor"><b>Issues:</b></td><td>N/A</td></tr>
 * </table>
 *
 * @author James Lemieux
 */
public final class SequenceList<E> extends TransformedList<E,E> implements RandomAccess {

    /** The values participating in the sequence. */
    private final List<E> sequence = new ArrayList<E>();

    /** The comparator that defines the order of the source and sequence values. */
    private final Comparator<? super E> comparator;

    /**
     * The object containing the logic which produces next and previous
     * sequence values by inspecting any source value.
     */
    private final Sequencer<E> sequencer;

    /**
     * Constructs a SequenceList containing a sequence of values produced by
     * the <code>sequencer</code> which cover the range of values contained
     * within the <code>source</code>.
     *
     * @param source the raw values to build a sequence around
     * @param sequencer the logic to produce sequence values relative to a value
     */
    public SequenceList(EventList<E> source, Sequencer<E> sequencer) {
        this(source, sequencer, (Comparator<E>) GlazedLists.comparableComparator());
    }

    /**
     * Constructs a SequenceList containing a sequence of values produced by
     * the <code>sequencer</code> which cover the range of values contained
     * within the <code>source</code>. The given <code>comparator</code>
     * determines the order of the sequence values.
     *
     * @param source the raw values to build a sequence around
     * @param sequencer the logic to produce sequence values relative to a value
     * @param comparator determines the order of the sequence values
     */
    public SequenceList(EventList<E> source, Sequencer<E> sequencer, Comparator<? super E> comparator) {
        this(new SortedList<E>(source, comparator), sequencer, comparator);
    }

    private SequenceList(SortedList<E> source, Sequencer<E> sequencer, Comparator<? super E> comparator) {
        super(source);

        if (sequencer == null)
            throw new IllegalArgumentException("sequencer may not be null");
        if (comparator == null)
            throw new IllegalArgumentException("comparator may not be null");

        this.sequencer = sequencer;
        this.comparator = comparator;
        this.updateSequence();
        source.addListEventListener(this);
    }

    /**
     * @return <tt>false</tt>; SequenceList is readonly
     */
    @Override
    protected boolean isWritable() {
        return false;
    }

    /** {@inheritDoc} */
    @Override
    public int size() {
        return this.sequence.size();
    }

    /** {@inheritDoc} */
    @Override
    public E get(int index) {
        return this.sequence.get(index);
    }

    /**
     * A Sequencer defines the logic required to calculate the previous and
     * next sequence values given any value. It is important to note that the
     * arguments passed to {@link #previous} and {@link #next} will not always
     * be sequence values themselves. For example if a Sequencer is contains
     * logic to produce a sequence of numbers evenly divisible by 2, it must
     * handle returning the next and previous even number relative to
     * <strong>any</strong> integer. So the Sequencer logic must produce:
     *
     * <ul>
     *   <li><code>previous(5)</code> returns 4
     *   <li><code>previous(6)</code> returns 4
     *   <li><code>next(5)</code> returns 6
     *   <li><code>next(4)</code> returns 6
     * </ul>
     */
    public interface Sequencer<E> {
        /**
         * Given a sequencable <code>value</code>, produce the previous value
         * in the sequence such that <code>value</code> is now included in the
         * sequence.
         *
         * @param value a sequencable value
         * @return the previous value in the sequence such that <code>value</code>
         *      would be included within the bounds of the sequence
         */
        public E previous(E value);

        /**
         * Given a sequencable <code>value</code>, produce the next value
         * in the sequence such that <code>value</code> is now included in the
         * sequence.
         *
         * @param value a sequencable value
         * @return the next value in the sequence such that <code>value</code>
         *      would be included within the bounds of the sequence
         */
        public E next(E value);
    }

    /**
     * Returns <tt>true</tt> if <code>value</code> is exactly a sequence value
     * (i.e. could be stored at some index within this {@link SequenceList}.
     */
    private boolean isSequenceValue(E value) {
        final E sequencedValue = sequencer.previous(sequencer.next(value));
        return comparator.compare(value, sequencedValue) == 0;
    }

    /**
     * Returns the previous value in the sequence defined by this list or
     * <code>value</code> itself if it is a sequence value.
     *
     * @param value the value relative to which the previous sequence value is returned
     * @return the previous sequence value relative to the given <code>value</code>
     */
    public E getPreviousSequenceValue(E value) {
        // if value is already a sequence value, return it
        if (isSequenceValue(value))
            return value;

        // ask the sequencer for the previous value
        return sequencer.previous(value);
    }

    /**
     * Returns the next value in the sequence defined by this list or
     * <code>value</code> itself if it is a sequence value.
     *
     * @param value the value relative to which the next sequence value is returned
     * @return the next sequence value relative to the given <code>value</code>
     */
    public E getNextSequenceValue(E value) {
        // if value is already a sequence value, return it
        if (isSequenceValue(value))
            return value;

        // ask the sequencer for the next value
        return sequencer.next(value);
    }

    /** {@inheritDoc} */
    @Override
    public void listChanged(ListEvent<E> listChanges) {
        this.updateSequence();
    }

    /**
     * A convenience method to update the sequence to minimally cover the
     * underlying SortedList.
     */
    private void updateSequence() {
        updates.beginEvent();

        // check for the special case when the underlying list has been completely cleared
        if (source.isEmpty()) {
            while (!sequence.isEmpty()) {
                updates.elementDeleted(0, sequence.remove(0));
            }

        } else {
            // seed this SequenceList with the initial two values
            if (this.isEmpty()) {
                final E value = source.get(0);
                final E previousSequenceValue = getPreviousSequenceValue(value);
                final E nextSequenceValue = getNextSequenceValue(value);

                sequence.add(0, previousSequenceValue);
                updates.elementInserted(0, previousSequenceValue);
                sequence.add(1, nextSequenceValue);
                updates.elementInserted(1, nextSequenceValue);
            }

            // add the necessary leading sequence values
            final E firstSourceValue = source.get(0);
            while (comparator.compare(firstSourceValue, get(0)) == -1) {
                E element = sequencer.previous(get(0));
                sequence.add(0, element);
                updates.elementInserted(0, element);
            }

            // remove the unnecessary leading sequence values
            while (comparator.compare(get(1), firstSourceValue) == -1) {
                E oldValue = sequence.remove(0);
                updates.elementDeleted(0, oldValue);
            }

            // add the necessary trailing sequence values
            final E lastSourceValue = source.get(source.size()-1);
            while (comparator.compare(lastSourceValue, get(size()-1)) == 1) {
                E element = sequencer.next(get(size() - 1));
                int index = size();
                sequence.add(index, element);
                updates.elementInserted(index, element);
            }

            // remove the unnecessary trailing sequence values
            while (comparator.compare(get(size()-2), lastSourceValue) == 1) {
                final int lastIndex = size()-1;
                updates.elementDeleted(lastIndex, sequence.remove(lastIndex));
            }
        }

        updates.commitEvent();
    }
}