1/*
2 * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package javax.swing;
27
28import java.util.*;
29import java.io.Serializable;
30
31
32/**
33 * A simple implementation of <code>SpinnerModel</code> whose
34 * values are defined by an array or a <code>List</code>.
35 * For example to create a model defined by
36 * an array of the names of the days of the week:
37 * <pre>
38 * String[] days = new DateFormatSymbols().getWeekdays();
39 * SpinnerModel model = new SpinnerListModel(Arrays.asList(days).subList(1, 8));
40 * </pre>
41 * This class only stores a reference to the array or <code>List</code>
42 * so if an element of the underlying sequence changes, it's up
43 * to the application to notify the <code>ChangeListeners</code> by calling
44 * <code>fireStateChanged</code>.
45 * <p>
46 * This model inherits a <code>ChangeListener</code>.
47 * The <code>ChangeListener</code>s are notified whenever the
48 * model's <code>value</code> or <code>list</code> properties changes.
49 *
50 * @see JSpinner
51 * @see SpinnerModel
52 * @see AbstractSpinnerModel
53 * @see SpinnerNumberModel
54 * @see SpinnerDateModel
55 *
56 * @author Hans Muller
57 * @since 1.4
58 */
59@SuppressWarnings("serial") // Superclass is not serializable across versions
60public class SpinnerListModel extends AbstractSpinnerModel implements Serializable
61{
62    private List<?> list;
63    private int index;
64
65
66    /**
67     * Constructs a <code>SpinnerModel</code> whose sequence of
68     * values is defined by the specified <code>List</code>.
69     * The initial value (<i>current element</i>)
70     * of the model will be <code>values.get(0)</code>.
71     * If <code>values</code> is <code>null</code> or has zero
72     * size, an <code>IllegalArugmentException</code> is thrown.
73     *
74     * @param values the sequence this model represents
75     * @throws IllegalArgumentException if <code>values</code> is
76     *    <code>null</code> or zero size
77     */
78    public SpinnerListModel(List<?> values) {
79        if (values == null || values.size() == 0) {
80            throw new IllegalArgumentException("SpinnerListModel(List) expects non-null non-empty List");
81        }
82        this.list = values;
83        this.index = 0;
84    }
85
86
87    /**
88     * Constructs a <code>SpinnerModel</code> whose sequence of values
89     * is defined by the specified array.  The initial value of the model
90     * will be <code>values[0]</code>.  If <code>values</code> is
91     * <code>null</code> or has zero length, an
92     * <code>IllegalArgumentException</code> is thrown.
93     *
94     * @param values the sequence this model represents
95     * @throws IllegalArgumentException if <code>values</code> is
96     *    <code>null</code> or zero length
97     */
98    public SpinnerListModel(Object[] values) {
99        if (values == null || values.length == 0) {
100            throw new IllegalArgumentException("SpinnerListModel(Object[]) expects non-null non-empty Object[]");
101        }
102        this.list = Arrays.asList(values);
103        this.index = 0;
104    }
105
106
107    /**
108     * Constructs an effectively empty <code>SpinnerListModel</code>.
109     * The model's list will contain a single
110     * <code>"empty"</code> string element.
111     */
112    public SpinnerListModel() {
113        this(new Object[]{"empty"});
114    }
115
116
117    /**
118     * Returns the <code>List</code> that defines the sequence for this model.
119     *
120     * @return the value of the <code>list</code> property
121     * @see #setList
122     */
123    public List<?> getList() {
124        return list;
125    }
126
127
128    /**
129     * Changes the list that defines this sequence and resets the index
130     * of the models <code>value</code> to zero.  Note that <code>list</code>
131     * is not copied, the model just stores a reference to it.
132     * <p>
133     * This method fires a <code>ChangeEvent</code> if <code>list</code> is
134     * not equal to the current list.
135     *
136     * @param list the sequence that this model represents
137     * @throws IllegalArgumentException if <code>list</code> is
138     *    <code>null</code> or zero length
139     * @see #getList
140     */
141    public void setList(List<?> list) {
142        if ((list == null) || (list.size() == 0)) {
143            throw new IllegalArgumentException("invalid list");
144        }
145        if (!list.equals(this.list)) {
146            this.list = list;
147            index = 0;
148            fireStateChanged();
149        }
150    }
151
152
153    /**
154     * Returns the current element of the sequence.
155     *
156     * @return the <code>value</code> property
157     * @see SpinnerModel#getValue
158     * @see #setValue
159     */
160    public Object getValue() {
161        return list.get(index);
162    }
163
164
165    /**
166     * Changes the current element of the sequence and notifies
167     * <code>ChangeListeners</code>.  If the specified
168     * value is not equal to an element of the underlying sequence
169     * then an <code>IllegalArgumentException</code> is thrown.
170     * In the following example the <code>setValue</code> call
171     * would cause an exception to be thrown:
172     * <pre>
173     * String[] values = {"one", "two", "free", "four"};
174     * SpinnerModel model = new SpinnerListModel(values);
175     * model.setValue("TWO");
176     * </pre>
177     *
178     * @param elt the sequence element that will be model's current value
179     * @throws IllegalArgumentException if the specified value isn't allowed
180     * @see SpinnerModel#setValue
181     * @see #getValue
182     */
183    public void setValue(Object elt) {
184        int index = list.indexOf(elt);
185        if (index == -1) {
186            throw new IllegalArgumentException("invalid sequence element");
187        }
188        else if (index != this.index) {
189            this.index = index;
190            fireStateChanged();
191        }
192    }
193
194
195    /**
196     * Returns the next legal value of the underlying sequence or
197     * <code>null</code> if value is already the last element.
198     *
199     * @return the next legal value of the underlying sequence or
200     *     <code>null</code> if value is already the last element
201     * @see SpinnerModel#getNextValue
202     * @see #getPreviousValue
203     */
204    public Object getNextValue() {
205        return (index >= (list.size() - 1)) ? null : list.get(index + 1);
206    }
207
208
209    /**
210     * Returns the previous element of the underlying sequence or
211     * <code>null</code> if value is already the first element.
212     *
213     * @return the previous element of the underlying sequence or
214     *     <code>null</code> if value is already the first element
215     * @see SpinnerModel#getPreviousValue
216     * @see #getNextValue
217     */
218    public Object getPreviousValue() {
219        return (index <= 0) ? null : list.get(index - 1);
220    }
221
222
223    /**
224     * Returns the next object that starts with <code>substring</code>.
225     *
226     * @param substring the string to be matched
227     * @return the match
228     */
229    Object findNextMatch(String substring) {
230        int max = list.size();
231
232        if (max == 0) {
233            return null;
234        }
235        int counter = index;
236
237        do {
238            Object value = list.get(counter);
239            String string = value.toString();
240
241            if (string != null && string.startsWith(substring)) {
242                return value;
243            }
244            counter = (counter + 1) % max;
245        } while (counter != index);
246        return null;
247    }
248}
249