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 <= idx < {@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}