001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.tagging.ac;
003    
004    import java.util.ArrayList;
005    import java.util.Collection;
006    import java.util.Collections;
007    import java.util.HashMap;
008    import java.util.List;
009    import java.util.Map;
010    
011    import javax.swing.JTable;
012    import 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     */
030    public class AutoCompletionList extends AbstractTableModel {
031    
032        /** the bare list of AutoCompletionItems */
033        private ArrayList<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<AutoCompletionListItem>();
046            filtered = new ArrayList<AutoCompletionListItem>();
047            valutToItemMap = new HashMap<String, AutoCompletionListItem>();
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 value a list of strings to add
135         * @param priority the priority to use
136         */
137        public void add(Collection<String> values, AutoCompletionItemPritority 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<= idx < {@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        ArrayList<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        public int getColumnCount() {
279            return 1;
280        }
281    
282        public int getRowCount() {
283    
284            return list == null ? 0 : getFilteredSize();
285        }
286    
287        public Object getValueAt(int rowIndex, int columnIndex) {
288            return list == null ? null : getFilteredItem(rowIndex);
289        }
290    
291        public void dump() {
292            System.out.println("---------------------------------");
293            for (AutoCompletionListItem item: list) {
294                System.out.println(item);
295            }
296            System.out.println("---------------------------------");
297        }
298    }