001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.tagging.ac; 003 004import java.util.Collection; 005import java.util.Objects; 006import java.util.Optional; 007import java.util.Set; 008import java.util.TreeSet; 009import java.util.stream.Collectors; 010 011/** 012 * A sorted set of {@link AutoCompletionItem}s. 013 * 014 * Items are sorted with higher priority first, then according to lexicographic order 015 * on the value of the {@code AutoCompletionListItem}. 016 * 017 * @since 12859 (extracted from {@code gui.tagging.ac.AutoCompletionList}) 018 */ 019public class AutoCompletionSet extends TreeSet<AutoCompletionItem> { 020 021 private static final long serialVersionUID = 1L; 022 023 // Keep a separate tree set of values for determining fast if a value is present 024 private final Set<String> values = new TreeSet<>(); 025 026 @Override 027 public boolean add(AutoCompletionItem e) { 028 // Is there already an item for the value? 029 String value = e.getValue(); 030 if (contains(value)) { // Fast 031 Optional<AutoCompletionItem> result = stream().filter(i -> i.getValue().equals(e.getValue())).findFirst(); // Slow 032 if (result.isPresent()) { 033 AutoCompletionItem item = result.get(); 034 // yes: merge priorities 035 AutoCompletionPriority newPriority = item.getPriority().mergeWith(e.getPriority()); 036 // if needed, remove/re-add the updated item to maintain set ordering 037 if (!item.getPriority().equals(newPriority)) { 038 super.remove(item); 039 item.setPriority(newPriority); 040 return super.add(item); 041 } else { 042 return false; 043 } 044 } else { 045 // Should never happen if values is correctly synchronized with this set 046 throw new IllegalStateException(value); 047 } 048 } else { 049 values.add(value); 050 return super.add(e); 051 } 052 } 053 054 @Override 055 public boolean remove(Object o) { 056 if (o instanceof AutoCompletionItem) { 057 values.remove(((AutoCompletionItem) o).getValue()); 058 } 059 return super.remove(o); 060 } 061 062 @Override 063 public void clear() { 064 values.clear(); 065 super.clear(); 066 } 067 068 /** 069 * Adds a list of strings to this list. Only strings which 070 * are not null and which do not exist yet in the list are added. 071 * 072 * @param values a list of strings to add 073 * @param priority the priority to use 074 * @return {@code true} if this set changed as a result of the call 075 */ 076 public boolean addAll(Collection<String> values, AutoCompletionPriority priority) { 077 return addAll(values.stream().filter(Objects::nonNull).map(v -> new AutoCompletionItem(v, priority)).collect(Collectors.toList())); 078 } 079 080 /** 081 * Adds values that have been entered by the user. 082 * @param values values that have been entered by the user 083 * @return {@code true} if this set changed as a result of the call 084 */ 085 public boolean addUserInput(Collection<String> values) { 086 int i = 0; 087 boolean modified = false; 088 for (String value : values) { 089 if (value != null && add(new AutoCompletionItem(value, new AutoCompletionPriority(false, false, false, i++)))) { 090 modified = true; 091 } 092 } 093 return modified; 094 } 095 096 /** 097 * Checks whether an item with the given value is already in the list. Ignores priority of the items. 098 * 099 * @param value the value of an auto completion item 100 * @return true, if value is in the list; false, otherwise 101 */ 102 public boolean contains(String value) { 103 return values.contains(value); 104 } 105 106 /** 107 * Removes the auto completion item with key <code>key</code> 108 * @param key the key 109 * @return {@code true} if an element was removed 110 */ 111 public boolean remove(String key) { 112 return values.remove(key) && removeIf(i -> i.getValue().equals(key)); 113 } 114}