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 // Keep a separate tree set of values for determining fast if a value is present 022 private final Set<String> values = new TreeSet<>(); 023 024 @Override 025 public boolean add(AutoCompletionItem e) { 026 // Is there already an item for the value? 027 String value = e.getValue(); 028 if (contains(value)) { // Fast 029 Optional<AutoCompletionItem> result = stream().filter(i -> i.getValue().equals(e.getValue())).findFirst(); // Slow 030 if (result.isPresent()) { 031 AutoCompletionItem item = result.get(); 032 // yes: merge priorities 033 AutoCompletionPriority newPriority = item.getPriority().mergeWith(e.getPriority()); 034 // if needed, remove/re-add the updated item to maintain set ordering 035 if (!item.getPriority().equals(newPriority)) { 036 super.remove(item); 037 item.setPriority(newPriority); 038 return super.add(item); 039 } else { 040 return false; 041 } 042 } else { 043 // Should never happen if values is correctly synchronized with this set 044 throw new IllegalStateException(value); 045 } 046 } else { 047 values.add(value); 048 return super.add(e); 049 } 050 } 051 052 @Override 053 public boolean remove(Object o) { 054 if (o instanceof AutoCompletionItem) { 055 values.remove(((AutoCompletionItem) o).getValue()); 056 } 057 return super.remove(o); 058 } 059 060 @Override 061 public void clear() { 062 values.clear(); 063 super.clear(); 064 } 065 066 /** 067 * Adds a list of strings to this list. Only strings which 068 * are not null and which do not exist yet in the list are added. 069 * 070 * @param values a list of strings to add 071 * @param priority the priority to use 072 * @return {@code true} if this set changed as a result of the call 073 */ 074 public boolean addAll(Collection<String> values, AutoCompletionPriority priority) { 075 return addAll(values.stream().filter(Objects::nonNull).map(v -> new AutoCompletionItem(v, priority)).collect(Collectors.toList())); 076 } 077 078 /** 079 * Adds values that have been entered by the user. 080 * @param values values that have been entered by the user 081 * @return {@code true} if this set changed as a result of the call 082 */ 083 public boolean addUserInput(Collection<String> values) { 084 int i = 0; 085 boolean modified = false; 086 for (String value : values) { 087 if (value != null && add(new AutoCompletionItem(value, new AutoCompletionPriority(false, false, false, i++)))) { 088 modified = true; 089 } 090 } 091 return modified; 092 } 093 094 /** 095 * Checks whether an item with the given value is already in the list. Ignores priority of the items. 096 * 097 * @param value the value of an auto completion item 098 * @return true, if value is in the list; false, otherwise 099 */ 100 public boolean contains(String value) { 101 return values.contains(value); 102 } 103 104 /** 105 * Removes the auto completion item with key <code>key</code> 106 * @param key the key 107 * @return {@code true} if an element was removed 108 */ 109 public boolean remove(String key) { 110 return values.remove(key) && removeIf(i -> i.getValue().equals(key)); 111 } 112}