From a1eee88a547e722d1fb9b9e6bb7fe037899c007f Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Wed, 12 Sep 2018 13:12:10 -0400 Subject: [PATCH] Improved search query support. --- .../lyndir/masterpassword/util/Utilities.java | 7 + .../gui/util/CollectionListModel.java | 45 ++--- .../masterpassword/gui/view/FilesPanel.java | 2 +- .../gui/view/UserContentPanel.java | 176 +++++++++++------- .../lyndir/masterpassword/model/MPQuery.java | 150 +++++++++++++++ .../lyndir/masterpassword/model/MPSite.java | 2 +- .../lyndir/masterpassword/model/MPUser.java | 2 +- .../model/impl/MPBasicSite.java | 10 +- .../model/impl/MPBasicUser.java | 10 +- public/site | 2 +- 10 files changed, 298 insertions(+), 108 deletions(-) create mode 100644 platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPQuery.java diff --git a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/util/Utilities.java b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/util/Utilities.java index 04d90b94..ce85ee91 100644 --- a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/util/Utilities.java +++ b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/util/Utilities.java @@ -27,6 +27,13 @@ public final class Utilities { return value; } + public static String ifNotNullOrEmptyElse(@Nullable final String value, @Nonnull final String emptyValue) { + if ((value == null) || value.isEmpty()) + return emptyValue; + + return value; + } + @Nonnull public static R ifNotNullElse(@Nullable final T value, final Function consumer, @Nonnull final R nullValue) { if (value == null) diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/CollectionListModel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/CollectionListModel.java index e7e86706..08b6ae7c 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/CollectionListModel.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/CollectionListModel.java @@ -1,10 +1,11 @@ package com.lyndir.masterpassword.gui.util; +import static com.google.common.base.Preconditions.*; + import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.lyndir.lhunath.opal.system.logging.Logger; -import com.lyndir.lhunath.opal.system.util.ObjectUtils; import java.util.*; import java.util.function.Consumer; import javax.annotation.Nullable; @@ -62,7 +63,7 @@ public class CollectionListModel extends AbstractListModel public synchronized void set(final Iterable elements) { ListIterator oldIt = model.listIterator(); for (int from = 0; oldIt.hasNext(); ++from) { - int to = Iterables.indexOf( elements, Predicates.equalTo( oldIt.next() ) ); + int to = Iterables.indexOf( elements, Predicates.equalTo( oldIt.next() ) ); if (to != from) { oldIt.remove(); @@ -82,7 +83,7 @@ public class CollectionListModel extends AbstractListModel } if ((selectedItem == null) || !model.contains( selectedItem )) - setSelectedItem( getElementAt( 0 ) ); + selectItem( getElementAt( 0 ) ); } @SafeVarargs @@ -91,19 +92,26 @@ public class CollectionListModel extends AbstractListModel } @Override - @SuppressWarnings({ "unchecked", "SuspiciousMethodCalls" }) - public synchronized void setSelectedItem(@Nullable final Object newSelectedItem) { - if (!Objects.equals( selectedItem, newSelectedItem )) { - selectedItem = (E) newSelectedItem; + @Deprecated + @SuppressWarnings("unchecked") + public synchronized void setSelectedItem(@Nullable final Object/* E */ newSelectedItem) { + selectItem( (E) newSelectedItem ); + } - fireContentsChanged( this, -1, -1 ); - //noinspection ObjectEquality - if ((list != null) && (list.getModel() == this)) - list.setSelectedValue( selectedItem, true ); + public synchronized CollectionListModel selectItem(@Nullable final E newSelectedItem) { + if (Objects.equals( selectedItem, newSelectedItem )) + return this; - if (selectionConsumer != null) - selectionConsumer.accept( selectedItem ); - } + selectedItem = newSelectedItem; + + fireContentsChanged( this, -1, -1 ); + //noinspection ObjectEquality + if ((list != null) && (list.getModel() == this)) + list.setSelectedValue( selectedItem, true ); + + if (selectionConsumer != null) + selectionConsumer.accept( selectedItem ); + return this; } @Nullable @@ -112,11 +120,6 @@ public class CollectionListModel extends AbstractListModel return selectedItem; } - public CollectionListModel select(final E selectedItem) { - setSelectedItem( selectedItem ); - return this; - } - public synchronized void registerList(final JList list) { // TODO: This class should probably implement ListSelectionModel instead. if (this.list != null) @@ -139,7 +142,7 @@ public class CollectionListModel extends AbstractListModel @Override public synchronized CollectionListModel selection(@Nullable final E selectedItem, @Nullable final Consumer selectionConsumer) { this.selectionConsumer = null; - setSelectedItem( selectedItem ); + selectItem( selectedItem ); return selection( selectionConsumer ); } @@ -147,7 +150,7 @@ public class CollectionListModel extends AbstractListModel @Override public synchronized void valueChanged(final ListSelectionEvent event) { //noinspection ObjectEquality - if (!event.getValueIsAdjusting() && (event.getSource() == list) && (list.getModel() == this)) { + if (!event.getValueIsAdjusting() && (event.getSource() == list) && (checkNotNull( list ).getModel() == this)) { selectedItem = list.getSelectedValue(); if (selectionConsumer != null) diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java index c2ddda77..9f1aaa64 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java @@ -64,7 +64,7 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener, Ma @Override public void onUserSelected(@Nullable final MPUser user) { - usersModel.setSelectedItem( user ); + usersModel.selectItem( user ); avatarButton.setIcon( Res.icons().avatar( (user == null)? 0: user.getAvatar() ) ); } } diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java index 3500d0e3..b0a762f3 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java @@ -15,7 +15,6 @@ import com.lyndir.masterpassword.gui.util.*; import com.lyndir.masterpassword.gui.util.Platform; import com.lyndir.masterpassword.model.*; import com.lyndir.masterpassword.model.impl.*; -import com.lyndir.masterpassword.util.Utilities; import java.awt.*; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; @@ -479,15 +478,16 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, "Delete the site from the user." ); @Nonnull - private final MPUser user; - private final JLabel passwordLabel; - private final JLabel passwordField; - private final JLabel answerLabel; - private final JLabel answerField; - private final JLabel queryLabel; - private final JTextField queryField; - private final CollectionListModel> sitesModel; - private final JList> sitesList; + private final MPUser user; + private final JLabel passwordLabel; + private final JLabel passwordField; + private final JLabel answerLabel; + private final JLabel answerField; + private final JLabel queryLabel; + private final JTextField queryField; + private final CollectionListModel>> sitesModel; + private final CollectionListModel> questionsModel; + private final JList>> sitesList; private Future updateSitesJob; @@ -517,6 +517,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, answerField = Components.heading( SwingConstants.CENTER ); answerField.setForeground( Res.colors().highlightFg() ); answerField.setFont( Res.fonts().bigValueFont( SIZE_RESULT ) ); + questionsModel = new CollectionListModel>().selection( this::showQuestionItem ); add( Components.heading( user.getFullName(), SwingConstants.CENTER ) ); @@ -538,7 +539,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, add( Components.strut() ); add( Components.scrollPane( sitesList = Components.list( - sitesModel = new CollectionListModel>().selection( this::showSiteResult ), + sitesModel = new CollectionListModel>>().selection( this::showSiteItem ), this::getSiteDescription ) ) ); add( Components.strut() ); @@ -580,7 +581,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, } public void showSiteSettings() { - MPSite site = sitesModel.getSelectedItem(); + MPSite site = getSite(); if (site == null) return; @@ -627,22 +628,22 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, } public void showSiteQuestions() { - MPSite site = sitesModel.getSelectedItem(); + MPSite site = getSite(); if (site == null) return; - CollectionListModel questionsModel = new CollectionListModel().selection( this::showQuestionResult ); - JList questionsList = Components.list( - questionsModel, question -> Strings.isNullOrEmpty( question.getKeyword() )? "": question.getKeyword() ); - JTextField queryField = Components.textField( null, query -> Res.job( () -> { - Collection questions = new LinkedList<>( site.findQuestions( query ) ); - if (questions.stream().noneMatch( question -> question.getKeyword().equalsIgnoreCase( query ) )) - questions.add( new MPNewQuestion( site, Utilities.ifNotNullElse( query, "" ) ) ); + JList> questionsList = + Components.list( questionsModel, this::getQuestionDescription ); + JTextField queryField = Components.textField( null, queryText -> Res.job( () -> { + MPQuery query = new MPQuery( queryText ); + Collection> questionItems = new LinkedList<>( site.findQuestions( query ) ); + if (questionItems.stream().noneMatch( MPQuery.Result::isExact )) + questionItems.add( MPQuery.Result.allOf( new MPNewQuestion( site, query.getQuery() ), query.getQuery() ) ); - Res.ui( () -> questionsModel.set( questions ) ); + Res.ui( () -> questionsModel.set( questionItems ) ); } ) ); queryField.putClientProperty( "JTextField.variant", "search" ); - queryField.addActionListener( event -> useQuestion( questionsModel.getSelectedItem() ) ); + queryField.addActionListener( this::useQuestion ); queryField.addKeyListener( new KeyAdapter() { @Override public void keyPressed(final KeyEvent event) { @@ -672,7 +673,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, } public void showSiteValues() { - MPSite site = sitesModel.getSelectedItem(); + MPSite site = getSite(); if (site == null) return; @@ -717,7 +718,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, } public void showSiteKeys() { - MPSite site = sitesModel.getSelectedItem(); + MPSite site = getSite(); if (site == null) return; @@ -787,7 +788,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, } public void deleteSite() { - MPSite site = sitesModel.getSelectedItem(); + MPSite site = getSite(); if (site == null) return; @@ -797,55 +798,62 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, user.deleteSite( site ); } - private String getSiteDescription(@Nonnull final MPSite site) { + private String getSiteDescription(@Nullable final MPQuery.Result> item) { + MPSite site = (item != null)? item.getOption(): null; + if (site == null) + return " "; if (site instanceof MPNewSite) - return strf( "%s <Add new site>", queryField.getText() ); + return strf( "%s <Add new site>", item.getKeyAsHTML() ); ImmutableList.Builder parameters = ImmutableList.builder(); - try { - MPFileSite fileSite = (site instanceof MPFileSite)? (MPFileSite) site: null; - if (fileSite != null) - parameters.add( Res.format( fileSite.getLastUsed() ) ); - parameters.add( site.getAlgorithm().version() ); - parameters.add( strf( "#%d", site.getCounter().longValue() ) ); - parameters.add( strf( "%s", site.getLogin() ) ); - if ((fileSite != null) && (fileSite.getUrl() != null)) - parameters.add( fileSite.getUrl() ); - } - catch (final MPAlgorithmException | MPKeyUnavailableException e) { - logger.err( e, "While generating site description." ); - parameters.add( e.getLocalizedMessage() ); - } + MPFileSite fileSite = (site instanceof MPFileSite)? (MPFileSite) site: null; + if (fileSite != null) + parameters.add( Res.format( fileSite.getLastUsed() ) ); + parameters.add( site.getAlgorithm().version() ); + parameters.add( strf( "#%d", site.getCounter().longValue() ) ); + if ((fileSite != null) && (fileSite.getUrl() != null)) + parameters.add( fileSite.getUrl() ); - return strf( "%s (%s)", site.getSiteName(), + return strf( "%s (%s)", item.getKeyAsHTML(), Joiner.on( " - " ).skipNulls().join( parameters.build() ) ); } + private String getQuestionDescription(@Nullable final MPQuery.Result item) { + MPQuestion question = (item != null)? item.getOption(): null; + if (question == null) + return ""; + if (question instanceof MPNewQuestion) + return strf( "%s <Add new question>", item.getKeyAsHTML() ); + + return strf( "%s", item.getKeyAsHTML() ); + } + private void useSite(final ActionEvent event) { - MPSite site = sitesModel.getSelectedItem(); + MPSite site = getSite(); if (site instanceof MPNewSite) { - if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog( + if (JOptionPane.YES_OPTION != JOptionPane.showConfirmDialog( this, strf( "Remember the site %s?", site.getSiteName() ), - "New Site", JOptionPane.YES_NO_OPTION )) { - sitesModel.setSelectedItem( user.addSite( site.getSiteName() ) ); - useSite( event ); - } - return; + "New Site", JOptionPane.YES_NO_OPTION )) + return; + + site = user.addSite( site.getSiteName() ); } - boolean loginResult = (copyLoginKeyStroke.getModifiers() & event.getModifiers()) != 0; + boolean loginResult = (copyLoginKeyStroke.getModifiers() & event.getModifiers()) != 0; + MPSite fsite = site; showSiteResult( site, loginResult, result -> { if (result == null) return; - if (site instanceof MPFileSite) - ((MPFileSite) site).use(); + if (fsite instanceof MPFileSite) + ((MPFileSite) fsite).use(); copyResult( result ); } ); } - private void showSiteResult(@Nullable final MPSite site) { + private void showSiteItem(@Nullable final MPQuery.Result> item) { + MPSite site = (item != null)? item.getOption(): null; showSiteResult( site, false, result -> { } ); } @@ -862,8 +870,11 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, return null; }, resultCallback.andThen( result -> Res.ui( () -> { - passwordLabel.setText( ((result != null) && (site != null))? strf( "Your password for %s:", site.getSiteName() ): " " ); - passwordField.setText( (result != null)? result: " " ); + if (!loginResult && (site != null)) { + passwordLabel.setText( (result != null)? strf( "Your password for %s:", site.getSiteName() ): " " ); + passwordField.setText( (result != null)? result: " " ); + } + settingsButton.setEnabled( result != null ); questionsButton.setEnabled( result != null ); editButton.setEnabled( result != null ); @@ -872,30 +883,33 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, } ) ) ); } - private void useQuestion(@Nullable final MPQuestion question) { + private void useQuestion(final ActionEvent event) { + MPQuestion question = getQuestion(); if (question instanceof MPNewQuestion) { - if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog( + if (JOptionPane.YES_OPTION != JOptionPane.showConfirmDialog( this, strf( "Remember the security question with keyword %s?", Strings.isNullOrEmpty( question.getKeyword() )? "": question.getKeyword() ), - "New Question", JOptionPane.YES_NO_OPTION )) { - useQuestion( question.getSite().addQuestion( question.getKeyword() ) ); - } - return; + "New Question", JOptionPane.YES_NO_OPTION )) + return; + + question = question.getSite().addQuestion( question.getKeyword() ); } + MPQuestion fquestion = question; showQuestionResult( question, result -> { if (result == null) return; - if (question instanceof MPFileQuestion) - ((MPFileQuestion) question).use(); + if (fquestion instanceof MPFileQuestion) + ((MPFileQuestion) fquestion).use(); copyResult( result ); } ); } - private void showQuestionResult(@Nullable final MPQuestion question) { + private void showQuestionItem(@Nullable final MPQuery.Result item) { + MPQuestion question = (item != null)? item.getOption(): null; showQuestionResult( question, answer -> { } ); } @@ -939,6 +953,24 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, } ); } + @Nullable + private MPSite getSite() { + MPQuery.Result> selectedSite = sitesModel.getSelectedItem(); + if (selectedSite == null) + return null; + + return selectedSite.getOption(); + } + + @Nullable + private MPQuestion getQuestion() { + MPQuery.Result selectedQuestion = questionsModel.getSelectedItem(); + if (selectedQuestion == null) + return null; + + return selectedQuestion.getOption(); + } + @Override public void keyTyped(final KeyEvent event) { } @@ -955,25 +987,27 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, sitesList.dispatchEvent( event ); } - private synchronized void updateSites(@Nullable final String query) { + private synchronized void updateSites(@Nullable final String queryText) { if (updateSitesJob != null) updateSitesJob.cancel( true ); updateSitesJob = Res.job( () -> { - Collection> sites = new LinkedList<>( user.findSites( query ) ); + MPQuery query = new MPQuery( queryText ); + Collection>> siteItems = + new LinkedList<>( user.findSites( query ) ); - if (!Strings.isNullOrEmpty( query )) - if (sites.stream().noneMatch( site -> site.getSiteName().equalsIgnoreCase( query ) )) - sites.add( new MPNewSite( user, query ) ); + if (!Strings.isNullOrEmpty( queryText )) + if (siteItems.stream().noneMatch( MPQuery.Result::isExact )) + siteItems.add( MPQuery.Result.allOf( new MPNewSite( user, query.getQuery() ), query.getQuery() ) ); - Res.ui( () -> sitesModel.set( sites ) ); + Res.ui( () -> sitesModel.set( siteItems ) ); } ); } @Override public void onUserUpdated(final MPUser user) { updateSites( queryField.getText() ); - showSiteResult( sitesModel.getSelectedItem() ); + showSiteItem( sitesModel.getSelectedItem() ); } @Override diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPQuery.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPQuery.java new file mode 100644 index 00000000..023d8468 --- /dev/null +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPQuery.java @@ -0,0 +1,150 @@ +package com.lyndir.masterpassword.model; + +import java.util.*; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.jetbrains.annotations.NotNull; + + +/** + * @author lhunath, 2018-09-11 + */ +public class MPQuery { + + @Nonnull + private final String query; + + public MPQuery(@Nullable final String query) { + this.query = (query != null)? query: ""; + } + + @Nonnull + public String getQuery() { + return query; + } + + /** + * @return {@code true} if this query is contained wholly inside the given {@code key}. + */ + @Nonnull + public > Optional> find(final T option, final Function keyForOption) { + CharSequence key = keyForOption.apply( option ); + Result result = Result.noneOf( option, key ); + if (query.isEmpty()) + return Optional.of( result ); + if (key.length() == 0) + return Optional.empty(); + + // Consume query and key characters until one of them runs out, recording any matches against the result's key. + int q = 0, k = 0; + while ((q < query.length()) && (k < key.length())) { + if (query.charAt( q ) == key.charAt( k )) { + result.keyMatchedAt( k ); + ++q; + } + + ++k; + } + + // If query is consumed, the result is a hit. + return (q >= query.length())? Optional.of( result ): Optional.empty(); + } + + public static class Result> implements Comparable> { + + private final T option; + private final CharSequence key; + private final boolean[] keyMatches; + + Result(final T option, final CharSequence key) { + this.option = option; + this.key = key; + + keyMatches = new boolean[key.length()]; + } + + public static > Result noneOf(final T option, final CharSequence key) { + return new Result<>( option, key ); + } + + public static > Result allOf(final T option, final CharSequence key) { + Result result = noneOf( option, key ); + Arrays.fill( result.keyMatches, true ); + return result; + } + + @Nonnull + public T getOption() { + return option; + } + + @Nonnull + public CharSequence getKey() { + return key; + } + + public String getKeyAsHTML() { + return getKeyAsHTML( "u" ); + } + + @SuppressWarnings({ "MagicCharacter", "HardcodedFileSeparator" }) + public String getKeyAsHTML(final String mark) { + String closeMark = mark.contains( " " )? mark.substring( 0, mark.indexOf( ' ' ) ): mark; + StringBuilder html = new StringBuilder(); + boolean marked = false; + + for (int i = 0; i < key.length(); ++i) { + if (keyMatches[i] && !marked) { + html.append( '<' ).append( mark ).append( '>' ); + marked = true; + } else if (!keyMatches[i] && marked) { + html.append( '<' ).append( '/' ).append( closeMark ).append( '>' ); + marked = false; + } + + html.append( key.charAt( i ) ); + } + + if (marked) + html.append( '<' ).append( '/' ).append( closeMark ).append( '>' ); + + return html.toString(); + } + + public boolean[] getKeyMatches() { + return keyMatches.clone(); + } + + public boolean isExact() { + for (final boolean keyMatch : keyMatches) + if (!keyMatch) + return false; + + return true; + } + + private void keyMatchedAt(final int k) { + keyMatches[k] = true; + } + + @Override + public int compareTo(@NotNull final Result o) { + return getOption().compareTo( o.getOption() ); + } + + @Override + public boolean equals(final Object o) { + if (!(o instanceof Result)) + return false; + + Result r = (Result) o; + return Objects.equals( option, r.option ) && Objects.equals( key, r.key ) && Arrays.equals( keyMatches, r.keyMatches ); + } + + @Override + public int hashCode() { + return getOption().hashCode(); + } + } +} diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPSite.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPSite.java index f35a150a..9955f437 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPSite.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPSite.java @@ -117,5 +117,5 @@ public interface MPSite extends Comparable> { Collection getQuestions(); @Nonnull - ImmutableCollection findQuestions(@Nullable String query); + ImmutableCollection> findQuestions(MPQuery query); } diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java index 8753742a..88c3b084 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java @@ -112,7 +112,7 @@ public interface MPUser> extends Comparable> { Collection getSites(); @Nonnull - ImmutableCollection findSites(@Nullable String query); + ImmutableCollection> findSites(MPQuery query); void addListener(Listener listener); diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicSite.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicSite.java index 2d3b3a1c..2fae0e25 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicSite.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicSite.java @@ -21,7 +21,6 @@ package com.lyndir.masterpassword.model.impl; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*; import static com.lyndir.lhunath.opal.system.util.StringUtils.*; -import com.google.common.base.Strings; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableSortedSet; import com.google.common.primitives.UnsignedInteger; @@ -193,11 +192,10 @@ public abstract class MPBasicSite, Q extends MPQuestion> ext @Nonnull @Override - public ImmutableCollection findQuestions(@Nullable final String query) { - ImmutableSortedSet.Builder results = ImmutableSortedSet.naturalOrder(); - for (final Q question : getQuestions()) - if (Strings.isNullOrEmpty( query ) || question.getKeyword().startsWith( query )) - results.add( question ); + public ImmutableCollection> findQuestions(final MPQuery query) { + ImmutableSortedSet.Builder> results = ImmutableSortedSet.naturalOrder(); + for (final Q question : questions) + query.find( question, MPQuestion::getKeyword ).ifPresent( results::add ); return results.build(); } diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java index 557a3a2d..d8dacf7c 100755 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java @@ -20,7 +20,6 @@ package com.lyndir.masterpassword.model.impl; import static com.lyndir.lhunath.opal.system.util.StringUtils.*; -import com.google.common.base.Strings; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableSortedSet; import com.lyndir.lhunath.opal.system.CodeUtils; @@ -201,11 +200,10 @@ public abstract class MPBasicUser> extends Changeabl @Nonnull @Override - public ImmutableCollection findSites(@Nullable final String query) { - ImmutableSortedSet.Builder results = ImmutableSortedSet.naturalOrder(); - for (final S site : getSites()) - if (Strings.isNullOrEmpty( query ) || site.getSiteName().startsWith( query )) - results.add( site ); + public ImmutableCollection> findSites(final MPQuery query) { + ImmutableSortedSet.Builder> results = ImmutableSortedSet.naturalOrder(); + for (final S site : sites.values()) + query.find( site, MPSite::getSiteName ).ifPresent( results::add ); return results.build(); } diff --git a/public/site b/public/site index ff77947d..914a60cd 160000 --- a/public/site +++ b/public/site @@ -1 +1 @@ -Subproject commit ff77947d449b9d92bc254e04a0506f4b5badae62 +Subproject commit 914a60cd25707f4ac456ad225580a86a5a95e637