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}