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