├── .gitignore ├── LICENSE.md ├── README.md ├── bintray.gradle ├── build.gradle ├── gradlew ├── gradlew.bat ├── install.gradle ├── local.properties ├── media ├── Diagram.png └── Diagram2.png ├── settings.gradle └── src ├── main └── java │ └── net │ └── andreinc │ └── jasuggest │ ├── JaCacheConfig.java │ ├── JaSuggest.java │ └── examples │ └── Example01.java └── test ├── java └── net │ └── andreinc │ └── jasuggest │ ├── JaSuggestNoCacheTest.java │ └── TestUtils.java └── resources └── english_words.text /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/* 2 | gradle/* 3 | .idea/* 4 | build/* -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2017 Andrei N. Ciobanu 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## jasuggest 2 | 3 | A simple autosuggest library based on a [Trie](https://en.wikipedia.org/wiki/Trie) implementation. 4 | 5 | Available in [jcenter()](https://bintray.com/nomemory/maven/jasuggest). 6 | 7 | **Maven** 8 | ``` 9 | 10 | net.andreinc.jasuggest 11 | jasuggest 12 | 0.0.1 13 | pom 14 | 15 | ``` 16 | 17 | **Gradle** 18 | ``` 19 | compile 'net.andreinc.jasuggest:jasuggest:0.0.1' 20 | ``` 21 | 22 | ## Simple Example 23 | 24 | ```java 25 | 26 | String[] words = { "us", "usa", "use", "useful", "useless", "user", "usurper" }; 27 | 28 | // All the searches will be cached in a ConcurrentHashMap with a maximum size of 512 elements. 29 | // Check: https://github.com/jhalterman/expiringmap 30 | JaCacheConfig jaCacheConfig = 31 | JaCacheConfig.builder() 32 | .maxSize(512) 33 | .build(); 34 | 35 | JaSuggest jaSuggest = JaSuggest.builder() 36 | .ignoreCase() 37 | .withCache(jaCacheConfig) 38 | .buildFrom(words); 39 | 40 | List result = jaSuggest.findSuggestions("use"); 41 | 42 | // [useful, useless, user] 43 | ``` 44 | 45 | The above example creates internally creates a Trie based on the supplied `words`. In memory the Trie looks like: 46 | 47 | ![Image of the trie](https://github.com/nomemory/jasuggest/blob/master/media/Diagram.png) 48 | 49 | **Notes:** 50 | 51 | * Each blue node has an additional property that marks the existence of an word. So when the `findSuggestions()` method is called a tree traversal is performed in order to determine all the possible outcomes. When a "blue node" is visited the result is added to the map and the traversing operation continues; 52 | 53 | * The library supports caching the results. 54 | 55 | ## Simple Example - Increasing performance at the cost of memory consumption 56 | 57 | The builder() method `prebuiltWords()` adds more information to the Trie. Basically each "blue node" that marks the existence of a word also contains the same word as a property: 58 | 59 | ![Image of the trie](https://github.com/nomemory/jasuggest/blob/master/media/Diagram2.png) 60 | 61 | So the only change you need to make is when you create the `JaSuggest` object: 62 | 63 | ```java 64 | JaSuggest jaSuggest = JaSuggest.builder() 65 | .ignoreCase() 66 | .prebuiltWords() // !HERE! 67 | .withCache(jaCacheConfig) 68 | .buildFrom(words); 69 | ``` 70 | -------------------------------------------------------------------------------- /bintray.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | 3 | version = '0.0.1' 4 | 5 | task sourcesJar(type: Jar) { 6 | classifier = 'sources' 7 | from sourceSets.main.allSource 8 | } 9 | 10 | task javadocJar(type: Jar, dependsOn: javadoc) { 11 | classifier 'javadoc' 12 | from javadoc.destinationDir 13 | } 14 | 15 | artifacts { 16 | archives javadocJar 17 | archives sourcesJar 18 | } 19 | 20 | // Bintray 21 | Properties properties = new Properties() 22 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 23 | 24 | bintray { 25 | user = properties.getProperty("bintray.user") 26 | key = properties.getProperty("bintray.apikey") 27 | 28 | configurations = ['archives'] 29 | pkg { 30 | repo = 'maven' 31 | name = 'jasuggest' 32 | desc = 'A simple autosuggest library based on a Java Trie implementation.' 33 | websiteUrl = 'https://github.com/nomemory/jasuggest.git' 34 | vcsUrl = 'https://github.com/nomemory/jasuggest.git' 35 | licenses = ["Apache-2.0"] 36 | publish = true 37 | publicDownloadNumbers = true 38 | } 39 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript() { 2 | repositories { 3 | jcenter() 4 | } 5 | dependencies { 6 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2' 7 | } 8 | } 9 | 10 | plugins { 11 | id "com.github.johnrengelman.shadow" version "1.2.4" 12 | id "com.jfrog.bintray" version "1.7.3" 13 | } 14 | 15 | apply plugin: 'java' 16 | apply plugin: 'idea' 17 | 18 | group 'net.andreinc' 19 | version '0.0.1' 20 | 21 | repositories { 22 | jcenter() 23 | mavenCentral() 24 | } 25 | 26 | dependencies { 27 | compile group: 'net.jodah', name: 'expiringmap', version: '0.5.8' 28 | 29 | compileOnly "org.projectlombok:lombok:1.16.16" 30 | 31 | testCompile group: 'junit', name: 'junit', version: '4.12' 32 | } 33 | 34 | apply from: 'install.gradle' 35 | apply from: 'bintray.gradle' 36 | 37 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :leaf 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /install.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven' 2 | 3 | group = 'net.andreinc.jasuggest' 4 | 5 | install { 6 | repositories.mavenInstaller { 7 | pom { 8 | project { 9 | packaging 'jar' 10 | groupId 'net.andreinc.jasuggest' 11 | artifactId 'jasuggest' 12 | 13 | name 'jasuggest' // YOUR LIBRARY NAME 14 | description 'A simple autosuggest library based on a Java Trie implementation.' 15 | url 'https://github.com/nomemory/jasuggest.git' // YOUR SITE 16 | 17 | licenses { 18 | license { 19 | name 'The Apache Software License, Version 2.0' 20 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 21 | } 22 | } 23 | developers { 24 | developer { 25 | id 'nomemory' //YOUR ID 26 | name 'Andrei N. Ciobanu' //YOUR NAME 27 | email 'n@n.com' //YOUR EMAIL 28 | } 29 | } 30 | scm { 31 | connection 'https://github.com/nomemory/jasuggest' // YOUR GIT REPO 32 | developerConnection 'https://github.com/nomemory/jasuggest' // YOUR GIT REPO 33 | url 'https://github.com/nomemory/jasuggest' // YOUR SITE 34 | 35 | } 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /local.properties: -------------------------------------------------------------------------------- 1 | bintray.user=nomemory 2 | bintray.apikey= -------------------------------------------------------------------------------- /media/Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomemory/jasuggest/1b8870f7c7021f1f755b99f1a904e8343a4a073b/media/Diagram.png -------------------------------------------------------------------------------- /media/Diagram2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomemory/jasuggest/1b8870f7c7021f1f755b99f1a904e8343a4a073b/media/Diagram2.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'jasuggest' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/net/andreinc/jasuggest/JaCacheConfig.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Andrei N. Ciobanu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package net.andreinc.jasuggest; 18 | 19 | import lombok.*; 20 | import lombok.experimental.FieldDefaults; 21 | import net.jodah.expiringmap.ExpirationPolicy; 22 | 23 | import java.util.concurrent.TimeUnit; 24 | 25 | import static lombok.AccessLevel.PRIVATE; 26 | 27 | @Builder 28 | @ToString 29 | @EqualsAndHashCode 30 | @FieldDefaults(level = PRIVATE) 31 | public class JaCacheConfig { 32 | @Builder.Default @Getter 33 | int maxSize = 2048; 34 | 35 | @Builder.Default @Getter @NonNull 36 | ExpirationPolicy expirationPolicy = ExpirationPolicy.CREATED; 37 | 38 | @Builder.Default @Getter 39 | long expiration = 24; 40 | 41 | @Builder.Default @Getter @NonNull 42 | public final TimeUnit expirationUnit = TimeUnit.HOURS; 43 | 44 | public static JaCacheConfig defaultConfig() { 45 | return JaCacheConfig.builder().build(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/net/andreinc/jasuggest/JaSuggest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Andrei N. Ciobanu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package net.andreinc.jasuggest; 18 | 19 | import lombok.*; 20 | import lombok.experimental.FieldDefaults; 21 | import net.jodah.expiringmap.ExpiringMap; 22 | 23 | import java.util.*; 24 | 25 | import static java.util.Collections.sort; 26 | import static lombok.AccessLevel.PRIVATE; 27 | 28 | public class JaSuggest { 29 | 30 | private boolean ignoreCase = false; 31 | private boolean prebuiltWords = false; 32 | 33 | private Map> cache; 34 | private JaMap nodes; 35 | 36 | private JaSuggest(JaSuggestBuilder jaSuggestBuilder) { 37 | if (jaSuggestBuilder.cacheConfig!=null) { 38 | this.cache = ExpiringMap.builder() 39 | .maxSize(jaSuggestBuilder.cacheConfig.getMaxSize()) 40 | .expirationPolicy(jaSuggestBuilder.cacheConfig.getExpirationPolicy()) 41 | .expiration(jaSuggestBuilder.cacheConfig.getExpiration(), 42 | jaSuggestBuilder.cacheConfig.getExpirationUnit()) 43 | .build(); 44 | } 45 | 46 | this.ignoreCase = jaSuggestBuilder.ignoreCase; 47 | this.prebuiltWords = jaSuggestBuilder.prebuiltWords; 48 | 49 | this.nodes = new JaMap(); 50 | } 51 | 52 | public static JaSuggestBuilder builder() { return new JaSuggestBuilder(); } 53 | 54 | public boolean hasCache() { return cache != null; } 55 | 56 | /** 57 | * Returns the current size of the map from memory. 58 | * If the cache doesn't exist it returns -1 59 | * 60 | * @return 61 | */ 62 | public int cacheSize() { return (hasCache()) ? cache.size() : -1; } 63 | 64 | /** 65 | * Creates a snapshot of the cache from the memory and returns it. 66 | * 67 | * @return A cloned representation of the cache from the memory. If the cache is not enabled it will always return an empty Map. 68 | */ 69 | public Map> cacheSnapshot() { 70 | final Map> result = new HashMap<>(); 71 | 72 | if (hasCache()) { 73 | for(Map.Entry> entry : cache.entrySet()) { 74 | result.put(entry.getKey(), new ArrayList<>(entry.getValue())); 75 | } 76 | } 77 | 78 | return result; 79 | } 80 | 81 | private JaSuggest from(@NonNull String... terms) { 82 | addTerms(terms); 83 | return this; 84 | } 85 | 86 | private JaSuggest from(@NonNull Iterable terms) { 87 | addTerms(terms); 88 | return this; 89 | } 90 | 91 | private void addTerms(@NonNull String... terms) { 92 | for(String term : terms) { 93 | if (term == null) { 94 | throw new NullPointerException("Null term detected. Please check if the array String... doesn't contain null values."); 95 | } 96 | this.nodes.addTerm(term, prebuiltWords); 97 | } 98 | } 99 | 100 | private void addTerms(Iterable terms) { 101 | for(String term : terms) { 102 | if (term == null) { 103 | throw new NullPointerException("Null term detected. Please check if the Iterable doesn't contain null values."); 104 | } 105 | this.nodes.addTerm(this.ignoreCase ? term.toLowerCase() : term, prebuiltWords); 106 | } 107 | } 108 | 109 | /** 110 | * Searches the current Trie for suggestions based on the given prefix. 111 | * All the possible suggestions will be retrieved and the results will be sorted. 112 | * 113 | * @param prefix The search prefix. 114 | * 115 | * @return A sorted list of all possible suggestions. 116 | */ 117 | public List findSuggestions(@NonNull String prefix) { 118 | return this.findSuggestionsInternal(prefix, Integer.MAX_VALUE, true); 119 | } 120 | 121 | /** 122 | * Searches the current Trie for suggestion based on the given prefix. 123 | * Not all the possible suggestions will be retrieved but only the first 'maxResult'. 124 | * The results will be sorted. 125 | * 126 | * @param prefix The search prefix. 127 | * @param maxResults The maximum number of results. 128 | * 129 | * @return A sorted List of suggestions. 130 | */ 131 | public List findSuggestions(@NonNull String prefix, int maxResults) { 132 | return this.findSuggestionsInternal(prefix, maxResults, true); 133 | } 134 | 135 | /** 136 | * Searches the current Trie for suggestions based on the given prefix. 137 | * All the possible suggestions will be retrieved. 138 | * 139 | * @param prefix The search prefix. 140 | * @param sorted Sort the results or not 141 | * 142 | * @return A List of suggestions. 143 | */ 144 | public List findSuggestions(@NonNull String prefix, boolean sorted) { 145 | return this.findSuggestionsInternal(prefix, Integer.MAX_VALUE, sorted); 146 | } 147 | 148 | /** 149 | * Searches the current Trie for suggestions based on a given prefix. 150 | * 151 | * @param prefix The search prefix. 152 | * @param maxResults The total number of results. 153 | * @param sorted Sort the result or not. 154 | * 155 | * @return A List of suggestions. 156 | */ 157 | public List findSuggestionsInternal(@NonNull String prefix, int maxResults, boolean sorted) { 158 | List list = new ArrayList<>(); 159 | List tmp; 160 | 161 | if (ignoreCase) { 162 | prefix = prefix.toLowerCase(); 163 | } 164 | 165 | if (hasCache() && (tmp=cache.get(prefix))!=null) { 166 | list = new ArrayList<>(tmp); 167 | } else { 168 | JaMap local = getLocationByPrefix(prefix); 169 | 170 | if (null == local) { 171 | // Return empty list if prefix is not present 172 | return list; 173 | } 174 | 175 | if (prebuiltWords) { 176 | findSuggestionsWithPrebuiltWords(prefix, maxResults, list); 177 | } 178 | else { 179 | findSuggestionsInternalWithJaSolution(prefix, maxResults, list); 180 | } 181 | 182 | if (sorted) { 183 | sort(list); 184 | } 185 | 186 | if (list.size() > maxResults) { 187 | List newList = new ArrayList<>(maxResults); 188 | for(int i = 0; i < maxResults; ++i) { 189 | newList.add(list.get(i)); 190 | } 191 | list = newList; 192 | } 193 | } 194 | 195 | if (hasCache()) { 196 | cache.put(prefix, list); 197 | } 198 | 199 | return list; 200 | } 201 | 202 | private void findSuggestionsInternalWithJaSolution(@NonNull String prefix, int maxResults, List list) { 203 | JaMap local = getLocationByPrefix(prefix); 204 | Iterator it; 205 | Character c; 206 | 207 | Stack stack = new Stack<>(); 208 | JaSolution current = new JaSolution(local, prefix); 209 | stack.push(current); 210 | 211 | while (!stack.isEmpty()) { 212 | current = stack.pop(); 213 | 214 | if (current.getNode().isLeaf() && !current.getTerm().equals(prefix)) { 215 | list.add(current.getTerm()); 216 | } 217 | 218 | it = current.getNode().keySet().iterator(); 219 | 220 | while (it.hasNext()) { 221 | c = it.next(); 222 | stack.push(new JaSolution(current.getNode().get(c), current.getTerm() + c)); 223 | } 224 | } 225 | } 226 | 227 | private void findSuggestionsWithPrebuiltWords(@NonNull String prefix, int maxResults, List list) { 228 | Iterator it; 229 | 230 | Stack stack = new Stack<>(); 231 | JaMap current = getLocationByPrefix(prefix); 232 | stack.push(current); 233 | 234 | while(!stack.isEmpty()) { 235 | current = stack.pop(); 236 | 237 | if (current.isLeaf() && !current.getTerm().equals(prefix)) { 238 | list.add(current.getTerm()); 239 | } 240 | 241 | it = current.values().iterator(); 242 | 243 | while(it.hasNext()) { 244 | stack.push(it.next()); 245 | } 246 | } 247 | } 248 | 249 | private JaMap getLocationByPrefix(@NonNull String prefix) { 250 | JaMap local = this.nodes; 251 | for(int i = 0; i < prefix.length(); ++i) { 252 | local = local.get(prefix.charAt(i)); 253 | if (null == local) { 254 | return null; 255 | } 256 | } 257 | return local; 258 | } 259 | 260 | @NoArgsConstructor 261 | @FieldDefaults(level = PRIVATE) 262 | public static class JaSuggestBuilder { 263 | 264 | JaCacheConfig cacheConfig; 265 | boolean ignoreCase = false; 266 | boolean prebuiltWords = false; 267 | 268 | public JaSuggestBuilder withCache(JaCacheConfig config) { 269 | this.cacheConfig = config; 270 | return this; 271 | } 272 | 273 | public JaSuggestBuilder withCache() { 274 | this.cacheConfig = JaCacheConfig.defaultConfig(); 275 | return this; 276 | } 277 | 278 | public JaSuggestBuilder ignoreCase() { 279 | this.ignoreCase = true; 280 | return this; 281 | } 282 | 283 | /** 284 | * If this option is activated the words are stored on the leafs of the Trie. 285 | * This mean that memory consumption will increase accordingly, but the results will be retrieved 286 | * in a way that can increase performance. 287 | * 288 | * @return 289 | */ 290 | public JaSuggestBuilder prebuiltWords() { 291 | this.prebuiltWords = true; 292 | return this; 293 | } 294 | 295 | /** 296 | * Creates a JaSuggest object from a given array of terms. 297 | * If one of the terms in the array is NULL, a NullPointerException will be thrown. 298 | * Validate input before calling this method. 299 | * 300 | * @param terms An array of String[] that contains the list of terms we are going to (later) auto-suggest. 301 | * Eg.: An array of countries. 302 | * 303 | * @return An instance of JaSuggest 304 | */ 305 | public JaSuggest buildFrom(String... terms) { 306 | return new JaSuggest(this).from(terms); 307 | } 308 | 309 | /** 310 | * Creates a JaSuggest object from a given Iterable (eg.: a List of Strings). 311 | * If one of the terms in the Iterable object is NULL, a NullPointerException will be thrown. 312 | * Validate input before calling this method. 313 | * 314 | * @param terms An Iterable of String that contains the terms we are going to (later) auto-suggest. 315 | * Eg.: An List of countries. 316 | * 317 | * @return An instance of JaSuggest 318 | */ 319 | public JaSuggest buildFrom(Iterable terms) { 320 | return new JaSuggest(this).from(terms); 321 | } 322 | } 323 | } 324 | 325 | @ToString 326 | class JaMap extends HashMap { 327 | 328 | @Getter @Setter private boolean isLeaf; 329 | @Getter @Setter private String term; 330 | 331 | protected void addTerm(String term, boolean prebuiltWords) { 332 | 333 | JaMap current = this; 334 | int lastLetterIndex = term.length() - 1; 335 | 336 | for (int i = 0; i < term.length(); i++) { 337 | 338 | current.putIfAbsent(term.charAt(i), new JaMap()); 339 | current = current.get(term.charAt(i)); 340 | 341 | if (!current.isLeaf() && lastLetterIndex == i) { 342 | current.setLeaf(true); 343 | if (prebuiltWords) { 344 | current.setTerm(term); 345 | } 346 | } 347 | } 348 | } 349 | } 350 | 351 | @Data 352 | @AllArgsConstructor 353 | class JaSolution { 354 | private JaMap node; 355 | private String term; 356 | } -------------------------------------------------------------------------------- /src/main/java/net/andreinc/jasuggest/examples/Example01.java: -------------------------------------------------------------------------------- 1 | package net.andreinc.jasuggest.examples; 2 | 3 | import net.andreinc.jasuggest.JaCacheConfig; 4 | import net.andreinc.jasuggest.JaSuggest; 5 | import net.jodah.expiringmap.ExpirationPolicy; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created by andreinicolinciobanu on 06/07/17. 11 | */ 12 | public class Example01 { 13 | public static void example01() { 14 | 15 | String[] words = { "us", "usa", "use", "useful", "useless", "user", "usurper", "ux", "util", "utility" }; 16 | 17 | JaCacheConfig jaCacheConfig = 18 | JaCacheConfig.builder() 19 | .maxSize(512) 20 | .expirationPolicy(ExpirationPolicy.ACCESSED) 21 | .build(); 22 | 23 | JaSuggest jaSuggest = JaSuggest.builder() 24 | .ignoreCase() 25 | .prebuiltWords() 26 | .withCache(jaCacheConfig) 27 | .buildFrom(words); 28 | 29 | List result = jaSuggest.findSuggestions("use"); 30 | 31 | System.out.println(result); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/net/andreinc/jasuggest/JaSuggestNoCacheTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Andrei N. Ciobanu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package net.andreinc.jasuggest; 18 | 19 | import org.junit.Test; 20 | 21 | import java.util.List; 22 | 23 | import static net.andreinc.jasuggest.TestUtils.getEnglishWords; 24 | import static net.andreinc.jasuggest.TestUtils.isStringListSorted; 25 | import static org.junit.Assert.assertTrue; 26 | 27 | public class JaSuggestNoCacheTest { 28 | 29 | private static final List ENGLISH_WORDS = getEnglishWords(); 30 | 31 | @Test 32 | public void testFindSuggestionsEmptyList() throws Exception { 33 | JaSuggest jaSuggest = JaSuggest.builder().buildFrom(); 34 | 35 | assertTrue(jaSuggest.findSuggestions("") != null); 36 | assertTrue(jaSuggest.findSuggestions("").size()==0); 37 | 38 | assertTrue(jaSuggest.findSuggestions("", false) != null); 39 | assertTrue(jaSuggest.findSuggestions("", false).size() == 0); 40 | 41 | assertTrue(jaSuggest.findSuggestions("", 100) != null); 42 | assertTrue(jaSuggest.findSuggestions("", 100).size()==0); 43 | } 44 | 45 | @Test 46 | public void testFindSuggestionsEmptyListIgnoreCase() throws Exception { 47 | JaSuggest jaSuggest = JaSuggest.builder().ignoreCase().buildFrom(); 48 | 49 | assertTrue(jaSuggest.findSuggestions("") != null); 50 | assertTrue(jaSuggest.findSuggestions("").size()==0); 51 | 52 | assertTrue(jaSuggest.findSuggestions("", false) != null); 53 | assertTrue(jaSuggest.findSuggestions("", false).size() == 0); 54 | 55 | assertTrue(jaSuggest.findSuggestions("", 100) != null); 56 | assertTrue(jaSuggest.findSuggestions("", 100).size()==0); 57 | } 58 | 59 | @Test 60 | public void testFindSuggestionsEmptyListPrebuilt() throws Exception { 61 | JaSuggest jaSuggest = JaSuggest.builder().prebuiltWords().buildFrom(); 62 | 63 | assertTrue(jaSuggest.findSuggestions("") != null); 64 | assertTrue(jaSuggest.findSuggestions("").size()==0); 65 | 66 | assertTrue(jaSuggest.findSuggestions("", false) != null); 67 | assertTrue(jaSuggest.findSuggestions("", false).size() == 0); 68 | 69 | assertTrue(jaSuggest.findSuggestions("", 100) != null); 70 | assertTrue(jaSuggest.findSuggestions("", 100).size()==0); 71 | } 72 | 73 | @Test 74 | public void testFindSuggestionsEmptyListPrebuiltIgnoreCase() throws Exception { 75 | JaSuggest jaSuggest = JaSuggest.builder().prebuiltWords().ignoreCase().buildFrom(); 76 | 77 | assertTrue(jaSuggest.findSuggestions("") != null); 78 | assertTrue(jaSuggest.findSuggestions("").size()==0); 79 | 80 | assertTrue(jaSuggest.findSuggestions("", false) != null); 81 | assertTrue(jaSuggest.findSuggestions("", false).size() == 0); 82 | 83 | assertTrue(jaSuggest.findSuggestions("", 100) != null); 84 | assertTrue(jaSuggest.findSuggestions("", 100).size()==0); 85 | } 86 | 87 | @Test 88 | public void testFindSuggestionsCorrectResults() throws Exception { 89 | 90 | JaSuggest jaSuggest = JaSuggest.builder().buildFrom(ENGLISH_WORDS); 91 | 92 | List resultAB = jaSuggest.findSuggestions("ab"); 93 | 94 | for(String suggestion : resultAB) { 95 | assertTrue(suggestion.startsWith("ab")); 96 | } 97 | 98 | assertTrue(isStringListSorted(resultAB)); 99 | } 100 | 101 | @Test 102 | public void testFindSuggestionsCorrectResultsIgnoreCase() throws Exception { 103 | 104 | JaSuggest jaSuggest = JaSuggest.builder().ignoreCase().buildFrom(ENGLISH_WORDS); 105 | JaSuggest jaSuggest2 = JaSuggest.builder().buildFrom(ENGLISH_WORDS); 106 | 107 | List resultAB = jaSuggest.findSuggestions("aB"); 108 | List resultAB2 = jaSuggest2.findSuggestions("ab"); 109 | 110 | assertTrue(resultAB.size() == resultAB2.size()); 111 | 112 | for(String suggestion : resultAB) { 113 | assertTrue(suggestion.startsWith("ab")); 114 | } 115 | 116 | for (int i = 0; i < resultAB.size(); i++) { 117 | assertTrue(resultAB.get(i).equals(resultAB2.get(i))); 118 | } 119 | 120 | assertTrue(isStringListSorted(resultAB)); 121 | } 122 | 123 | @Test 124 | public void testFindSuggestionsCorrectResultsPrebuilt() throws Exception { 125 | 126 | JaSuggest jaSuggest = JaSuggest.builder().prebuiltWords().buildFrom(ENGLISH_WORDS); 127 | 128 | List resultAB = jaSuggest.findSuggestions("ab"); 129 | 130 | for(String suggestion : resultAB) { 131 | assertTrue(suggestion.startsWith("ab")); 132 | } 133 | 134 | 135 | assertTrue(isStringListSorted(resultAB)); 136 | } 137 | 138 | @Test 139 | public void testFindSuggestionsCorrectResultsPrebuiltIgnoreCase() throws Exception { 140 | 141 | JaSuggest jaSuggest = JaSuggest.builder().prebuiltWords().ignoreCase().buildFrom(ENGLISH_WORDS); 142 | JaSuggest jaSuggest2 = JaSuggest.builder().prebuiltWords().buildFrom(ENGLISH_WORDS); 143 | 144 | List resultAB = jaSuggest.findSuggestions("Ab"); 145 | List resultAB2 = jaSuggest2.findSuggestions("ab"); 146 | 147 | assertTrue(resultAB.size() == resultAB2.size()); 148 | 149 | for(String suggestion : resultAB) { 150 | assertTrue(suggestion.startsWith("ab")); 151 | } 152 | 153 | for (int i = 0; i < resultAB.size(); i++) { 154 | assertTrue(resultAB.get(i).equals(resultAB2.get(i))); 155 | } 156 | 157 | assertTrue(isStringListSorted(resultAB)); 158 | } 159 | 160 | @Test 161 | public void testFindSuggestionsCorrectResultsNotSorted() throws Exception { 162 | JaSuggest jaSuggest = JaSuggest.builder().buildFrom(ENGLISH_WORDS); 163 | 164 | List resultABNotSorted = jaSuggest.findSuggestions("ab"); 165 | 166 | for(String suggestion: resultABNotSorted) { 167 | assertTrue(suggestion.startsWith("ab")); 168 | } 169 | } 170 | 171 | @Test 172 | public void testFindSuggestionsCorrectResultsNotSortedIgnoreCase() throws Exception { 173 | JaSuggest jaSuggest = JaSuggest.builder().ignoreCase().buildFrom(ENGLISH_WORDS); 174 | JaSuggest jaSuggest2 = JaSuggest.builder().buildFrom(ENGLISH_WORDS); 175 | 176 | List resultABNotSorted = jaSuggest.findSuggestions("Ab"); 177 | List resultABNotSorted2 = jaSuggest2.findSuggestions("ab"); 178 | 179 | assertTrue(resultABNotSorted.size() == resultABNotSorted2.size()); 180 | 181 | for(String suggestion: resultABNotSorted) { 182 | assertTrue(suggestion.startsWith("ab")); 183 | } 184 | 185 | for (int i = 0; i < resultABNotSorted.size(); i++) { 186 | assertTrue(resultABNotSorted.get(i).equals(resultABNotSorted2.get(i))); 187 | } 188 | 189 | } 190 | 191 | @Test 192 | public void testFindSuggestionsCorrectResultsNotSortedPrebuilt() throws Exception { 193 | JaSuggest jaSuggest = JaSuggest.builder().prebuiltWords().buildFrom(ENGLISH_WORDS); 194 | 195 | List resultABNotSorted = jaSuggest.findSuggestions("ab", false); 196 | 197 | for(String suggestion: resultABNotSorted) { 198 | assertTrue(suggestion.startsWith("ab")); 199 | } 200 | } 201 | 202 | @Test 203 | public void testFindSuggestionsCorrectResultsNotSortedPrebuiltIgnoreCase() throws Exception { 204 | JaSuggest jaSuggest = JaSuggest.builder().prebuiltWords().ignoreCase().buildFrom(ENGLISH_WORDS); 205 | JaSuggest jaSuggest2 = JaSuggest.builder().prebuiltWords().buildFrom(ENGLISH_WORDS); 206 | 207 | List resultABNotSorted = jaSuggest.findSuggestions("AB", false); 208 | List resultABNotSorted2 = jaSuggest2.findSuggestions("ab", false); 209 | 210 | assertTrue(resultABNotSorted.size() == resultABNotSorted2.size()); 211 | 212 | for(String suggestion: resultABNotSorted) { 213 | assertTrue(suggestion.startsWith("ab")); 214 | } 215 | 216 | for (int i = 0; i < resultABNotSorted.size(); i++) { 217 | assertTrue(resultABNotSorted.get(i).equals(resultABNotSorted2.get(i))); 218 | } 219 | } 220 | 221 | @Test 222 | public void testFindSuggestionsCorrectResultsMaxSize() throws Exception { 223 | JaSuggest jaSuggest = JaSuggest.builder().buildFrom(ENGLISH_WORDS); 224 | 225 | List resultABMax = jaSuggest.findSuggestions("ab", 10); 226 | 227 | assertTrue(resultABMax.size() == 10); 228 | 229 | for(String suggestion : resultABMax) { 230 | assertTrue(suggestion.startsWith("ab")); 231 | } 232 | 233 | assertTrue(isStringListSorted(resultABMax)); 234 | } 235 | 236 | @Test 237 | public void testFindSuggestionsCorrectResultsMaxSizeIgnoreCase() throws Exception { 238 | JaSuggest jaSuggest = JaSuggest.builder().ignoreCase().buildFrom(ENGLISH_WORDS); 239 | JaSuggest jaSuggest2 = JaSuggest.builder().buildFrom(ENGLISH_WORDS); 240 | 241 | List resultABMax = jaSuggest.findSuggestions("aB", 10); 242 | List resultABMax2 = jaSuggest.findSuggestions("ab", 10); 243 | 244 | assertTrue(resultABMax.size() == 10 && resultABMax2.size() == 10); 245 | 246 | for(String suggestion : resultABMax) { 247 | assertTrue(suggestion.startsWith("ab")); 248 | } 249 | 250 | for (int i = 0; i < resultABMax.size(); i++) { 251 | assertTrue(resultABMax.get(i).equals(resultABMax2.get(i))); 252 | } 253 | 254 | assertTrue(isStringListSorted(resultABMax)); 255 | } 256 | 257 | @Test 258 | public void testFindSuggestionsCorrectResultsMaxSizePrebuilt() throws Exception { 259 | JaSuggest jaSuggest = JaSuggest.builder().prebuiltWords().buildFrom(ENGLISH_WORDS); 260 | 261 | List resultABMax = jaSuggest.findSuggestions("ab", 10); 262 | 263 | assertTrue(resultABMax.size() == 10); 264 | 265 | for(String suggestion : resultABMax) { 266 | assertTrue(suggestion.startsWith("ab")); 267 | } 268 | 269 | assertTrue(isStringListSorted(resultABMax)); 270 | } 271 | 272 | @Test 273 | public void testFindSuggestionsCorrectResultsMaxSizePrebuiltIgnoreCase() throws Exception { 274 | JaSuggest jaSuggest = JaSuggest.builder().prebuiltWords().ignoreCase().buildFrom(ENGLISH_WORDS); 275 | JaSuggest jaSuggest2 = JaSuggest.builder().prebuiltWords().buildFrom(ENGLISH_WORDS); 276 | 277 | List resultABMax = jaSuggest.findSuggestions("aB", 10); 278 | List resultABMax2 = jaSuggest2.findSuggestions("ab", 10); 279 | 280 | assertTrue(resultABMax.size() == 10); 281 | assertTrue(resultABMax2.size() == 10); 282 | 283 | for(String suggestion : resultABMax) { 284 | assertTrue(suggestion.startsWith("ab")); 285 | } 286 | 287 | for (int i = 0; i < resultABMax.size(); i++) { 288 | assertTrue(resultABMax.get(i).equals(resultABMax2.get(i))); 289 | } 290 | 291 | assertTrue(isStringListSorted(resultABMax)); 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/test/java/net/andreinc/jasuggest/TestUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Andrei N. Ciobanu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package net.andreinc.jasuggest; 18 | 19 | import java.io.BufferedReader; 20 | import java.io.IOException; 21 | import java.io.InputStreamReader; 22 | import java.io.UncheckedIOException; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | import static java.nio.charset.Charset.defaultCharset; 27 | 28 | public class TestUtils { 29 | 30 | public static final String ENGLISH_WORDS_TXT = "english_words.text"; 31 | 32 | public static List getEnglishWords() { 33 | ClassLoader loader = TestUtils.class.getClassLoader(); 34 | List result = new ArrayList<>(); 35 | try (BufferedReader buff = new BufferedReader(new InputStreamReader(loader.getResourceAsStream(ENGLISH_WORDS_TXT), defaultCharset()))) { 36 | for (String line = buff.readLine(); line != null; line = buff.readLine()) { 37 | result.add(line); 38 | } 39 | } catch (IOException e) { 40 | throw new UncheckedIOException(e); 41 | } 42 | return result; 43 | } 44 | 45 | public static boolean isStringListSorted(List list) { 46 | String crt = ""; 47 | for(String str : list) { 48 | if (crt.compareTo(str) > 0) { 49 | return false; 50 | } 51 | crt = str; 52 | } 53 | return true; 54 | } 55 | } 56 | --------------------------------------------------------------------------------