Skip to content

Commit da5163a

Browse files
authored
Merge pull request #16 from scijava/improve-module-search
This branch adds the following search features to ModuleSearcher: * searching the menu * searching for parts of the search string divided by space individually It also adds a bunch of tests for the ModuleSearcher.
2 parents f7bea17 + fd6afdc commit da5163a

File tree

4 files changed

+171
-7
lines changed

4 files changed

+171
-7
lines changed

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,5 +118,10 @@
118118
<artifactId>prettytime</artifactId>
119119
<version>${prettytime.version}</version>
120120
</dependency>
121+
<dependency>
122+
<groupId>junit</groupId>
123+
<artifactId>junit</artifactId>
124+
<scope>test</scope>
125+
</dependency>
121126
</dependencies>
122127
</project>

src/main/java/org/scijava/search/module/ModuleSearcher.java

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
package org.scijava.search.module;
3131

32+
import java.util.Arrays;
3233
import java.util.Collections;
3334
import java.util.LinkedHashSet;
3435
import java.util.List;
@@ -80,22 +81,33 @@ public List<SearchResult> search(final String text, final boolean fuzzy) {
8081
.collect(Collectors.toList());
8182

8283
final String textLower = text.toLowerCase();
84+
final List<String> textLowerParts = Arrays.asList(textLower.split("\\s+"));
8385

84-
// First, add modules where title starts with the text.
86+
// Add modules where title starts with the text.
8587
modules.stream() //
8688
.filter(info -> startsWith(info, textLower) ) //
8789
.forEach(matches::add);
8890

89-
// Next, add modules where title has text inside somewhere.
91+
// Add modules where title has text inside somewhere.
9092
modules.stream() //
91-
.filter(info -> hasSubstring(info, textLower)) //
93+
.filter(info -> hasSubstringInTitle(info, textLower)) //
9294
.forEach(matches::add);
9395

94-
// Finally, add modules where menu path has text inside somewhere.
96+
// Add modules where menu path has text inside somewhere.
9597
modules.stream() //
96-
.filter(info -> hasSubstring(info, textLower)) //
98+
.filter(info -> hasSubstringInMenu(info, textLower)) //
9799
.forEach(matches::add);
98100

101+
// Add modules where title has all parts of the text inside somewhere.
102+
modules.stream() //
103+
.filter(info -> hasSubstringsInTitle(info, textLowerParts)) //
104+
.forEach(matches::add);
105+
106+
// Add modules where menu path has all parts of the text inside somewhere.
107+
modules.stream() //
108+
.filter(info -> hasSubstringsInMenu(info, textLowerParts)) //
109+
.forEach(matches::add);
110+
99111
// Wrap each matching ModuleInfo in a ModuleSearchResult.
100112
return matches.stream() //
101113
.map(info -> new ModuleSearchResult(info, baseDir)) //
@@ -160,11 +172,35 @@ private boolean startsWith(final ModuleInfo info, final String desiredLower) {
160172
return title != null && title.toLowerCase().startsWith(desiredLower);
161173
}
162174

163-
private boolean hasSubstring(final ModuleInfo info,
164-
final String desiredLower)
175+
private boolean hasSubstringInTitle(final ModuleInfo info,
176+
final String desiredLower)
165177
{
166178
final String title = title(info);
167179
return title != null && //
168180
title.toLowerCase().matches(".*" + desiredLower + ".*");
169181
}
182+
183+
private boolean hasSubstringsInTitle(final ModuleInfo info,
184+
final List<String> desiredLower)
185+
{
186+
final String title = title(info);
187+
if(title == null) return false;
188+
return desiredLower.stream().allMatch(part -> title.toLowerCase().contains(part));
189+
}
190+
191+
private boolean hasSubstringInMenu(final ModuleInfo info,
192+
final String desiredLower)
193+
{
194+
MenuPath menuPath = info.getMenuPath();
195+
if(menuPath == null) return false;
196+
return menuPath.stream().anyMatch(entry -> entry.getName().toLowerCase().contains(desiredLower));
197+
}
198+
199+
private boolean hasSubstringsInMenu(final ModuleInfo info,
200+
final List<String> desiredLower)
201+
{
202+
MenuPath menuPath = info.getMenuPath();
203+
if(menuPath == null) return false;
204+
return desiredLower.stream().allMatch(part -> menuPath.stream().anyMatch(entry -> entry.getName().toLowerCase().contains(part)));
205+
}
170206
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package org.scijava.search.module;
2+
3+
import org.junit.Before;
4+
import org.junit.Test;
5+
import org.scijava.Context;
6+
import org.scijava.InstantiableException;
7+
import org.scijava.MenuPath;
8+
import org.scijava.command.CommandInfo;
9+
import org.scijava.module.ModuleInfo;
10+
import org.scijava.module.ModuleService;
11+
import org.scijava.plugin.PluginInfo;
12+
import org.scijava.plugin.PluginService;
13+
import org.scijava.search.SearchResult;
14+
import org.scijava.search.Searcher;
15+
16+
import java.util.List;
17+
18+
import static org.junit.Assert.assertFalse;
19+
import static org.junit.Assert.assertNotNull;
20+
import static org.junit.Assert.assertTrue;
21+
22+
public class ModuleSearcherTest {
23+
24+
private ModuleService moduleService;
25+
private Searcher moduleSearcher;
26+
27+
@Before
28+
public void init() throws InstantiableException {
29+
Context context = new Context();
30+
moduleService = context.getService(ModuleService.class);
31+
PluginInfo<Searcher> info = context.getService(PluginService.class).getPlugin(ModuleSearcher.class, Searcher.class);
32+
moduleSearcher = info.createInstance();
33+
context.inject(moduleSearcher);
34+
}
35+
36+
@Test
37+
public void testMatchingBeginning() {
38+
createTestModule("Do something silly", "");
39+
List<SearchResult> results = moduleSearcher.search("Do", true);
40+
assertNotNull(results);
41+
assertTrue(results.size()>=1);
42+
assertTrue(containsModule(results, "Do something silly"));
43+
}
44+
45+
@Test
46+
public void testMatchingParts() {
47+
createTestModule("Do something silly", "");
48+
List<SearchResult> results = moduleSearcher.search("Do silly", true);
49+
assertNotNull(results);
50+
assertTrue(results.size()>=1);
51+
assertTrue(containsModule(results, "Do something silly"));
52+
}
53+
54+
@Test
55+
public void testMatchingEnd() {
56+
createTestModule("Do something silly", "");
57+
List<SearchResult> results = moduleSearcher.search("silly", true);
58+
assertNotNull(results);
59+
assertTrue(results.size()>=1);
60+
assertTrue(containsModule(results, "Do something silly"));
61+
}
62+
63+
@Test
64+
public void testCaseMismatch() {
65+
createTestModule("Do something silly", "");
66+
List<SearchResult> results = moduleSearcher.search("do", true);
67+
assertNotNull(results);
68+
assertTrue(results.size()>=1);
69+
assertTrue(containsModule(results, "Do something silly"));
70+
}
71+
72+
@Test
73+
public void testMenu() {
74+
createTestModule("nolabel", "Do>something>silly");
75+
List<SearchResult> results = moduleSearcher.search("silly", true);
76+
assertNotNull(results);
77+
assertTrue(results.size()>=1);
78+
assertTrue(containsModule(results, "nolabel"));
79+
}
80+
81+
@Test
82+
public void testMatchingPartsInMenu() {
83+
createTestModule("nolabel", "Do>something>silly");
84+
List<SearchResult> results = moduleSearcher.search("Do silly", true);
85+
assertNotNull(results);
86+
assertTrue(results.size()>=1);
87+
assertTrue(containsModule(results, "nolabel"));
88+
}
89+
90+
@Test
91+
public void testNonMatchingPartsInMenu() {
92+
createTestModule("nolabel", "Do>something>silly");
93+
List<SearchResult> results = moduleSearcher.search("Do nothing", true);
94+
assertNotNull(results);
95+
assertFalse(containsModule(results, "nolabel"));
96+
}
97+
98+
private boolean containsModule(List<SearchResult> results, String moduleName) {
99+
boolean foundModule = false;
100+
for(SearchResult result : results) {
101+
if(moduleName.equals(result.identifier())) foundModule = true;
102+
}
103+
return foundModule;
104+
}
105+
106+
private void createTestModule(String label, String menuPath) {
107+
ModuleInfo info = new CommandInfo(TestCommand.class);
108+
info.setLabel(label);
109+
info.setMenuPath(new MenuPath(menuPath));
110+
moduleService.addModule(info);
111+
}
112+
113+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.scijava.search.module;
2+
3+
import org.scijava.command.Command;
4+
5+
public class TestCommand implements Command {
6+
@Override
7+
public void run() {
8+
9+
}
10+
}

0 commit comments

Comments
 (0)