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.Set;
008
009import javax.swing.JTable;
010import javax.swing.table.AbstractTableModel;
011
012import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
013import org.openstreetmap.josm.data.tagging.ac.AutoCompletionPriority;
014import org.openstreetmap.josm.data.tagging.ac.AutoCompletionSet;
015import org.openstreetmap.josm.tools.CheckParameterUtil;
016
017/**
018 * AutoCompletionList manages a graphical list of {@link AutoCompletionItem}s.
019 *
020 * The list is sorted, items with higher priority first, then according to lexicographic order
021 * on the value of the {@link AutoCompletionItem}.
022 *
023 * AutoCompletionList maintains two views on the list of {@link AutoCompletionItem}s.
024 * <ol>
025 *   <li>the bare, unfiltered view which includes all items</li>
026 *   <li>a filtered view, which includes only items which match a current filter expression</li>
027 * </ol>
028 *
029 * AutoCompletionList is an {@link AbstractTableModel} which serves the list of filtered
030 * items to a {@link JTable}.
031 * @since 1762
032 */
033public class AutoCompletionList extends AbstractTableModel {
034
035    /** the bare list of AutoCompletionItems */
036    private final transient AutoCompletionSet list;
037    /**  the filtered list of AutoCompletionItems */
038    private final transient ArrayList<AutoCompletionItem> filtered;
039    /** the filter expression */
040    private String filter;
041
042    /**
043     * constructor
044     */
045    public AutoCompletionList() {
046        list = new AutoCompletionSet();
047        filtered = new ArrayList<>();
048    }
049
050    /**
051     * applies a filter expression to the list of {@link AutoCompletionItem}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     * @throws IllegalArgumentException if filter is null
058     */
059    public void applyFilter(String filter) {
060        CheckParameterUtil.ensureParameterNotNull(filter, "filter");
061        this.filter = filter;
062        filter();
063    }
064
065    /**
066     * clears the current filter
067     */
068    public void clearFilter() {
069        filter = null;
070        filter();
071    }
072
073    /**
074     * Returns the current filter expression.
075     * @return the current filter expression; null, if no filter expression is set
076     */
077    public String getFilter() {
078        return filter;
079    }
080
081    /**
082     * adds an {@link AutoCompletionItem} to the list. Only adds the item if it
083     * is not null and if not in the list yet.
084     *
085     * @param item the item
086     * @since 12859
087     */
088    public void add(AutoCompletionItem item) {
089        if (item != null && list.add(item)) {
090            filter();
091        }
092    }
093
094    /**
095     * adds another {@link AutoCompletionList} to this list. An item is only
096     * added it is not null and if it does not exist in the list yet.
097     *
098     * @param other another auto completion list; must not be null
099     * @throws IllegalArgumentException if other is null
100     */
101    public void add(AutoCompletionList other) {
102        CheckParameterUtil.ensureParameterNotNull(other, "other");
103        add(other.list);
104    }
105
106    /**
107     * adds a collection of {@link AutoCompletionItem} to this list. An item is only
108     * added it is not null and if it does not exist in the list yet.
109     *
110     * @param collection auto completion collection; must not be null
111     * @throws IllegalArgumentException if other is null
112     * @since 12859
113     */
114    public void add(Collection<AutoCompletionItem> collection) {
115        CheckParameterUtil.ensureParameterNotNull(collection, "collection");
116        if (list.addAll(collection)) {
117            filter();
118        }
119    }
120
121    /**
122     * adds a list of strings to this list. Only strings which
123     * are not null and which do not exist yet in the list are added.
124     *
125     * @param values a list of strings to add
126     * @param priority the priority to use
127     * @since 12859
128     */
129    public void add(Collection<String> values, AutoCompletionPriority priority) {
130        if (values != null && list.addAll(values, priority)) {
131            filter();
132        }
133    }
134
135    /**
136     * Adds values that have been entered by the user.
137     * @param values values that have been entered by the user
138     */
139    public void addUserInput(Collection<String> values) {
140        if (values != null && list.addUserInput(values)) {
141            filter();
142        }
143    }
144
145    /**
146     * checks whether a specific item is already in the list. Matches for the
147     * the value <strong>and</strong> the priority of the item
148     *
149     * @param item the item to check
150     * @return true, if item is in the list; false, otherwise
151     * @since 12859
152     */
153    public boolean contains(AutoCompletionItem item) {
154        return list.contains(item);
155    }
156
157    /**
158     * checks whether an item with the given value is already in the list. Ignores
159     * priority of the items.
160     *
161     * @param value the value of an auto completion item
162     * @return true, if value is in the list; false, otherwise
163     */
164    public boolean contains(String value) {
165        return list.contains(value);
166    }
167
168    /**
169     * removes the auto completion item with key <code>key</code>
170     * @param key the key
171     */
172    public void remove(String key) {
173        if (key != null) {
174            list.remove(key);
175        }
176    }
177
178    protected void filter() {
179        filtered.clear();
180        if (filter == null) {
181            // Collections.copy throws an exception "Source does not fit in dest"
182            filtered.ensureCapacity(list.size());
183            filtered.addAll(list);
184            return;
185        }
186
187        // apply the pattern to list of possible values. If it matches, add the
188        // value to the list of filtered values
189        //
190        list.stream().filter(e -> e.getValue().startsWith(filter)).forEach(filtered::add);
191        fireTableDataChanged();
192    }
193
194    /**
195     * replies the number of filtered items
196     *
197     * @return the number of filtered items
198     */
199    public int getFilteredSize() {
200        return filtered.size();
201    }
202
203    /**
204     * replies the idx-th item from the list of filtered items
205     * @param idx the index; must be in the range 0 &lt;= idx &lt; {@link #getFilteredSize()}
206     * @return the item
207     *
208     * @throws IndexOutOfBoundsException if idx is out of bounds
209     * @since 12859
210     */
211    public AutoCompletionItem getFilteredItemAt(int idx) {
212        return filtered.get(idx);
213    }
214
215    AutoCompletionSet getSet() {
216        return list;
217    }
218
219    Set<AutoCompletionItem> getUnmodifiableSet() {
220        return Collections.unmodifiableSet(list);
221    }
222
223    /**
224     * removes all elements from the auto completion list
225     */
226    public void clear() {
227        list.clear();
228        fireTableDataChanged();
229    }
230
231    @Override
232    public int getColumnCount() {
233        return 1;
234    }
235
236    @Override
237    public int getRowCount() {
238        return list == null ? 0 : getFilteredSize();
239    }
240
241    @Override
242    public Object getValueAt(int rowIndex, int columnIndex) {
243        return list == null ? null : getFilteredItemAt(rowIndex);
244    }
245}