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}