001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.tagging.ac;
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Collections;
007import java.util.HashMap;
008import java.util.List;
009import java.util.Map;
010
011import javax.swing.JTable;
012import javax.swing.table.AbstractTableModel;
013
014import org.openstreetmap.josm.tools.CheckParameterUtil;
015
016/**
017 * AutoCompletionList manages a list of {@link AutoCompletionListItem}s.
018 *
019 * The list is sorted, items with higher priority first, then according to lexicographic order
020 * on the value of the {@link AutoCompletionListItem}.
021 *
022 * AutoCompletionList maintains two views on the list of {@link AutoCompletionListItem}s.
023 * <ol>
024 *   <li>the bare, unfiltered view which includes all items</li>
025 *   <li>a filtered view, which includes only items which match a current filter expression</li>
026 * </ol>
027 *
028 * AutoCompletionList is an {@link AbstractTableModel} which serves the list of filtered
029 * items to a {@link JTable}.
030 *
031 */
032public class AutoCompletionList extends AbstractTableModel {
033
034    /** the bare list of AutoCompletionItems */
035    private final transient List<AutoCompletionListItem> list;
036    /**  the filtered list of AutoCompletionItems */
037    private final transient ArrayList<AutoCompletionListItem> filtered;
038    /** the filter expression */
039    private String filter;
040    /** map from value to priority */
041    private final transient Map<String, AutoCompletionListItem> valutToItemMap;
042
043    /**
044     * constructor
045     */
046    public AutoCompletionList() {
047        list = new ArrayList<>();
048        filtered = new ArrayList<>();
049        valutToItemMap = new HashMap<>();
050    }
051
052    /**
053     * applies a filter expression to the list of {@link AutoCompletionListItem}s.
054     *
055     * The matching criterion is a case insensitive substring match.
056     *
057     * @param filter  the filter expression; must not be null
058     *
059     * @throws IllegalArgumentException if filter is null
060     */
061    public void applyFilter(String filter) {
062        CheckParameterUtil.ensureParameterNotNull(filter, "filter");
063        this.filter = filter;
064        filter();
065    }
066
067    /**
068     * clears the current filter
069     *
070     */
071    public void clearFilter() {
072        filter = null;
073        filter();
074    }
075
076    /**
077     * @return the current filter expression; null, if no filter expression is set
078     */
079    public String getFilter() {
080        return filter;
081    }
082
083    /**
084     * adds an AutoCompletionListItem to the list. Only adds the item if it
085     * is not null and if not in the list yet.
086     *
087     * @param item the item
088     */
089    public void add(AutoCompletionListItem item) {
090        if (item == null)
091            return;
092        appendOrUpdatePriority(item);
093        sort();
094        filter();
095    }
096
097    /**
098     * adds another AutoCompletionList to this list. An item is only
099     * added it is not null and if it does not exist in the list yet.
100     *
101     * @param other another auto completion list; must not be null
102     * @throws IllegalArgumentException if other is null
103     */
104    public void add(AutoCompletionList other) {
105        CheckParameterUtil.ensureParameterNotNull(other, "other");
106        for (AutoCompletionListItem item : other.list) {
107            appendOrUpdatePriority(item);
108        }
109        sort();
110        filter();
111    }
112
113    /**
114     * adds a list of AutoCompletionListItem to this list. Only items which
115     * are not null and which do not exist yet in the list are added.
116     *
117     * @param other a list of AutoCompletionListItem; must not be null
118     * @throws IllegalArgumentException if other is null
119     */
120    public void add(List<AutoCompletionListItem> other) {
121        CheckParameterUtil.ensureParameterNotNull(other, "other");
122        for (AutoCompletionListItem toadd : other) {
123            appendOrUpdatePriority(toadd);
124        }
125        sort();
126        filter();
127    }
128
129    /**
130     * adds a list of strings to this list. Only strings which
131     * are not null and which do not exist yet in the list are added.
132     *
133     * @param values a list of strings to add
134     * @param priority the priority to use
135     */
136    public void add(Collection<String> values, AutoCompletionItemPriority priority) {
137        if (values == null) return;
138        for (String value: values) {
139            if (value == null) {
140                continue;
141            }
142            AutoCompletionListItem item = new AutoCompletionListItem(value, priority);
143            appendOrUpdatePriority(item);
144
145        }
146        sort();
147        filter();
148    }
149
150    public void addUserInput(Collection<String> values) {
151        if (values == null) return;
152        int i = 0;
153        for (String value: values) {
154            if (value == null) {
155                continue;
156            }
157            AutoCompletionListItem item = new AutoCompletionListItem(value, new AutoCompletionItemPriority(false, false, false, i));
158            appendOrUpdatePriority(item);
159            i++;
160        }
161        sort();
162        filter();
163    }
164
165    protected void appendOrUpdatePriority(AutoCompletionListItem toAdd) {
166        AutoCompletionListItem item = valutToItemMap.get(toAdd.getValue());
167        if (item == null) {
168            // new item does not exist yet. Add it to the list
169            list.add(toAdd);
170            valutToItemMap.put(toAdd.getValue(), toAdd);
171        } else {
172            item.setPriority(item.getPriority().mergeWith(toAdd.getPriority()));
173        }
174    }
175
176    /**
177     * checks whether a specific item is already in the list. Matches for the
178     * the value <strong>and</strong> the priority of the item
179     *
180     * @param item the item to check
181     * @return true, if item is in the list; false, otherwise
182     */
183    public boolean contains(AutoCompletionListItem item) {
184        if (item == null)
185            return false;
186        return list.contains(item);
187    }
188
189    /**
190     * checks whether an item with the given value is already in the list. Ignores
191     * priority of the items.
192     *
193     * @param value the value of an auto completion item
194     * @return true, if value is in the list; false, otherwise
195     */
196    public boolean contains(String value) {
197        if (value == null)
198            return false;
199        for (AutoCompletionListItem item: list) {
200            if (item.getValue().equals(value))
201                return true;
202        }
203        return false;
204    }
205
206    /**
207     * removes the auto completion item with key <code>key</code>
208     * @param key  the key;
209     */
210    public void remove(String key) {
211        if (key == null)
212            return;
213        for (int i = 0; i < list.size(); i++) {
214            AutoCompletionListItem item = list.get(i);
215            if (item.getValue().equals(key)) {
216                list.remove(i);
217                return;
218            }
219        }
220    }
221
222    /**
223     * sorts the list
224     */
225    protected void sort() {
226        Collections.sort(list);
227    }
228
229    protected void filter() {
230        filtered.clear();
231        if (filter == null) {
232            // Collections.copy throws an exception "Source does not fit in dest"
233            filtered.ensureCapacity(list.size());
234            for (AutoCompletionListItem item: list) {
235                filtered.add(item);
236            }
237            return;
238        }
239
240        // apply the pattern to list of possible values. If it matches, add the
241        // value to the list of filtered values
242        //
243        for (AutoCompletionListItem item : list) {
244            if (item.getValue().startsWith(filter)) {
245                filtered.add(item);
246            }
247        }
248        fireTableDataChanged();
249    }
250
251    /**
252     * replies the number of filtered items
253     *
254     * @return the number of filtered items
255     */
256    public int getFilteredSize() {
257        return this.filtered.size();
258    }
259
260    /**
261     * replies the idx-th item from the list of filtered items
262     * @param idx the index; must be in the range 0 &lt;= idx &lt; {@link #getFilteredSize()}
263     * @return the item
264     *
265     * @throws IndexOutOfBoundsException if idx is out of bounds
266     */
267    public AutoCompletionListItem getFilteredItem(int idx) {
268        if (idx < 0 || idx >= getFilteredSize())
269            throw new IndexOutOfBoundsException("idx out of bounds. idx=" + idx);
270        return filtered.get(idx);
271    }
272
273    List<AutoCompletionListItem> getList() {
274        return list;
275    }
276
277    List<AutoCompletionListItem> getUnmodifiableList() {
278        return Collections.unmodifiableList(list);
279    }
280
281    /**
282     * removes all elements from the auto completion list
283     *
284     */
285    public void clear() {
286        valutToItemMap.clear();
287        list.clear();
288        fireTableDataChanged();
289    }
290
291    @Override
292    public int getColumnCount() {
293        return 1;
294    }
295
296    @Override
297    public int getRowCount() {
298
299        return list == null ? 0 : getFilteredSize();
300    }
301
302    @Override
303    public Object getValueAt(int rowIndex, int columnIndex) {
304        return list == null ? null : getFilteredItem(rowIndex);
305    }
306}