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 * @since 1762 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 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 * @throws IllegalArgumentException if other is null 102 */ 103 public void add(AutoCompletionList other) { 104 CheckParameterUtil.ensureParameterNotNull(other, "other"); 105 for (AutoCompletionListItem item : other.list) { 106 appendOrUpdatePriority(item); 107 } 108 sort(); 109 filter(); 110 } 111 112 /** 113 * adds a list of AutoCompletionListItem to this list. Only items which 114 * are not null and which do not exist yet in the list are added. 115 * 116 * @param other a list of AutoCompletionListItem; must not be null 117 * @throws IllegalArgumentException if other is null 118 */ 119 public void add(List<AutoCompletionListItem> other) { 120 CheckParameterUtil.ensureParameterNotNull(other, "other"); 121 for (AutoCompletionListItem toadd : other) { 122 appendOrUpdatePriority(toadd); 123 } 124 sort(); 125 filter(); 126 } 127 128 /** 129 * adds a list of strings to this list. Only strings which 130 * are not null and which do not exist yet in the list are added. 131 * 132 * @param values a list of strings to add 133 * @param priority the priority to use 134 */ 135 public void add(Collection<String> values, AutoCompletionItemPriority priority) { 136 if (values == null) 137 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 /** 151 * Adds values that have been entered by the user. 152 * @param values values that have been entered by the user 153 */ 154 public void addUserInput(Collection<String> values) { 155 if (values == null) 156 return; 157 int i = 0; 158 for (String value : values) { 159 if (value != null) { 160 appendOrUpdatePriority( 161 new AutoCompletionListItem(value, new AutoCompletionItemPriority(false, false, false, i++))); 162 } 163 } 164 sort(); 165 filter(); 166 } 167 168 protected void appendOrUpdatePriority(AutoCompletionListItem toAdd) { 169 AutoCompletionListItem item = valutToItemMap.get(toAdd.getValue()); 170 if (item == null) { 171 // new item does not exist yet. Add it to the list 172 list.add(toAdd); 173 valutToItemMap.put(toAdd.getValue(), toAdd); 174 } else { 175 item.setPriority(item.getPriority().mergeWith(toAdd.getPriority())); 176 } 177 } 178 179 /** 180 * checks whether a specific item is already in the list. Matches for the 181 * the value <strong>and</strong> the priority of the item 182 * 183 * @param item the item to check 184 * @return true, if item is in the list; false, otherwise 185 */ 186 public boolean contains(AutoCompletionListItem item) { 187 if (item == null) 188 return false; 189 return list.contains(item); 190 } 191 192 /** 193 * checks whether an item with the given value is already in the list. Ignores 194 * priority of the items. 195 * 196 * @param value the value of an auto completion item 197 * @return true, if value is in the list; false, otherwise 198 */ 199 public boolean contains(String value) { 200 if (value == null) 201 return false; 202 for (AutoCompletionListItem item: list) { 203 if (item.getValue().equals(value)) 204 return true; 205 } 206 return false; 207 } 208 209 /** 210 * removes the auto completion item with key <code>key</code> 211 * @param key the key; 212 */ 213 public void remove(String key) { 214 if (key == null) 215 return; 216 for (int i = 0; i < list.size(); i++) { 217 AutoCompletionListItem item = list.get(i); 218 if (item.getValue().equals(key)) { 219 list.remove(i); 220 return; 221 } 222 } 223 } 224 225 /** 226 * sorts the list 227 */ 228 protected void sort() { 229 Collections.sort(list); 230 } 231 232 protected void filter() { 233 filtered.clear(); 234 if (filter == null) { 235 // Collections.copy throws an exception "Source does not fit in dest" 236 filtered.ensureCapacity(list.size()); 237 for (AutoCompletionListItem item: list) { 238 filtered.add(item); 239 } 240 return; 241 } 242 243 // apply the pattern to list of possible values. If it matches, add the 244 // value to the list of filtered values 245 // 246 for (AutoCompletionListItem item : list) { 247 if (item.getValue().startsWith(filter)) { 248 filtered.add(item); 249 } 250 } 251 fireTableDataChanged(); 252 } 253 254 /** 255 * replies the number of filtered items 256 * 257 * @return the number of filtered items 258 */ 259 public int getFilteredSize() { 260 return this.filtered.size(); 261 } 262 263 /** 264 * replies the idx-th item from the list of filtered items 265 * @param idx the index; must be in the range 0 <= idx < {@link #getFilteredSize()} 266 * @return the item 267 * 268 * @throws IndexOutOfBoundsException if idx is out of bounds 269 */ 270 public AutoCompletionListItem getFilteredItem(int idx) { 271 if (idx < 0 || idx >= getFilteredSize()) 272 throw new IndexOutOfBoundsException("idx out of bounds. idx=" + idx); 273 return filtered.get(idx); 274 } 275 276 List<AutoCompletionListItem> getList() { 277 return list; 278 } 279 280 List<AutoCompletionListItem> getUnmodifiableList() { 281 return Collections.unmodifiableList(list); 282 } 283 284 /** 285 * removes all elements from the auto completion list 286 * 287 */ 288 public void clear() { 289 valutToItemMap.clear(); 290 list.clear(); 291 fireTableDataChanged(); 292 } 293 294 @Override 295 public int getColumnCount() { 296 return 1; 297 } 298 299 @Override 300 public int getRowCount() { 301 302 return list == null ? 0 : getFilteredSize(); 303 } 304 305 @Override 306 public Object getValueAt(int rowIndex, int columnIndex) { 307 return list == null ? null : getFilteredItem(rowIndex); 308 } 309}