001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.tagging.ac;
003
004import java.util.Comparator;
005import java.util.Objects;
006
007import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel;
008
009/**
010 * A data model for the {@link AutoCompComboBox}
011 *
012 * @author marcello@perathoner.de
013 * @param <E> The element type.
014 * @since 18173
015 */
016public class AutoCompComboBoxModel<E> extends JosmComboBoxModel<E> {
017
018    /**
019     * The comparator used by {@link #findBestCandidate}
020     * <p>
021     * The comparator is used exclusively for autocompleting, and not for sorting the combobox
022     * entries.  The default comparator sorts elements in alphabetical order according to
023     * {@code E::toString}.
024     */
025    private Comparator<E> comparator;
026
027    /**
028     * Constructs a new empty model with a default {@link #comparator}.
029     */
030    public AutoCompComboBoxModel() {
031        setComparator(Comparator.comparing(E::toString));
032    }
033
034    /**
035     * Constructs a new empty model with a custom {@link #comparator}.
036     *
037     * @param comparator A custom {@link #comparator}.
038     */
039    public AutoCompComboBoxModel(Comparator<E> comparator) {
040        setComparator(comparator);
041    }
042
043    /**
044     * Sets a custom {@link #comparator}.
045     * <p>
046     * Example:
047     * {@code setComparator(Comparator.comparing(E::getPriority).thenComparing(E::toString));}
048     * <p>
049     * If {@code <E>} implements {@link java.lang.Comparable Comparable} you can automagically create a
050     * comparator with {@code setComparator(Comparator.naturalOrder());}.
051     *
052     * @param comparator A custom comparator.
053     */
054    public void setComparator(Comparator<E> comparator) {
055        Objects.requireNonNull(comparator, "A comparator cannot be null.");
056        this.comparator = comparator;
057    }
058
059    /**
060     * Finds the best candidate for autocompletion.
061     * <p>
062     * Looks in the model for an element whose prefix matches {@code prefix}. If more than one
063     * element matches {@code prefix}, returns the first of the matching elements (first according
064     * to {@link #comparator}). An element that is equal to {@code prefix} is always preferred.
065     *
066     * @param prefix The prefix to match.
067     * @return The best candidate (may be null)
068     */
069    public E findBestCandidate(String prefix) {
070        return elements.stream()
071            .filter(o -> o.toString().startsWith(prefix))
072            // an element equal to the prefix is always the best candidate
073            .min((x, y) -> x.toString().equals(prefix) ? -1 :
074                           y.toString().equals(prefix) ? 1 :
075                           comparator.compare(x, y))
076            .orElse(null);
077    }
078}