2
0

Improved search query support.

This commit is contained in:
Maarten Billemont
2018-09-12 13:12:10 -04:00
parent ac5286853a
commit a1eee88a54
10 changed files with 298 additions and 108 deletions

View File

@@ -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();
}
}
}

View File

@@ -117,5 +117,5 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
Collection<Q> getQuestions();
@Nonnull
ImmutableCollection<Q> findQuestions(@Nullable String query);
ImmutableCollection<MPQuery.Result<Q>> findQuestions(MPQuery query);
}

View File

@@ -112,7 +112,7 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
Collection<S> getSites();
@Nonnull
ImmutableCollection<S> findSites(@Nullable String query);
ImmutableCollection<MPQuery.Result<S>> findSites(MPQuery query);
void addListener(Listener listener);

View File

@@ -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<U extends MPUser<?>, Q extends MPQuestion> ext
@Nonnull
@Override
public ImmutableCollection<Q> findQuestions(@Nullable final String query) {
ImmutableSortedSet.Builder<Q> results = ImmutableSortedSet.naturalOrder();
for (final Q question : getQuestions())
if (Strings.isNullOrEmpty( query ) || question.getKeyword().startsWith( query ))
results.add( question );
public ImmutableCollection<MPQuery.Result<Q>> findQuestions(final MPQuery query) {
ImmutableSortedSet.Builder<MPQuery.Result<Q>> results = ImmutableSortedSet.naturalOrder();
for (final Q question : questions)
query.find( question, MPQuestion::getKeyword ).ifPresent( results::add );
return results.build();
}

View File

@@ -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<S extends MPBasicSite<?, ?>> extends Changeabl
@Nonnull
@Override
public ImmutableCollection<S> findSites(@Nullable final String query) {
ImmutableSortedSet.Builder<S> results = ImmutableSortedSet.naturalOrder();
for (final S site : getSites())
if (Strings.isNullOrEmpty( query ) || site.getSiteName().startsWith( query ))
results.add( site );
public ImmutableCollection<MPQuery.Result<S>> findSites(final MPQuery query) {
ImmutableSortedSet.Builder<MPQuery.Result<S>> results = ImmutableSortedSet.naturalOrder();
for (final S site : sites.values())
query.find( site, MPSite::getSiteName ).ifPresent( results::add );
return results.build();
}