Improved search query support.
This commit is contained in:
		@@ -27,6 +27,13 @@ public final class Utilities {
 | 
				
			|||||||
        return value;
 | 
					        return value;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static String ifNotNullOrEmptyElse(@Nullable final String value, @Nonnull final String emptyValue) {
 | 
				
			||||||
 | 
					        if ((value == null) || value.isEmpty())
 | 
				
			||||||
 | 
					            return emptyValue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Nonnull
 | 
					    @Nonnull
 | 
				
			||||||
    public static <T, R> R ifNotNullElse(@Nullable final T value, final Function<T, R> consumer, @Nonnull final R nullValue) {
 | 
					    public static <T, R> R ifNotNullElse(@Nullable final T value, final Function<T, R> consumer, @Nonnull final R nullValue) {
 | 
				
			||||||
        if (value == null)
 | 
					        if (value == null)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,11 @@
 | 
				
			|||||||
package com.lyndir.masterpassword.gui.util;
 | 
					package com.lyndir.masterpassword.gui.util;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static com.google.common.base.Preconditions.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.google.common.base.Predicates;
 | 
					import com.google.common.base.Predicates;
 | 
				
			||||||
import com.google.common.collect.ImmutableList;
 | 
					import com.google.common.collect.ImmutableList;
 | 
				
			||||||
import com.google.common.collect.Iterables;
 | 
					import com.google.common.collect.Iterables;
 | 
				
			||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
 | 
					import com.lyndir.lhunath.opal.system.logging.Logger;
 | 
				
			||||||
import com.lyndir.lhunath.opal.system.util.ObjectUtils;
 | 
					 | 
				
			||||||
import java.util.*;
 | 
					import java.util.*;
 | 
				
			||||||
import java.util.function.Consumer;
 | 
					import java.util.function.Consumer;
 | 
				
			||||||
import javax.annotation.Nullable;
 | 
					import javax.annotation.Nullable;
 | 
				
			||||||
@@ -62,7 +63,7 @@ public class CollectionListModel<E> extends AbstractListModel<E>
 | 
				
			|||||||
    public synchronized void set(final Iterable<? extends E> elements) {
 | 
					    public synchronized void set(final Iterable<? extends E> elements) {
 | 
				
			||||||
        ListIterator<E> oldIt = model.listIterator();
 | 
					        ListIterator<E> oldIt = model.listIterator();
 | 
				
			||||||
        for (int from = 0; oldIt.hasNext(); ++from) {
 | 
					        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) {
 | 
					            if (to != from) {
 | 
				
			||||||
                oldIt.remove();
 | 
					                oldIt.remove();
 | 
				
			||||||
@@ -82,7 +83,7 @@ public class CollectionListModel<E> extends AbstractListModel<E>
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ((selectedItem == null) || !model.contains( selectedItem ))
 | 
					        if ((selectedItem == null) || !model.contains( selectedItem ))
 | 
				
			||||||
            setSelectedItem( getElementAt( 0 ) );
 | 
					            selectItem( getElementAt( 0 ) );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @SafeVarargs
 | 
					    @SafeVarargs
 | 
				
			||||||
@@ -91,19 +92,26 @@ public class CollectionListModel<E> extends AbstractListModel<E>
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    @SuppressWarnings({ "unchecked", "SuspiciousMethodCalls" })
 | 
					    @Deprecated
 | 
				
			||||||
    public synchronized void setSelectedItem(@Nullable final Object newSelectedItem) {
 | 
					    @SuppressWarnings("unchecked")
 | 
				
			||||||
        if (!Objects.equals( selectedItem, newSelectedItem )) {
 | 
					    public synchronized void setSelectedItem(@Nullable final Object/* E */ newSelectedItem) {
 | 
				
			||||||
            selectedItem = (E) newSelectedItem;
 | 
					        selectItem( (E) newSelectedItem );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            fireContentsChanged( this, -1, -1 );
 | 
					    public synchronized CollectionListModel<E> selectItem(@Nullable final E newSelectedItem) {
 | 
				
			||||||
            //noinspection ObjectEquality
 | 
					        if (Objects.equals( selectedItem, newSelectedItem ))
 | 
				
			||||||
            if ((list != null) && (list.getModel() == this))
 | 
					            return this;
 | 
				
			||||||
                list.setSelectedValue( selectedItem, true );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (selectionConsumer != null)
 | 
					        selectedItem = newSelectedItem;
 | 
				
			||||||
                selectionConsumer.accept( selectedItem );
 | 
					
 | 
				
			||||||
        }
 | 
					        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
 | 
					    @Nullable
 | 
				
			||||||
@@ -112,11 +120,6 @@ public class CollectionListModel<E> extends AbstractListModel<E>
 | 
				
			|||||||
        return selectedItem;
 | 
					        return selectedItem;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public CollectionListModel<E> select(final E selectedItem) {
 | 
					 | 
				
			||||||
        setSelectedItem( selectedItem );
 | 
					 | 
				
			||||||
        return this;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public synchronized void registerList(final JList<E> list) {
 | 
					    public synchronized void registerList(final JList<E> list) {
 | 
				
			||||||
        // TODO: This class should probably implement ListSelectionModel instead.
 | 
					        // TODO: This class should probably implement ListSelectionModel instead.
 | 
				
			||||||
        if (this.list != null)
 | 
					        if (this.list != null)
 | 
				
			||||||
@@ -139,7 +142,7 @@ public class CollectionListModel<E> extends AbstractListModel<E>
 | 
				
			|||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public synchronized CollectionListModel<E> selection(@Nullable final E selectedItem, @Nullable final Consumer<E> selectionConsumer) {
 | 
					    public synchronized CollectionListModel<E> selection(@Nullable final E selectedItem, @Nullable final Consumer<E> selectionConsumer) {
 | 
				
			||||||
        this.selectionConsumer = null;
 | 
					        this.selectionConsumer = null;
 | 
				
			||||||
        setSelectedItem( selectedItem );
 | 
					        selectItem( selectedItem );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return selection( selectionConsumer );
 | 
					        return selection( selectionConsumer );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -147,7 +150,7 @@ public class CollectionListModel<E> extends AbstractListModel<E>
 | 
				
			|||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public synchronized void valueChanged(final ListSelectionEvent event) {
 | 
					    public synchronized void valueChanged(final ListSelectionEvent event) {
 | 
				
			||||||
        //noinspection ObjectEquality
 | 
					        //noinspection ObjectEquality
 | 
				
			||||||
        if (!event.getValueIsAdjusting() && (event.getSource() == list) && (list.getModel() == this)) {
 | 
					        if (!event.getValueIsAdjusting() && (event.getSource() == list) && (checkNotNull( list ).getModel() == this)) {
 | 
				
			||||||
            selectedItem = list.getSelectedValue();
 | 
					            selectedItem = list.getSelectedValue();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (selectionConsumer != null)
 | 
					            if (selectionConsumer != null)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -64,7 +64,7 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener, Ma
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void onUserSelected(@Nullable final MPUser<?> user) {
 | 
					    public void onUserSelected(@Nullable final MPUser<?> user) {
 | 
				
			||||||
        usersModel.setSelectedItem( user );
 | 
					        usersModel.selectItem( user );
 | 
				
			||||||
        avatarButton.setIcon( Res.icons().avatar( (user == null)? 0: user.getAvatar() ) );
 | 
					        avatarButton.setIcon( Res.icons().avatar( (user == null)? 0: user.getAvatar() ) );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,6 @@ import com.lyndir.masterpassword.gui.util.*;
 | 
				
			|||||||
import com.lyndir.masterpassword.gui.util.Platform;
 | 
					import com.lyndir.masterpassword.gui.util.Platform;
 | 
				
			||||||
import com.lyndir.masterpassword.model.*;
 | 
					import com.lyndir.masterpassword.model.*;
 | 
				
			||||||
import com.lyndir.masterpassword.model.impl.*;
 | 
					import com.lyndir.masterpassword.model.impl.*;
 | 
				
			||||||
import com.lyndir.masterpassword.util.Utilities;
 | 
					 | 
				
			||||||
import java.awt.*;
 | 
					import java.awt.*;
 | 
				
			||||||
import java.awt.datatransfer.StringSelection;
 | 
					import java.awt.datatransfer.StringSelection;
 | 
				
			||||||
import java.awt.datatransfer.Transferable;
 | 
					import java.awt.datatransfer.Transferable;
 | 
				
			||||||
@@ -479,15 +478,16 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
				
			|||||||
                                                                   "Delete the site from the user." );
 | 
					                                                                   "Delete the site from the user." );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @Nonnull
 | 
					        @Nonnull
 | 
				
			||||||
        private final MPUser<?>                      user;
 | 
					        private final MPUser<?>                                                 user;
 | 
				
			||||||
        private final JLabel                         passwordLabel;
 | 
					        private final JLabel                                                    passwordLabel;
 | 
				
			||||||
        private final JLabel                         passwordField;
 | 
					        private final JLabel                                                    passwordField;
 | 
				
			||||||
        private final JLabel                         answerLabel;
 | 
					        private final JLabel                                                    answerLabel;
 | 
				
			||||||
        private final JLabel                         answerField;
 | 
					        private final JLabel                                                    answerField;
 | 
				
			||||||
        private final JLabel                         queryLabel;
 | 
					        private final JLabel                                                    queryLabel;
 | 
				
			||||||
        private final JTextField                     queryField;
 | 
					        private final JTextField                                                queryField;
 | 
				
			||||||
        private final CollectionListModel<MPSite<?>> sitesModel;
 | 
					        private final CollectionListModel<MPQuery.Result<? extends MPSite<?>>>  sitesModel;
 | 
				
			||||||
        private final JList<MPSite<?>>               sitesList;
 | 
					        private final CollectionListModel<MPQuery.Result<? extends MPQuestion>> questionsModel;
 | 
				
			||||||
 | 
					        private final JList<MPQuery.Result<? extends MPSite<?>>>                sitesList;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private Future<?> updateSitesJob;
 | 
					        private Future<?> updateSitesJob;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -517,6 +517,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
				
			|||||||
            answerField = Components.heading( SwingConstants.CENTER );
 | 
					            answerField = Components.heading( SwingConstants.CENTER );
 | 
				
			||||||
            answerField.setForeground( Res.colors().highlightFg() );
 | 
					            answerField.setForeground( Res.colors().highlightFg() );
 | 
				
			||||||
            answerField.setFont( Res.fonts().bigValueFont( SIZE_RESULT ) );
 | 
					            answerField.setFont( Res.fonts().bigValueFont( SIZE_RESULT ) );
 | 
				
			||||||
 | 
					            questionsModel = new CollectionListModel<MPQuery.Result<? extends MPQuestion>>().selection( this::showQuestionItem );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
 | 
					            add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -538,7 +539,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
				
			|||||||
            add( Components.strut() );
 | 
					            add( Components.strut() );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            add( Components.scrollPane( sitesList = Components.list(
 | 
					            add( Components.scrollPane( sitesList = Components.list(
 | 
				
			||||||
                    sitesModel = new CollectionListModel<MPSite<?>>().selection( this::showSiteResult ),
 | 
					                    sitesModel = new CollectionListModel<MPQuery.Result<? extends MPSite<?>>>().selection( this::showSiteItem ),
 | 
				
			||||||
                    this::getSiteDescription ) ) );
 | 
					                    this::getSiteDescription ) ) );
 | 
				
			||||||
            add( Components.strut() );
 | 
					            add( Components.strut() );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -580,7 +581,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void showSiteSettings() {
 | 
					        public void showSiteSettings() {
 | 
				
			||||||
            MPSite<?> site = sitesModel.getSelectedItem();
 | 
					            MPSite<?> site = getSite();
 | 
				
			||||||
            if (site == null)
 | 
					            if (site == null)
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -627,22 +628,22 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void showSiteQuestions() {
 | 
					        public void showSiteQuestions() {
 | 
				
			||||||
            MPSite<?> site = sitesModel.getSelectedItem();
 | 
					            MPSite<?> site = getSite();
 | 
				
			||||||
            if (site == null)
 | 
					            if (site == null)
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            CollectionListModel<MPQuestion> questionsModel = new CollectionListModel<MPQuestion>().selection( this::showQuestionResult );
 | 
					            JList<MPQuery.Result<? extends MPQuestion>> questionsList =
 | 
				
			||||||
            JList<MPQuestion> questionsList = Components.list(
 | 
					                    Components.list( questionsModel, this::getQuestionDescription );
 | 
				
			||||||
                    questionsModel, question -> Strings.isNullOrEmpty( question.getKeyword() )? "<site>": question.getKeyword() );
 | 
					            JTextField queryField = Components.textField( null, queryText -> Res.job( () -> {
 | 
				
			||||||
            JTextField queryField = Components.textField( null, query -> Res.job( () -> {
 | 
					                MPQuery                                          query         = new MPQuery( queryText );
 | 
				
			||||||
                Collection<MPQuestion> questions = new LinkedList<>( site.findQuestions( query ) );
 | 
					                Collection<MPQuery.Result<? extends MPQuestion>> questionItems = new LinkedList<>( site.findQuestions( query ) );
 | 
				
			||||||
                if (questions.stream().noneMatch( question -> question.getKeyword().equalsIgnoreCase( query ) ))
 | 
					                if (questionItems.stream().noneMatch( MPQuery.Result::isExact ))
 | 
				
			||||||
                    questions.add( new MPNewQuestion( site, Utilities.ifNotNullElse( query, "" ) ) );
 | 
					                    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.putClientProperty( "JTextField.variant", "search" );
 | 
				
			||||||
            queryField.addActionListener( event -> useQuestion( questionsModel.getSelectedItem() ) );
 | 
					            queryField.addActionListener( this::useQuestion );
 | 
				
			||||||
            queryField.addKeyListener( new KeyAdapter() {
 | 
					            queryField.addKeyListener( new KeyAdapter() {
 | 
				
			||||||
                @Override
 | 
					                @Override
 | 
				
			||||||
                public void keyPressed(final KeyEvent event) {
 | 
					                public void keyPressed(final KeyEvent event) {
 | 
				
			||||||
@@ -672,7 +673,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void showSiteValues() {
 | 
					        public void showSiteValues() {
 | 
				
			||||||
            MPSite<?> site = sitesModel.getSelectedItem();
 | 
					            MPSite<?> site = getSite();
 | 
				
			||||||
            if (site == null)
 | 
					            if (site == null)
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -717,7 +718,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void showSiteKeys() {
 | 
					        public void showSiteKeys() {
 | 
				
			||||||
            MPSite<?> site = sitesModel.getSelectedItem();
 | 
					            MPSite<?> site = getSite();
 | 
				
			||||||
            if (site == null)
 | 
					            if (site == null)
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -787,7 +788,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void deleteSite() {
 | 
					        public void deleteSite() {
 | 
				
			||||||
            MPSite<?> site = sitesModel.getSelectedItem();
 | 
					            MPSite<?> site = getSite();
 | 
				
			||||||
            if (site == null)
 | 
					            if (site == null)
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -797,55 +798,62 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
				
			|||||||
                user.deleteSite( site );
 | 
					                user.deleteSite( site );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private String getSiteDescription(@Nonnull final MPSite<?> site) {
 | 
					        private String getSiteDescription(@Nullable final MPQuery.Result<? extends MPSite<?>> item) {
 | 
				
			||||||
 | 
					            MPSite<?> site = (item != null)? item.getOption(): null;
 | 
				
			||||||
 | 
					            if (site == null)
 | 
				
			||||||
 | 
					                return " ";
 | 
				
			||||||
            if (site instanceof MPNewSite)
 | 
					            if (site instanceof MPNewSite)
 | 
				
			||||||
                return strf( "<html><strong>%s</strong> <Add new site></html>", queryField.getText() );
 | 
					                return strf( "<html><strong>%s</strong> <Add new site></html>", item.getKeyAsHTML() );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ImmutableList.Builder<Object> parameters = ImmutableList.builder();
 | 
					            ImmutableList.Builder<Object> parameters = ImmutableList.builder();
 | 
				
			||||||
            try {
 | 
					            MPFileSite                    fileSite   = (site instanceof MPFileSite)? (MPFileSite) site: null;
 | 
				
			||||||
                MPFileSite fileSite = (site instanceof MPFileSite)? (MPFileSite) site: null;
 | 
					            if (fileSite != null)
 | 
				
			||||||
                if (fileSite != null)
 | 
					                parameters.add( Res.format( fileSite.getLastUsed() ) );
 | 
				
			||||||
                    parameters.add( Res.format( fileSite.getLastUsed() ) );
 | 
					            parameters.add( site.getAlgorithm().version() );
 | 
				
			||||||
                parameters.add( site.getAlgorithm().version() );
 | 
					            parameters.add( strf( "#%d", site.getCounter().longValue() ) );
 | 
				
			||||||
                parameters.add( strf( "#%d", site.getCounter().longValue() ) );
 | 
					            if ((fileSite != null) && (fileSite.getUrl() != null))
 | 
				
			||||||
                parameters.add( strf( "<em>%s</em>", site.getLogin() ) );
 | 
					                parameters.add( fileSite.getUrl() );
 | 
				
			||||||
                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() );
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return strf( "<html><strong>%s</strong> (%s)</html>", site.getSiteName(),
 | 
					            return strf( "<html><strong>%s</strong> (%s)</html>", item.getKeyAsHTML(),
 | 
				
			||||||
                         Joiner.on( " - " ).skipNulls().join( parameters.build() ) );
 | 
					                         Joiner.on( " - " ).skipNulls().join( parameters.build() ) );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private String getQuestionDescription(@Nullable final MPQuery.Result<? extends MPQuestion> item) {
 | 
				
			||||||
 | 
					            MPQuestion question = (item != null)? item.getOption(): null;
 | 
				
			||||||
 | 
					            if (question == null)
 | 
				
			||||||
 | 
					                return "<site>";
 | 
				
			||||||
 | 
					            if (question instanceof MPNewQuestion)
 | 
				
			||||||
 | 
					                return strf( "<html>%s <Add new question></html>", item.getKeyAsHTML() );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return strf( "<html>%s</html>", item.getKeyAsHTML() );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void useSite(final ActionEvent event) {
 | 
					        private void useSite(final ActionEvent event) {
 | 
				
			||||||
            MPSite<?> site = sitesModel.getSelectedItem();
 | 
					            MPSite<?> site = getSite();
 | 
				
			||||||
            if (site instanceof MPNewSite) {
 | 
					            if (site instanceof MPNewSite) {
 | 
				
			||||||
                if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(
 | 
					                if (JOptionPane.YES_OPTION != JOptionPane.showConfirmDialog(
 | 
				
			||||||
                        this, strf( "<html>Remember the site <strong>%s</strong>?</html>", site.getSiteName() ),
 | 
					                        this, strf( "<html>Remember the site <strong>%s</strong>?</html>", site.getSiteName() ),
 | 
				
			||||||
                        "New Site", JOptionPane.YES_NO_OPTION )) {
 | 
					                        "New Site", JOptionPane.YES_NO_OPTION ))
 | 
				
			||||||
                    sitesModel.setSelectedItem( user.addSite( site.getSiteName() ) );
 | 
					                    return;
 | 
				
			||||||
                    useSite( event );
 | 
					
 | 
				
			||||||
                }
 | 
					                site = user.addSite( site.getSiteName() );
 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            boolean loginResult = (copyLoginKeyStroke.getModifiers() & event.getModifiers()) != 0;
 | 
					            boolean   loginResult = (copyLoginKeyStroke.getModifiers() & event.getModifiers()) != 0;
 | 
				
			||||||
 | 
					            MPSite<?> fsite       = site;
 | 
				
			||||||
            showSiteResult( site, loginResult, result -> {
 | 
					            showSiteResult( site, loginResult, result -> {
 | 
				
			||||||
                if (result == null)
 | 
					                if (result == null)
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (site instanceof MPFileSite)
 | 
					                if (fsite instanceof MPFileSite)
 | 
				
			||||||
                    ((MPFileSite) site).use();
 | 
					                    ((MPFileSite) fsite).use();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                copyResult( result );
 | 
					                copyResult( result );
 | 
				
			||||||
            } );
 | 
					            } );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void showSiteResult(@Nullable final MPSite<?> site) {
 | 
					        private void showSiteItem(@Nullable final MPQuery.Result<? extends MPSite<?>> item) {
 | 
				
			||||||
 | 
					            MPSite<?> site = (item != null)? item.getOption(): null;
 | 
				
			||||||
            showSiteResult( site, false, result -> {
 | 
					            showSiteResult( site, false, result -> {
 | 
				
			||||||
            } );
 | 
					            } );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -862,8 +870,11 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                return null;
 | 
					                return null;
 | 
				
			||||||
            }, resultCallback.andThen( result -> Res.ui( () -> {
 | 
					            }, resultCallback.andThen( result -> Res.ui( () -> {
 | 
				
			||||||
                passwordLabel.setText( ((result != null) && (site != null))? strf( "Your password for %s:", site.getSiteName() ): " " );
 | 
					                if (!loginResult && (site != null)) {
 | 
				
			||||||
                passwordField.setText( (result != null)? result: " " );
 | 
					                    passwordLabel.setText( (result != null)? strf( "Your password for %s:", site.getSiteName() ): " " );
 | 
				
			||||||
 | 
					                    passwordField.setText( (result != null)? result: " " );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                settingsButton.setEnabled( result != null );
 | 
					                settingsButton.setEnabled( result != null );
 | 
				
			||||||
                questionsButton.setEnabled( result != null );
 | 
					                questionsButton.setEnabled( result != null );
 | 
				
			||||||
                editButton.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 (question instanceof MPNewQuestion) {
 | 
				
			||||||
                if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(
 | 
					                if (JOptionPane.YES_OPTION != JOptionPane.showConfirmDialog(
 | 
				
			||||||
                        this,
 | 
					                        this,
 | 
				
			||||||
                        strf( "<html>Remember the security question with keyword <strong>%s</strong>?</html>",
 | 
					                        strf( "<html>Remember the security question with keyword <strong>%s</strong>?</html>",
 | 
				
			||||||
                              Strings.isNullOrEmpty( question.getKeyword() )? "<empty>": question.getKeyword() ),
 | 
					                              Strings.isNullOrEmpty( question.getKeyword() )? "<empty>": question.getKeyword() ),
 | 
				
			||||||
                        "New Question", JOptionPane.YES_NO_OPTION )) {
 | 
					                        "New Question", JOptionPane.YES_NO_OPTION ))
 | 
				
			||||||
                    useQuestion( question.getSite().addQuestion( question.getKeyword() ) );
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					
 | 
				
			||||||
                return;
 | 
					                question = question.getSite().addQuestion( question.getKeyword() );
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            MPQuestion fquestion = question;
 | 
				
			||||||
            showQuestionResult( question, result -> {
 | 
					            showQuestionResult( question, result -> {
 | 
				
			||||||
                if (result == null)
 | 
					                if (result == null)
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (question instanceof MPFileQuestion)
 | 
					                if (fquestion instanceof MPFileQuestion)
 | 
				
			||||||
                    ((MPFileQuestion) question).use();
 | 
					                    ((MPFileQuestion) fquestion).use();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                copyResult( result );
 | 
					                copyResult( result );
 | 
				
			||||||
            } );
 | 
					            } );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void showQuestionResult(@Nullable final MPQuestion question) {
 | 
					        private void showQuestionItem(@Nullable final MPQuery.Result<? extends MPQuestion> item) {
 | 
				
			||||||
 | 
					            MPQuestion question = (item != null)? item.getOption(): null;
 | 
				
			||||||
            showQuestionResult( question, answer -> {
 | 
					            showQuestionResult( question, answer -> {
 | 
				
			||||||
            } );
 | 
					            } );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -939,6 +953,24 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
				
			|||||||
            } );
 | 
					            } );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Nullable
 | 
				
			||||||
 | 
					        private MPSite<?> getSite() {
 | 
				
			||||||
 | 
					            MPQuery.Result<? extends MPSite<?>> selectedSite = sitesModel.getSelectedItem();
 | 
				
			||||||
 | 
					            if (selectedSite == null)
 | 
				
			||||||
 | 
					                return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return selectedSite.getOption();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Nullable
 | 
				
			||||||
 | 
					        private MPQuestion getQuestion() {
 | 
				
			||||||
 | 
					            MPQuery.Result<? extends MPQuestion> selectedQuestion = questionsModel.getSelectedItem();
 | 
				
			||||||
 | 
					            if (selectedQuestion == null)
 | 
				
			||||||
 | 
					                return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return selectedQuestion.getOption();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        public void keyTyped(final KeyEvent event) {
 | 
					        public void keyTyped(final KeyEvent event) {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -955,25 +987,27 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
				
			|||||||
                sitesList.dispatchEvent( event );
 | 
					                sitesList.dispatchEvent( event );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private synchronized void updateSites(@Nullable final String query) {
 | 
					        private synchronized void updateSites(@Nullable final String queryText) {
 | 
				
			||||||
            if (updateSitesJob != null)
 | 
					            if (updateSitesJob != null)
 | 
				
			||||||
                updateSitesJob.cancel( true );
 | 
					                updateSitesJob.cancel( true );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            updateSitesJob = Res.job( () -> {
 | 
					            updateSitesJob = Res.job( () -> {
 | 
				
			||||||
                Collection<MPSite<?>> sites = new LinkedList<>( user.findSites( query ) );
 | 
					                MPQuery query = new MPQuery( queryText );
 | 
				
			||||||
 | 
					                Collection<MPQuery.Result<? extends MPSite<?>>> siteItems =
 | 
				
			||||||
 | 
					                        new LinkedList<>( user.findSites( query ) );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (!Strings.isNullOrEmpty( query ))
 | 
					                if (!Strings.isNullOrEmpty( queryText ))
 | 
				
			||||||
                    if (sites.stream().noneMatch( site -> site.getSiteName().equalsIgnoreCase( query ) ))
 | 
					                    if (siteItems.stream().noneMatch( MPQuery.Result::isExact ))
 | 
				
			||||||
                        sites.add( new MPNewSite( user, query ) );
 | 
					                        siteItems.add( MPQuery.Result.allOf( new MPNewSite( user, query.getQuery() ), query.getQuery() ) );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                Res.ui( () -> sitesModel.set( sites ) );
 | 
					                Res.ui( () -> sitesModel.set( siteItems ) );
 | 
				
			||||||
            } );
 | 
					            } );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        public void onUserUpdated(final MPUser<?> user) {
 | 
					        public void onUserUpdated(final MPUser<?> user) {
 | 
				
			||||||
            updateSites( queryField.getText() );
 | 
					            updateSites( queryField.getText() );
 | 
				
			||||||
            showSiteResult( sitesModel.getSelectedItem() );
 | 
					            showSiteItem( sitesModel.getSelectedItem() );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 <T extends Comparable<? super T>> Optional<Result<T>> find(final T option, final Function<T, CharSequence> keyForOption) {
 | 
				
			||||||
 | 
					        CharSequence key    = keyForOption.apply( option );
 | 
				
			||||||
 | 
					        Result<T>    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<T extends Comparable<? super T>> implements Comparable<Result<T>> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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 <T extends Comparable<? super T>> Result<T> noneOf(final T option, final CharSequence key) {
 | 
				
			||||||
 | 
					            return new Result<>( option, key );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static <T extends Comparable<? super T>> Result<T> allOf(final T option, final CharSequence key) {
 | 
				
			||||||
 | 
					            Result<T> 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<T> 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();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -117,5 +117,5 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
 | 
				
			|||||||
    Collection<Q> getQuestions();
 | 
					    Collection<Q> getQuestions();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Nonnull
 | 
					    @Nonnull
 | 
				
			||||||
    ImmutableCollection<Q> findQuestions(@Nullable String query);
 | 
					    ImmutableCollection<MPQuery.Result<Q>> findQuestions(MPQuery query);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -112,7 +112,7 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
 | 
				
			|||||||
    Collection<S> getSites();
 | 
					    Collection<S> getSites();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Nonnull
 | 
					    @Nonnull
 | 
				
			||||||
    ImmutableCollection<S> findSites(@Nullable String query);
 | 
					    ImmutableCollection<MPQuery.Result<S>> findSites(MPQuery query);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void addListener(Listener listener);
 | 
					    void addListener(Listener listener);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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.ObjectUtils.*;
 | 
				
			||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
 | 
					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.ImmutableCollection;
 | 
				
			||||||
import com.google.common.collect.ImmutableSortedSet;
 | 
					import com.google.common.collect.ImmutableSortedSet;
 | 
				
			||||||
import com.google.common.primitives.UnsignedInteger;
 | 
					import com.google.common.primitives.UnsignedInteger;
 | 
				
			||||||
@@ -193,11 +192,10 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Nonnull
 | 
					    @Nonnull
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public ImmutableCollection<Q> findQuestions(@Nullable final String query) {
 | 
					    public ImmutableCollection<MPQuery.Result<Q>> findQuestions(final MPQuery query) {
 | 
				
			||||||
        ImmutableSortedSet.Builder<Q> results = ImmutableSortedSet.naturalOrder();
 | 
					        ImmutableSortedSet.Builder<MPQuery.Result<Q>> results = ImmutableSortedSet.naturalOrder();
 | 
				
			||||||
        for (final Q question : getQuestions())
 | 
					        for (final Q question : questions)
 | 
				
			||||||
            if (Strings.isNullOrEmpty( query ) || question.getKeyword().startsWith( query ))
 | 
					            query.find( question, MPQuestion::getKeyword ).ifPresent( results::add );
 | 
				
			||||||
                results.add( question );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return results.build();
 | 
					        return results.build();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,6 @@ package com.lyndir.masterpassword.model.impl;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
 | 
					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.ImmutableCollection;
 | 
				
			||||||
import com.google.common.collect.ImmutableSortedSet;
 | 
					import com.google.common.collect.ImmutableSortedSet;
 | 
				
			||||||
import com.lyndir.lhunath.opal.system.CodeUtils;
 | 
					import com.lyndir.lhunath.opal.system.CodeUtils;
 | 
				
			||||||
@@ -201,11 +200,10 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Nonnull
 | 
					    @Nonnull
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public ImmutableCollection<S> findSites(@Nullable final String query) {
 | 
					    public ImmutableCollection<MPQuery.Result<S>> findSites(final MPQuery query) {
 | 
				
			||||||
        ImmutableSortedSet.Builder<S> results = ImmutableSortedSet.naturalOrder();
 | 
					        ImmutableSortedSet.Builder<MPQuery.Result<S>> results = ImmutableSortedSet.naturalOrder();
 | 
				
			||||||
        for (final S site : getSites())
 | 
					        for (final S site : sites.values())
 | 
				
			||||||
            if (Strings.isNullOrEmpty( query ) || site.getSiteName().startsWith( query ))
 | 
					            query.find( site, MPSite::getSiteName ).ifPresent( results::add );
 | 
				
			||||||
                results.add( site );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return results.build();
 | 
					        return results.build();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
 Submodule public/site updated: ff77947d44...914a60cd25
									
								
							
		Reference in New Issue
	
	Block a user