LUCENE-9570: code reformatting [final].

This commit is contained in:
Dawid Weiss 2021-01-05 13:44:05 +01:00
parent 2695624a9f
commit 2cbf261032
1285 changed files with 62211 additions and 49131 deletions

View File

@ -227,6 +227,22 @@ configure(project(":lucene:queryparser")) {
}
}
task regenerate() {
description "Regenerate any generated sources"
group "generation"
// Run regeneration tasks.
dependsOn javaccParserClassic, javaccParserSurround, javaccParserFlexible
// Clean up and reformat the generated sources after generation.
dependsOn "tidy"
}
// Make sure tidy runs after generation, if they're defined.
tasks.matching { it.name == "tidy" }.configureEach {
mustRunAfter javaccParserClassic, javaccParserSurround, javaccParserFlexible
}
task javacc() {
description "Regenerate query parsers (javacc syntax definitions)."
group "generation"
@ -244,7 +260,6 @@ configure(project(":solr:core")) {
javaccFile = file('src/java/org/apache/solr/parser/QueryParser.jj')
afterGenerate << commonCleanups
afterGenerate << { FileTree generatedFiles ->
generatedFiles.matching { include "QueryParser.java" }.each { file ->

View File

@ -16,116 +16,89 @@
*/
/*
* LUCENE-9564: This adds automatic (and enforced) code formatting.
* LUCENE-9564: This adds automatic (and enforced) code formatting using
* spotless and Google Java Format.
*/
def resources = scriptResources(buildscript)
allprojects { prj ->
configure(project(":lucene").subprojects) { prj ->
plugins.withType(JavaPlugin) {
prj.apply plugin: 'com.diffplug.spotless'
spotless {
java {
// TODO: work out how to have multiple different header files (we have
// classes in the codebase that have original headers).
// TODO: Work out how to support multiple different header files (we have
// classes in the codebase that have original headers). We currently use
// Apache RAT to enforce headers so this is of lesser priority.
//
// licenseHeaderFile file("${resources}/asl-header.txt"), '^(\\s*package)'
lineEndings 'UNIX'
endWithNewline()
googleJavaFormat('1.9')
// Apply to all Java sources
target "src/**/*.java"
// Exclude certain files (generated ones, mostly).
switch (project.path) {
// These modules are complete - all sources scanned.
case ":lucene:core":
target "src/java/**/*.java",
"src/test/**/*.java"
targetExclude "**/resources/**", "**/StandardTokenizerImpl.java"
break
case ":lucene:highlighter":
target "src/java/**/*.java",
"src/test/**/*.java"
targetExclude "**/resources/**"
break
case ":lucene:queries":
target "src/java/**/*.java",
"src/test/**/*.java"
targetExclude "**/resources/**"
targetExclude "**/StandardTokenizerImpl.java"
break
case ":lucene:analysis:common":
target "src/**/*.java"
targetExclude "**/resources/**",
"**/HTMLStripCharFilter.java",
targetExclude "**/HTMLStripCharFilter.java",
"**/UAX29URLEmailTokenizerImpl.java",
"**/tartarus/**"
break
case ":lucene:demo":
case ":lucene:analysis:morfologik":
case ":lucene:analysis:icu":
case ":lucene:analysis:kuromoji":
case ":lucene:memory":
case ":lucene:benchmark":
case ":lucene:analysis:nori":
case ":lucene:analysis:opennlp":
case ":lucene:analysis:phonetic":
case ":lucene:analysis:smartcn":
case ":lucene:analysis:stempel":
case ":lucene:classification":
case ":lucene:backward-codecs":
case ":lucene:codecs":
case ":lucene:join":
target "src/**/*.java"
targetExclude "**/resources/**"
case ":lucene:test-framework":
targetExclude "**/EmojiTokenizationTestUnicode_11_0.java",
"**/WordBreakTestUnicode_9_0_0.java"
break
case ":lucene:expressions":
target "src/**/*.java"
targetExclude "**/resources/**", "**/JavascriptLexer.java", "**/JavascriptParser.java",
targetExclude "**/JavascriptLexer.java",
"**/JavascriptParser.java",
"**/JavascriptVisitor.java"
break
// Partially complete.
case ":lucene:facet":
target "src/**/*.java"
targetExclude "**/taxonomy.8.6.3-cfs.zip"
break
// All others - disable reformatting/ checks for now.
case ":lucene:grouping":
case ":lucene:luke":
case ":lucene:misc":
case ":lucene:monitor":
case ":lucene:queryparser":
case ":lucene:replicator":
case ":lucene:sandbox":
case ":lucene:spatial3d":
case ":lucene:spatial-extras":
case ":lucene:suggest":
case ":lucene:test-framework":
default:
target 'non-existing/**'
targetExclude "**/classic/ParseException.java",
"**/classic/QueryParser.java",
"**/classic/QueryParserConstants.java",
"**/classic/QueryParserTokenManager.java",
"**/classic/Token.java",
"**/classic/TokenMgrError.java",
"**/standard/parser/ParseException.java",
"**/standard/parser/StandardSyntaxParser.java",
"**/standard/parser/StandardSyntaxParserConstants.java",
"**/standard/parser/StandardSyntaxParserTokenManager.java",
"**/standard/parser/Token.java",
"**/standard/parser/TokenMgrError.java",
"**/surround/parser/ParseException.java",
"**/surround/parser/QueryParser.java",
"**/surround/parser/QueryParserConstants.java",
"**/surround/parser/QueryParserTokenManager.java",
"**/surround/parser/Token.java",
"**/surround/parser/TokenMgrError.java"
break
}
}
}
// Workaround for an odd problem in spotless where it fails because
// of a missing folder.
spotlessJava {
doFirst {
project.mkdir("${buildDir}/spotless/spotlessJava")
}
}
}
}
// Add an alias to 'spotlessApply' simply called 'tidy' and add
// spotlessCheck to check.
allprojects { prj ->
// Add an alias to 'spotlessApply' simply called 'tidy' and wire up
// spotlessCheck to convention's check.
task tidy() {
description "Applies formatters and cleanups to sources."
group "verification"

View File

@ -191,6 +191,10 @@ Bug fixes
Other
* LUCENE-9570, LUCENE-9564: Apply google java format and enforce it on source Java files.
Review diffs and correct automatic formatting oddities. (Erick Erickson,
Bruno Roustant, Dawid Weiss)
* LUCENE-9631: Properly override slice() on subclasses of OffsetRange. (Dawid Weiss)
* LUCENE-9312: Allow gradle builds against arbitrary JVMs. (Tomoko Uchida, Dawid Weiss)

View File

@ -76,7 +76,10 @@ public final class HitQueue extends PriorityQueue<ScoreDoc> {
@Override
protected final boolean lessThan(ScoreDoc hitA, ScoreDoc hitB) {
if (hitA.score == hitB.score) return hitA.doc > hitB.doc;
else return hitA.score < hitB.score;
if (hitA.score == hitB.score) {
return hitA.doc > hitB.doc;
} else {
return hitA.score < hitB.score;
}
}
}

View File

@ -177,9 +177,8 @@ public class NIOFSDirectory extends FSDirectory {
b.limit(b.position() + toRead);
assert b.remaining() == toRead;
final int i = channel.read(b, pos);
if (i
< 0) { // be defensive here, even though we checked before hand, something could have
// changed
if (i < 0) {
// be defensive here, even though we checked before hand, something could have changed
throw new EOFException(
"read past EOF: "
+ this
@ -191,7 +190,8 @@ public class NIOFSDirectory extends FSDirectory {
+ end);
}
assert i > 0
: "FileChannel.read with non zero-length bb.remaining() must always read at least one byte (FileChannel is in blocking mode, see spec of ReadableByteChannel)";
: "FileChannel.read with non zero-length bb.remaining() must always read at least "
+ "one byte (FileChannel is in blocking mode, see spec of ReadableByteChannel)";
pos += i;
readLength -= i;
}

View File

@ -20,7 +20,6 @@ import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.FieldComparator;
@ -33,14 +32,14 @@ import org.apache.lucene.search.SortField;
import org.apache.lucene.util.FixedBitSet;
/**
* This collector specializes in collecting the most relevant document (group head) for each
* group that matches the query.
* This collector specializes in collecting the most relevant document (group head) for each group
* that matches the query.
*
* Clients should create new collectors by calling {@link #newCollector(GroupSelector, Sort)}
* <p>Clients should create new collectors by calling {@link #newCollector(GroupSelector, Sort)}
*
* @lucene.experimental
*/
@SuppressWarnings({"unchecked","rawtypes"})
@SuppressWarnings({"unchecked", "rawtypes"})
public abstract class AllGroupHeadsCollector<T> extends SimpleCollector {
private final GroupSelector<T> groupSelector;
@ -56,13 +55,15 @@ public abstract class AllGroupHeadsCollector<T> extends SimpleCollector {
/**
* Create a new AllGroupHeadsCollector based on the type of within-group Sort required
*
* @param selector a GroupSelector to define the groups
* @param sort the within-group sort to use to choose the group head document
* @param <T> the group value type
* @param sort the within-group sort to use to choose the group head document
* @param <T> the group value type
*/
public static <T> AllGroupHeadsCollector<T> newCollector(GroupSelector<T> selector, Sort sort) {
if (sort.equals(Sort.RELEVANCE))
if (sort.equals(Sort.RELEVANCE)) {
return new ScoringGroupHeadsCollector<>(selector, sort);
}
return new SortingGroupHeadsCollector<>(selector, sort);
}
@ -93,7 +94,8 @@ public abstract class AllGroupHeadsCollector<T> extends SimpleCollector {
}
/**
* @return an int array containing all group heads. The size of the array is equal to number of collected unique groups.
* @return an int array containing all group heads. The size of the array is equal to number of
* collected unique groups.
*/
public int[] retrieveGroupHeads() {
Collection<? extends GroupHead<T>> groupHeads = getCollectedGroupHeads();
@ -107,16 +109,13 @@ public abstract class AllGroupHeadsCollector<T> extends SimpleCollector {
return docHeads;
}
/**
* @return the number of group heads found for a query.
*/
/** @return the number of group heads found for a query. */
public int groupHeadsSize() {
return getCollectedGroupHeads().size();
}
/**
* Returns the collected group heads.
* Subsequent calls should return the same group heads.
* Returns the collected group heads. Subsequent calls should return the same group heads.
*
* @return the collected group heads
*/
@ -179,49 +178,46 @@ public abstract class AllGroupHeadsCollector<T> extends SimpleCollector {
/**
* Create a new GroupHead for the given group value, initialized with a doc, context and scorer
*/
protected abstract GroupHead<T> newGroupHead(int doc, T value, LeafReaderContext context, Scorable scorer) throws IOException;
protected abstract GroupHead<T> newGroupHead(
int doc, T value, LeafReaderContext context, Scorable scorer) throws IOException;
/**
* Represents a group head. A group head is the most relevant document for a particular group.
* The relevancy is based is usually based on the sort.
* Represents a group head. A group head is the most relevant document for a particular group. The
* relevancy is based is usually based on the sort.
*
* The group head contains a group value with its associated most relevant document id.
* <p>The group head contains a group value with its associated most relevant document id.
*/
public static abstract class GroupHead<T> {
public abstract static class GroupHead<T> {
public final T groupValue;
public int doc;
protected int docBase;
/**
* Create a new GroupHead for the given value
*/
/** Create a new GroupHead for the given value */
protected GroupHead(T groupValue, int doc, int docBase) {
this.groupValue = groupValue;
this.doc = doc + docBase;
this.docBase = docBase;
}
/**
* Called for each segment
*/
/** Called for each segment */
protected void setNextReader(LeafReaderContext ctx) throws IOException {
this.docBase = ctx.docBase;
}
/**
* Called for each segment
*/
/** Called for each segment */
protected abstract void setScorer(Scorable scorer) throws IOException;
/**
* Compares the specified document for a specified comparator against the current most relevant document.
* Compares the specified document for a specified comparator against the current most relevant
* document.
*
* @param compIDX The comparator index of the specified comparator.
* @param doc The specified document.
* @return -1 if the specified document wasn't competitive against the current most relevant document, 1 if the
* specified document was competitive against the current most relevant document. Otherwise 0.
* @return -1 if the specified document wasn't competitive against the current most relevant
* document, 1 if the specified document was competitive against the current most relevant
* document. Otherwise 0.
* @throws IOException If I/O related errors occur
*/
protected abstract int compare(int compIDX, int doc) throws IOException;
@ -233,12 +229,9 @@ public abstract class AllGroupHeadsCollector<T> extends SimpleCollector {
* @throws IOException If I/O related errors occur
*/
protected abstract void updateDocHead(int doc) throws IOException;
}
/**
* General implementation using a {@link FieldComparator} to select the group head
*/
/** General implementation using a {@link FieldComparator} to select the group head */
private static class SortingGroupHeadsCollector<T> extends AllGroupHeadsCollector<T> {
protected SortingGroupHeadsCollector(GroupSelector<T> selector, Sort sort) {
@ -246,7 +239,8 @@ public abstract class AllGroupHeadsCollector<T> extends SimpleCollector {
}
@Override
protected GroupHead<T> newGroupHead(int doc, T value, LeafReaderContext ctx, Scorable scorer) throws IOException {
protected GroupHead<T> newGroupHead(int doc, T value, LeafReaderContext ctx, Scorable scorer)
throws IOException {
return new SortingGroupHead<>(sort, value, doc, ctx, scorer);
}
}
@ -256,7 +250,9 @@ public abstract class AllGroupHeadsCollector<T> extends SimpleCollector {
final FieldComparator[] comparators;
final LeafFieldComparator[] leafComparators;
protected SortingGroupHead(Sort sort, T groupValue, int doc, LeafReaderContext context, Scorable scorer) throws IOException {
protected SortingGroupHead(
Sort sort, T groupValue, int doc, LeafReaderContext context, Scorable scorer)
throws IOException {
super(groupValue, doc, context.docBase);
final SortField[] sortFields = sort.getSort();
comparators = new FieldComparator[sortFields.length];
@ -300,9 +296,7 @@ public abstract class AllGroupHeadsCollector<T> extends SimpleCollector {
}
}
/**
* Specialized implementation for sorting by score
*/
/** Specialized implementation for sorting by score */
private static class ScoringGroupHeadsCollector<T> extends AllGroupHeadsCollector<T> {
protected ScoringGroupHeadsCollector(GroupSelector<T> selector, Sort sort) {
@ -310,7 +304,8 @@ public abstract class AllGroupHeadsCollector<T> extends SimpleCollector {
}
@Override
protected GroupHead<T> newGroupHead(int doc, T value, LeafReaderContext context, Scorable scorer) throws IOException {
protected GroupHead<T> newGroupHead(
int doc, T value, LeafReaderContext context, Scorable scorer) throws IOException {
return new ScoringGroupHead<>(scorer, value, doc, context.docBase);
}
}
@ -320,7 +315,8 @@ public abstract class AllGroupHeadsCollector<T> extends SimpleCollector {
private Scorable scorer;
private float topScore;
protected ScoringGroupHead(Scorable scorer, T groupValue, int doc, int docBase) throws IOException {
protected ScoringGroupHead(Scorable scorer, T groupValue, int doc, int docBase)
throws IOException {
super(groupValue, doc, docBase);
assert scorer.docID() == doc;
this.scorer = scorer;
@ -338,8 +334,9 @@ public abstract class AllGroupHeadsCollector<T> extends SimpleCollector {
assert compIDX == 0;
float score = scorer.score();
int c = Float.compare(score, topScore);
if (c > 0)
if (c > 0) {
topScore = score;
}
return c;
}
@ -348,5 +345,4 @@ public abstract class AllGroupHeadsCollector<T> extends SimpleCollector {
this.doc = doc + docBase;
}
}
}

View File

@ -20,17 +20,14 @@ import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.Scorable;
import org.apache.lucene.search.ScoreMode;
import org.apache.lucene.search.SimpleCollector;
/**
* A collector that collects all groups that match the
* query. Only the group value is collected, and the order
* is undefined. This collector does not determine
* the most relevant document of a group.
* A collector that collects all groups that match the query. Only the group value is collected, and
* the order is undefined. This collector does not determine the most relevant document of a group.
*
* @lucene.experimental
*/
@ -42,6 +39,7 @@ public class AllGroupsCollector<T> extends SimpleCollector {
/**
* Create a new AllGroupsCollector
*
* @param groupSelector the GroupSelector to determine groups
*/
public AllGroupsCollector(GroupSelector<T> groupSelector) {
@ -49,8 +47,10 @@ public class AllGroupsCollector<T> extends SimpleCollector {
}
/**
* Returns the total number of groups for the executed search.
* This is a convenience method. The following code snippet has the same effect: <pre>getGroups().size()</pre>
* Returns the total number of groups for the executed search. This is a convenience method. The
* following code snippet has the same effect:
*
* <pre>getGroups().size()</pre>
*
* @return The total number of groups for the executed search
*/
@ -60,8 +60,8 @@ public class AllGroupsCollector<T> extends SimpleCollector {
/**
* Returns the group values
* <p>
* This is an unordered collections of group values.
*
* <p>This is an unordered collections of group values.
*
* @return the group values
*/
@ -80,8 +80,9 @@ public class AllGroupsCollector<T> extends SimpleCollector {
@Override
public void collect(int doc) throws IOException {
groupSelector.advanceTo(doc);
if (groups.contains(groupSelector.currentValue()))
if (groups.contains(groupSelector.currentValue())) {
return;
}
groups.add(groupSelector.copyValue());
}
@ -89,4 +90,4 @@ public class AllGroupsCollector<T> extends SimpleCollector {
public ScoreMode scoreMode() {
return ScoreMode.COMPLETE_NO_SCORES; // the result is unaffected by relevancy
}
}
}

View File

@ -17,7 +17,6 @@
package org.apache.lucene.search.grouping;
import java.io.IOException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.DocIdSetIterator;
@ -40,29 +39,22 @@ import org.apache.lucene.util.ArrayUtil;
import org.apache.lucene.util.PriorityQueue;
// TODO: this sentence is too long for the class summary.
/** BlockGroupingCollector performs grouping with a
* single pass collector, as long as you are grouping by a
* doc block field, ie all documents sharing a given group
* value were indexed as a doc block using the atomic
* {@link IndexWriter#addDocuments IndexWriter.addDocuments()}
* or {@link IndexWriter#updateDocuments IndexWriter.updateDocuments()}
* API.
/**
* BlockGroupingCollector performs grouping with a single pass collector, as long as you are
* grouping by a doc block field, ie all documents sharing a given group value were indexed as a doc
* block using the atomic {@link IndexWriter#addDocuments IndexWriter.addDocuments()} or {@link
* IndexWriter#updateDocuments IndexWriter.updateDocuments()} API.
*
* <p>This results in faster performance (~25% faster QPS)
* than the two-pass grouping collectors, with the tradeoff
* being that the documents in each group must always be
* indexed as a block. This collector also fills in
* TopGroups.totalGroupCount without requiring the separate
* {@link org.apache.lucene.search.grouping.AllGroupsCollector}. However, this collector does
* not fill in the groupValue of each group; this field
* will always be null.
* <p>This results in faster performance (~25% faster QPS) than the two-pass grouping collectors,
* with the tradeoff being that the documents in each group must always be indexed as a block. This
* collector also fills in TopGroups.totalGroupCount without requiring the separate {@link
* org.apache.lucene.search.grouping.AllGroupsCollector}. However, this collector does not fill in
* the groupValue of each group; this field will always be null.
*
* <p><b>NOTE</b>: this collector makes no effort to verify
* the docs were in fact indexed as a block, so it's up to
* you to ensure this was the case.
* <p><b>NOTE</b>: this collector makes no effort to verify the docs were in fact indexed as a
* block, so it's up to you to ensure this was the case.
*
* <p>See {@link org.apache.lucene.search.grouping} for more
* details including a full code example.</p>
* <p>See {@link org.apache.lucene.search.grouping} for more details including a full code example.
*
* @lucene.experimental
*/
@ -104,14 +96,14 @@ public class BlockGroupingCollector extends SimpleCollector {
private static final class OneGroup {
LeafReaderContext readerContext;
//int groupOrd;
// int groupOrd;
int topGroupDoc;
int[] docs;
float[] scores;
int count;
int comparatorSlot;
}
// Sorts by groupSort. Not static -- uses comparators, reversed
private final class GroupQueue extends PriorityQueue<OneGroup> {
@ -122,13 +114,15 @@ public class BlockGroupingCollector extends SimpleCollector {
@Override
protected boolean lessThan(final OneGroup group1, final OneGroup group2) {
//System.out.println(" ltcheck");
// System.out.println(" ltcheck");
assert group1 != group2;
assert group1.comparatorSlot != group2.comparatorSlot;
final int numComparators = comparators.length;
for (int compIDX=0;compIDX < numComparators; compIDX++) {
final int c = reversed[compIDX] * comparators[compIDX].compare(group1.comparatorSlot, group2.comparatorSlot);
for (int compIDX = 0; compIDX < numComparators; compIDX++) {
final int c =
reversed[compIDX]
* comparators[compIDX].compare(group1.comparatorSlot, group2.comparatorSlot);
if (c != 0) {
// Short circuit
return c > 0;
@ -144,7 +138,8 @@ public class BlockGroupingCollector extends SimpleCollector {
// group is competitive we insert into the group queue
private void processGroup() throws IOException {
totalGroupCount++;
//System.out.println(" processGroup ord=" + lastGroupOrd + " competes=" + groupCompetes + " count=" + subDocUpto + " groupDoc=" + topGroupDoc);
// System.out.println(" processGroup ord=" + lastGroupOrd + " competes=" + groupCompetes + "
// count=" + subDocUpto + " groupDoc=" + topGroupDoc);
if (groupCompetes) {
if (!queueFull) {
// Startup transient: always add a new OneGroup
@ -158,20 +153,21 @@ public class BlockGroupingCollector extends SimpleCollector {
pendingSubScores = new float[10];
}
og.readerContext = currentReaderContext;
//og.groupOrd = lastGroupOrd;
// og.groupOrd = lastGroupOrd;
og.comparatorSlot = bottomSlot;
final OneGroup bottomGroup = groupQueue.add(og);
//System.out.println(" ADD group=" + getGroupString(lastGroupOrd) + " newBottom=" + getGroupString(bottomGroup.groupOrd));
// System.out.println(" ADD group=" + getGroupString(lastGroupOrd) + " newBottom=" +
// getGroupString(bottomGroup.groupOrd));
queueFull = groupQueue.size() == topNGroups;
if (queueFull) {
// Queue just became full; now set the real bottom
// in the comparators:
bottomSlot = bottomGroup.comparatorSlot;
//System.out.println(" set bottom=" + bottomSlot);
// System.out.println(" set bottom=" + bottomSlot);
for (int i = 0; i < comparators.length; i++) {
leafComparators[i].setBottom(bottomSlot);
}
//System.out.println(" QUEUE FULL");
// System.out.println(" QUEUE FULL");
} else {
// Queue not full yet -- just advance bottomSlot:
bottomSlot = groupQueue.size();
@ -193,10 +189,10 @@ public class BlockGroupingCollector extends SimpleCollector {
pendingSubScores = savScores;
}
og.readerContext = currentReaderContext;
//og.groupOrd = lastGroupOrd;
// og.groupOrd = lastGroupOrd;
bottomSlot = groupQueue.updateTop().comparatorSlot;
//System.out.println(" set bottom=" + bottomSlot);
// System.out.println(" set bottom=" + bottomSlot);
for (int i = 0; i < comparators.length; i++) {
leafComparators[i].setBottom(bottomSlot);
}
@ -208,22 +204,17 @@ public class BlockGroupingCollector extends SimpleCollector {
/**
* Create the single pass collector.
*
* @param groupSort The {@link Sort} used to sort the
* groups. The top sorted document within each group
* according to groupSort, determines how that group
* sorts against other groups. This must be non-null,
* ie, if you want to groupSort by relevance use
* Sort.RELEVANCE.
* @param topNGroups How many top groups to keep.
* @param needsScores true if the collected documents
* require scores, either because relevance is included
* in the withinGroupSort or because you plan to pass true
* for either getSscores or getMaxScores to {@link
* #getTopGroups}
* @param lastDocPerGroup a {@link Weight} that marks the
* last document in each group.
* @param groupSort The {@link Sort} used to sort the groups. The top sorted document within each
* group according to groupSort, determines how that group sorts against other groups. This
* must be non-null, ie, if you want to groupSort by relevance use Sort.RELEVANCE.
* @param topNGroups How many top groups to keep.
* @param needsScores true if the collected documents require scores, either because relevance is
* included in the withinGroupSort or because you plan to pass true for either getSscores or
* getMaxScores to {@link #getTopGroups}
* @param lastDocPerGroup a {@link Weight} that marks the last document in each group.
*/
public BlockGroupingCollector(Sort groupSort, int topNGroups, boolean needsScores, Weight lastDocPerGroup) {
public BlockGroupingCollector(
Sort groupSort, int topNGroups, boolean needsScores, Weight lastDocPerGroup) {
if (topNGroups < 1) {
throw new IllegalArgumentException("topNGroups must be >= 1 (got " + topNGroups + ")");
@ -239,7 +230,7 @@ public class BlockGroupingCollector extends SimpleCollector {
this.lastDocPerGroup = lastDocPerGroup;
this.groupSort = groupSort;
this.topNGroups = topNGroups;
final SortField[] sortFields = groupSort.getSort();
@ -259,29 +250,26 @@ public class BlockGroupingCollector extends SimpleCollector {
// typically they will be presented as a "single" result
// in the UI?
/** Returns the grouped results. Returns null if the
* number of groups collected is &lt;= groupOffset.
/**
* Returns the grouped results. Returns null if the number of groups collected is &lt;=
* groupOffset.
*
* <p><b>NOTE</b>: This collector is unable to compute
* the groupValue per group so it will always be null.
* This is normally not a problem, as you can obtain the
* value just like you obtain other values for each
* matching document (eg, via stored fields, via
* DocValues, etc.)
* <p><b>NOTE</b>: This collector is unable to compute the groupValue per group so it will always
* be null. This is normally not a problem, as you can obtain the value just like you obtain other
* values for each matching document (eg, via stored fields, via DocValues, etc.)
*
* @param withinGroupSort The {@link Sort} used to sort
* documents within each group.
* @param groupOffset Which group to start from
* @param withinGroupOffset Which document to start from
* within each group
* @param maxDocsPerGroup How many top documents to keep
* within each group.
* @param withinGroupSort The {@link Sort} used to sort documents within each group.
* @param groupOffset Which group to start from
* @param withinGroupOffset Which document to start from within each group
* @param maxDocsPerGroup How many top documents to keep within each group.
*/
public TopGroups<?> getTopGroups(Sort withinGroupSort, int groupOffset, int withinGroupOffset, int maxDocsPerGroup) throws IOException {
public TopGroups<?> getTopGroups(
Sort withinGroupSort, int groupOffset, int withinGroupOffset, int maxDocsPerGroup)
throws IOException {
//if (queueFull) {
//System.out.println("getTopGroups groupOffset=" + groupOffset + " topNGroups=" + topNGroups);
//}
// if (queueFull) {
// System.out.println("getTopGroups groupOffset=" + groupOffset + " topNGroups=" + topNGroups);
// }
if (subDocUpto != 0) {
processGroup();
}
@ -294,9 +282,9 @@ public class BlockGroupingCollector extends SimpleCollector {
float maxScore = Float.MIN_VALUE;
@SuppressWarnings({"unchecked","rawtypes"})
@SuppressWarnings({"unchecked", "rawtypes"})
final GroupDocs<Object>[] groups = new GroupDocs[groupQueue.size() - groupOffset];
for(int downTo=groupQueue.size()-groupOffset-1;downTo>=0;downTo--) {
for (int downTo = groupQueue.size() - groupOffset - 1; downTo >= 0; downTo--) {
final OneGroup og = groupQueue.pop();
// At this point we hold all docs w/ in each group,
@ -305,18 +293,21 @@ public class BlockGroupingCollector extends SimpleCollector {
if (withinGroupSort.equals(Sort.RELEVANCE)) {
// Sort by score
if (!needsScores) {
throw new IllegalArgumentException("cannot sort by relevance within group: needsScores=false");
throw new IllegalArgumentException(
"cannot sort by relevance within group: needsScores=false");
}
collector = TopScoreDocCollector.create(maxDocsPerGroup, Integer.MAX_VALUE);
} else {
// Sort by fields
collector = TopFieldCollector.create(withinGroupSort, maxDocsPerGroup, Integer.MAX_VALUE); // TODO: disable exact counts?
collector =
TopFieldCollector.create(
withinGroupSort, maxDocsPerGroup, Integer.MAX_VALUE); // TODO: disable exact counts?
}
float groupMaxScore = needsScores ? Float.NEGATIVE_INFINITY : Float.NaN;
LeafCollector leafCollector = collector.getLeafCollector(og.readerContext);
leafCollector.setScorer(fakeScorer);
for(int docIDX=0;docIDX<og.count;docIDX++) {
for (int docIDX = 0; docIDX < og.count; docIDX++) {
final int doc = og.docs[docIDX];
fakeScorer.doc = doc;
if (needsScores) {
@ -330,7 +321,7 @@ public class BlockGroupingCollector extends SimpleCollector {
final Object[] groupSortValues;
groupSortValues = new Comparable<?>[comparators.length];
for(int sortFieldIDX=0;sortFieldIDX<comparators.length;sortFieldIDX++) {
for (int sortFieldIDX = 0; sortFieldIDX < comparators.length; sortFieldIDX++) {
groupSortValues[sortFieldIDX] = comparators[sortFieldIDX].value(og.comparatorSlot);
}
@ -338,12 +329,14 @@ public class BlockGroupingCollector extends SimpleCollector {
// TODO: we could aggregate scores across children
// by Sum/Avg instead of passing NaN:
groups[downTo] = new GroupDocs<>(Float.NaN,
groupMaxScore,
new TotalHits(og.count, TotalHits.Relation.EQUAL_TO),
topDocs.scoreDocs,
null,
groupSortValues);
groups[downTo] =
new GroupDocs<>(
Float.NaN,
groupMaxScore,
new TotalHits(og.count, TotalHits.Relation.EQUAL_TO),
topDocs.scoreDocs,
null,
groupSortValues);
maxScore = Math.max(maxScore, groupMaxScore);
}
@ -355,10 +348,15 @@ public class BlockGroupingCollector extends SimpleCollector {
}
*/
return new TopGroups<>(new TopGroups<>(groupSort.getSort(),
withinGroupSort.getSort(),
totalHitCount, totalGroupedHitCount, groups, maxScore),
totalGroupCount);
return new TopGroups<>(
new TopGroups<>(
groupSort.getSort(),
withinGroupSort.getSort(),
totalHitCount,
totalGroupedHitCount,
groups,
maxScore),
totalGroupCount);
}
@Override
@ -380,7 +378,7 @@ public class BlockGroupingCollector extends SimpleCollector {
processGroup();
}
groupEndDocID = lastDocPerGroupBits.advance(doc);
//System.out.println(" adv " + groupEndDocID + " " + lastDocPerGroupBits);
// System.out.println(" adv " + groupEndDocID + " " + lastDocPerGroupBits);
subDocUpto = 0;
groupCompetes = !queueFull;
}
@ -404,15 +402,15 @@ public class BlockGroupingCollector extends SimpleCollector {
if (subDocUpto == 1) {
assert !queueFull;
//System.out.println(" init copy to bottomSlot=" + bottomSlot);
// System.out.println(" init copy to bottomSlot=" + bottomSlot);
for (LeafFieldComparator fc : leafComparators) {
fc.copy(bottomSlot, doc);
fc.setBottom(bottomSlot);
}
}
topGroupDoc = doc;
} else {
// Compare to bottomSlot
for (int compIDX = 0;; compIDX++) {
for (int compIDX = 0; ; compIDX++) {
final int c = reversed[compIDX] * leafComparators[compIDX].compareBottom(doc);
if (c < 0) {
// Definitely not competitive -- done
@ -428,25 +426,25 @@ public class BlockGroupingCollector extends SimpleCollector {
}
}
//System.out.println(" best w/in group!");
// System.out.println(" best w/in group!");
for (LeafFieldComparator fc : leafComparators) {
fc.copy(bottomSlot, doc);
// Necessary because some comparators cache
// details of bottom slot; this forces them to
// re-cache:
fc.setBottom(bottomSlot);
}
}
topGroupDoc = doc;
}
} else {
// We're not sure this group will make it into the
// queue yet
for (int compIDX = 0;; compIDX++) {
for (int compIDX = 0; ; compIDX++) {
final int c = reversed[compIDX] * leafComparators[compIDX].compareBottom(doc);
if (c < 0) {
// Definitely not competitive -- done
//System.out.println(" doc doesn't compete w/ top groups");
// System.out.println(" doc doesn't compete w/ top groups");
return;
} else if (c > 0) {
// Definitely competitive.
@ -455,7 +453,7 @@ public class BlockGroupingCollector extends SimpleCollector {
// Ties with bottom, except we know this docID is
// > docID in the queue (docs are visited in
// order), so not competitive:
//System.out.println(" doc doesn't compete w/ top groups");
// System.out.println(" doc doesn't compete w/ top groups");
return;
}
}
@ -468,7 +466,7 @@ public class BlockGroupingCollector extends SimpleCollector {
fc.setBottom(bottomSlot);
}
topGroupDoc = doc;
//System.out.println(" doc competes w/ top groups");
// System.out.println(" doc competes w/ top groups");
}
}
@ -479,7 +477,7 @@ public class BlockGroupingCollector extends SimpleCollector {
}
subDocUpto = 0;
docBase = readerContext.docBase;
//System.out.println("setNextReader base=" + docBase + " r=" + readerContext.reader);
// System.out.println("setNextReader base=" + docBase + " r=" + readerContext.reader);
Scorer s = lastDocPerGroup.scorer(readerContext);
if (s == null) {
lastDocPerGroupBits = null;
@ -489,7 +487,7 @@ public class BlockGroupingCollector extends SimpleCollector {
groupEndDocID = -1;
currentReaderContext = readerContext;
for (int i=0; i<comparators.length; i++) {
for (int i = 0; i < comparators.length; i++) {
leafComparators[i] = comparators[i].getLeafComparator(readerContext);
}
}
@ -513,6 +511,5 @@ public class BlockGroupingCollector extends SimpleCollector {
public float score() {
return score;
}
}
}

View File

@ -18,10 +18,12 @@ package org.apache.lucene.search.grouping;
import org.apache.lucene.search.FieldComparator; // javadocs
/**
* Expert: representation of a group in {@link FirstPassGroupingCollector},
* tracking the top doc and {@link FieldComparator} slot.
* @lucene.internal */
/**
* Expert: representation of a group in {@link FirstPassGroupingCollector}, tracking the top doc and
* {@link FieldComparator} slot.
*
* @lucene.internal
*/
public class CollectedSearchGroup<T> extends SearchGroup<T> {
int topDoc;
int comparatorSlot;

View File

@ -22,13 +22,13 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.ScoreMode;
import org.apache.lucene.search.SimpleCollector;
/**
* A second pass grouping collector that keeps track of distinct values for a specified field for the top N group.
* A second pass grouping collector that keeps track of distinct values for a specified field for
* the top N group.
*
* @lucene.experimental
*/
@ -36,12 +36,15 @@ public class DistinctValuesCollector<T, R> extends SecondPassGroupingCollector<T
/**
* Create a DistinctValuesCollector
*
* @param groupSelector the group selector to determine the top-level groups
* @param groups the top-level groups to collect for
* @param groups the top-level groups to collect for
* @param valueSelector a group selector to determine which values to collect per-group
*/
public DistinctValuesCollector(GroupSelector<T> groupSelector, Collection<SearchGroup<T>> groups,
GroupSelector<R> valueSelector) {
public DistinctValuesCollector(
GroupSelector<T> groupSelector,
Collection<SearchGroup<T>> groups,
GroupSelector<R> valueSelector) {
super(groupSelector, groups, new DistinctValuesReducer<>(valueSelector));
}
@ -58,12 +61,9 @@ public class DistinctValuesCollector<T, R> extends SecondPassGroupingCollector<T
public void collect(int doc) throws IOException {
if (valueSelector.advanceTo(doc) == GroupSelector.State.ACCEPT) {
R value = valueSelector.currentValue();
if (values.contains(value) == false)
values.add(valueSelector.copyValue());
}
else {
if (values.contains(null) == false)
values.add(null);
if (values.contains(value) == false) values.add(valueSelector.copyValue());
} else {
if (values.contains(null) == false) values.add(null);
}
}
@ -113,8 +113,8 @@ public class DistinctValuesCollector<T, R> extends SecondPassGroupingCollector<T
}
/**
* Returned by {@link DistinctValuesCollector#getGroups()},
* representing the value and set of distinct values for the group.
* Returned by {@link DistinctValuesCollector#getGroups()}, representing the value and set of
* distinct values for the group.
*/
public static class GroupCount<T, R> {
@ -126,5 +126,4 @@ public class DistinctValuesCollector<T, R> extends SecondPassGroupingCollector<T
this.uniqueValues = values;
}
}
}

View File

@ -20,8 +20,7 @@ package org.apache.lucene.search.grouping;
import java.util.Objects;
/**
* Represents a contiguous range of double values, with an inclusive minimum and
* exclusive maximum
* Represents a contiguous range of double values, with an inclusive minimum and exclusive maximum
*/
public class DoubleRange {
@ -30,9 +29,7 @@ public class DoubleRange {
/** The exclusive maximum value of this range */
public double max;
/**
* Creates a new double range, running from {@code min} inclusive to {@code max} exclusive
*/
/** Creates a new double range, running from {@code min} inclusive to {@code max} exclusive */
public DoubleRange(double min, double max) {
this.min = min;
this.max = max;
@ -48,8 +45,7 @@ public class DoubleRange {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DoubleRange that = (DoubleRange) o;
return Double.compare(that.min, min) == 0 &&
Double.compare(that.max, max) == 0;
return Double.compare(that.min, min) == 0 && Double.compare(that.max, max) == 0;
}
@Override

View File

@ -17,9 +17,7 @@
package org.apache.lucene.search.grouping;
/**
* Groups double values into ranges
*/
/** Groups double values into ranges */
public class DoubleRangeFactory {
private final double min;
@ -28,11 +26,12 @@ public class DoubleRangeFactory {
/**
* Creates a new DoubleRangeFactory
* @param min a minimum value; all doubles below this value are grouped into a single range
* @param width a standard width; all ranges between {@code min} and {@code max} are this wide,
* with the exception of the final range which may be up to this width. Ranges
* are inclusive at the lower end, and exclusive at the upper end.
* @param max a maximum value; all doubles above this value are grouped into a single range
*
* @param min a minimum value; all doubles below this value are grouped into a single range
* @param width a standard width; all ranges between {@code min} and {@code max} are this wide,
* with the exception of the final range which may be up to this width. Ranges are inclusive
* at the lower end, and exclusive at the upper end.
* @param max a maximum value; all doubles above this value are grouped into a single range
*/
public DoubleRangeFactory(double min, double width, double max) {
this.min = min;
@ -42,12 +41,14 @@ public class DoubleRangeFactory {
/**
* Finds the DoubleRange that a value should be grouped into
*
* @param value the value to group
* @param reuse an existing DoubleRange object to reuse
*/
public DoubleRange getRange(double value, DoubleRange reuse) {
if (reuse == null)
if (reuse == null) {
reuse = new DoubleRange(Double.MIN_VALUE, Double.MAX_VALUE);
}
if (value < min) {
reuse.max = min;
reuse.min = Double.MIN_VALUE;
@ -63,5 +64,4 @@ public class DoubleRangeFactory {
reuse.max = reuse.min + width;
return reuse;
}
}

View File

@ -21,15 +21,12 @@ import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.Scorable;
/**
* A GroupSelector implementation that groups documents by double values
*/
/** A GroupSelector implementation that groups documents by double values */
public class DoubleRangeGroupSelector extends GroupSelector<DoubleRange> {
private final DoubleValuesSource source;
@ -45,8 +42,10 @@ public class DoubleRangeGroupSelector extends GroupSelector<DoubleRange> {
/**
* Creates a new DoubleRangeGroupSelector
* @param source a DoubleValuesSource to retrieve double values per document
* @param rangeFactory a DoubleRangeFactory that defines how to group the double values into range buckets
*
* @param source a DoubleValuesSource to retrieve double values per document
* @param rangeFactory a DoubleRangeFactory that defines how to group the double values into range
* buckets
*/
public DoubleRangeGroupSelector(DoubleValuesSource source, DoubleRangeFactory rangeFactory) {
this.source = source;
@ -91,10 +90,11 @@ public class DoubleRangeGroupSelector extends GroupSelector<DoubleRange> {
inSecondPass = new HashSet<>();
includeEmpty = false;
for (SearchGroup<DoubleRange> group : searchGroups) {
if (group.groupValue == null)
if (group.groupValue == null) {
includeEmpty = true;
else
} else {
inSecondPass.add(group.groupValue);
}
}
}
}

View File

@ -22,7 +22,6 @@ import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.TreeSet;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.FieldComparator;
import org.apache.lucene.search.LeafFieldComparator;
@ -32,12 +31,11 @@ import org.apache.lucene.search.SimpleCollector;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
/** FirstPassGroupingCollector is the first of two passes necessary
* to collect grouped hits. This pass gathers the top N sorted
* groups. Groups are defined by a {@link GroupSelector}
/**
* FirstPassGroupingCollector is the first of two passes necessary to collect grouped hits. This
* pass gathers the top N sorted groups. Groups are defined by a {@link GroupSelector}
*
* <p>See {@link org.apache.lucene.search.grouping} for more
* details including a full code example.</p>
* <p>See {@link org.apache.lucene.search.grouping} for more details including a full code example.
*
* @lucene.experimental
*/
@ -56,6 +54,7 @@ public class FirstPassGroupingCollector<T> extends SimpleCollector {
// Set once we reach topNGroups unique groups:
/** @lucene.internal */
protected TreeSet<CollectedSearchGroup<T>> orderedGroups;
private int docBase;
private int spareSlot;
@ -63,16 +62,14 @@ public class FirstPassGroupingCollector<T> extends SimpleCollector {
* Create the first pass collector.
*
* @param groupSelector a GroupSelector used to defined groups
* @param groupSort The {@link Sort} used to sort the
* groups. The top sorted document within each group
* according to groupSort, determines how that group
* sorts against other groups. This must be non-null,
* ie, if you want to groupSort by relevance use
* Sort.RELEVANCE.
* @param groupSort The {@link Sort} used to sort the groups. The top sorted document within each
* group according to groupSort, determines how that group sorts against other groups. This
* must be non-null, ie, if you want to groupSort by relevance use Sort.RELEVANCE.
* @param topNGroups How many top groups to keep.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public FirstPassGroupingCollector(GroupSelector<T> groupSelector, Sort groupSort, int topNGroups) {
public FirstPassGroupingCollector(
GroupSelector<T> groupSelector, Sort groupSort, int topNGroups) {
this.groupSelector = groupSelector;
if (topNGroups < 1) {
throw new IllegalArgumentException("topNGroups must be >= 1 (got " + topNGroups + ")");
@ -91,7 +88,8 @@ public class FirstPassGroupingCollector<T> extends SimpleCollector {
for (int i = 0; i < sortFields.length; i++) {
final SortField sortField = sortFields[i];
// use topNGroups + 1 so we have a spare slot to use for comparing (tracked by this.spareSlot):
// use topNGroups + 1 so we have a spare slot to use for comparing (tracked by
// this.spareSlot):
comparators[i] = sortField.getComparator(topNGroups + 1, i);
reversed[i] = sortField.getReverse() ? -1 : 1;
}
@ -106,16 +104,16 @@ public class FirstPassGroupingCollector<T> extends SimpleCollector {
}
/**
* Returns top groups, starting from offset. This may
* return null, if no groups were collected, or if the
* number of unique groups collected is &lt;= offset.
* Returns top groups, starting from offset. This may return null, if no groups were collected, or
* if the number of unique groups collected is &lt;= offset.
*
* @param groupOffset The offset in the collected groups
* @return top groups, starting from offset
*/
public Collection<SearchGroup<T>> getTopGroups(int groupOffset) throws IOException {
//System.out.println("FP.getTopGroups groupOffset=" + groupOffset + " fillFields=" + fillFields + " groupMap.size()=" + groupMap.size());
// System.out.println("FP.getTopGroups groupOffset=" + groupOffset + " fillFields=" + fillFields
// + " groupMap.size()=" + groupMap.size());
if (groupOffset < 0) {
throw new IllegalArgumentException("groupOffset must be >= 0 (got " + groupOffset + ")");
@ -132,20 +130,22 @@ public class FirstPassGroupingCollector<T> extends SimpleCollector {
final Collection<SearchGroup<T>> result = new ArrayList<>();
int upto = 0;
final int sortFieldCount = comparators.length;
for(CollectedSearchGroup<T> group : orderedGroups) {
for (CollectedSearchGroup<T> group : orderedGroups) {
if (upto++ < groupOffset) {
continue;
}
// System.out.println(" group=" + (group.groupValue == null ? "null" : group.groupValue.toString()));
// System.out.println(" group=" + (group.groupValue == null ? "null" :
// group.groupValue.toString()));
SearchGroup<T> searchGroup = new SearchGroup<>();
searchGroup.groupValue = group.groupValue;
searchGroup.sortValues = new Object[sortFieldCount];
for(int sortFieldIDX=0;sortFieldIDX<sortFieldCount;sortFieldIDX++) {
searchGroup.sortValues[sortFieldIDX] = comparators[sortFieldIDX].value(group.comparatorSlot);
for (int sortFieldIDX = 0; sortFieldIDX < sortFieldCount; sortFieldIDX++) {
searchGroup.sortValues[sortFieldIDX] =
comparators[sortFieldIDX].value(group.comparatorSlot);
}
result.add(searchGroup);
}
//System.out.println(" return " + result.size() + " groups");
// System.out.println(" return " + result.size() + " groups");
return result;
}
@ -168,7 +168,7 @@ public class FirstPassGroupingCollector<T> extends SimpleCollector {
// Downside: if the number of unique groups is very low, this is
// wasted effort as we will most likely be updating an existing group.
if (orderedGroups != null) {
for (int compIDX = 0;; compIDX++) {
for (int compIDX = 0; ; compIDX++) {
final int c = reversed[compIDX] * leafComparators[compIDX].compareBottom(doc);
if (c < 0) {
// Definitely not competitive. So don't even bother to continue
@ -190,8 +190,9 @@ public class FirstPassGroupingCollector<T> extends SimpleCollector {
@Override
public void collect(int doc) throws IOException {
if (isCompetitive(doc) == false)
if (isCompetitive(doc) == false) {
return;
}
// TODO: should we add option to mean "ignore docs that
// don't have the group field" (instead of stuffing them
@ -236,7 +237,7 @@ public class FirstPassGroupingCollector<T> extends SimpleCollector {
// We already tested that the document is competitive, so replace
// the bottom group with this new group.
final CollectedSearchGroup<T> bottomGroup = orderedGroups.pollLast();
assert orderedGroups.size() == topNGroups -1;
assert orderedGroups.size() == topNGroups - 1;
groupMap.remove(bottomGroup.groupValue);
@ -261,16 +262,17 @@ public class FirstPassGroupingCollector<T> extends SimpleCollector {
}
// Update existing group:
for (int compIDX = 0;; compIDX++) {
for (int compIDX = 0; ; compIDX++) {
leafComparators[compIDX].copy(spareSlot, doc);
final int c = reversed[compIDX] * comparators[compIDX].compare(group.comparatorSlot, spareSlot);
final int c =
reversed[compIDX] * comparators[compIDX].compare(group.comparatorSlot, spareSlot);
if (c < 0) {
// Definitely not competitive.
return;
} else if (c > 0) {
// Definitely competitive; set remaining comparators:
for (int compIDX2=compIDX+1; compIDX2<comparators.length; compIDX2++) {
for (int compIDX2 = compIDX + 1; compIDX2 < comparators.length; compIDX2++) {
leafComparators[compIDX2].copy(spareSlot, doc);
}
break;
@ -289,7 +291,7 @@ public class FirstPassGroupingCollector<T> extends SimpleCollector {
if (orderedGroups != null) {
prevLast = orderedGroups.last();
orderedGroups.remove(group);
assert orderedGroups.size() == topNGroups-1;
assert orderedGroups.size() == topNGroups - 1;
} else {
prevLast = null;
}
@ -306,7 +308,8 @@ public class FirstPassGroupingCollector<T> extends SimpleCollector {
orderedGroups.add(group);
assert orderedGroups.size() == topNGroups;
final CollectedSearchGroup<?> newLast = orderedGroups.last();
// If we changed the value of the last group, or changed which group was last, then update bottom:
// If we changed the value of the last group, or changed which group was last, then update
// bottom:
if (group == newLast || prevLast != newLast) {
for (LeafFieldComparator fc : leafComparators) {
fc.setBottom(newLast.comparatorSlot);
@ -316,20 +319,21 @@ public class FirstPassGroupingCollector<T> extends SimpleCollector {
}
private void buildSortedSet() throws IOException {
final Comparator<CollectedSearchGroup<?>> comparator = new Comparator<CollectedSearchGroup<?>>() {
@Override
public int compare(CollectedSearchGroup<?> o1, CollectedSearchGroup<?> o2) {
for (int compIDX = 0;; compIDX++) {
FieldComparator<?> fc = comparators[compIDX];
final int c = reversed[compIDX] * fc.compare(o1.comparatorSlot, o2.comparatorSlot);
if (c != 0) {
return c;
} else if (compIDX == compIDXEnd) {
return o1.topDoc - o2.topDoc;
final Comparator<CollectedSearchGroup<?>> comparator =
new Comparator<CollectedSearchGroup<?>>() {
@Override
public int compare(CollectedSearchGroup<?> o1, CollectedSearchGroup<?> o2) {
for (int compIDX = 0; ; compIDX++) {
FieldComparator<?> fc = comparators[compIDX];
final int c = reversed[compIDX] * fc.compare(o1.comparatorSlot, o2.comparatorSlot);
if (c != 0) {
return c;
} else if (compIDX == compIDXEnd) {
return o1.topDoc - o2.topDoc;
}
}
}
}
}
};
};
orderedGroups = new TreeSet<>(comparator);
orderedGroups.addAll(groupMap.values());
@ -343,18 +347,14 @@ public class FirstPassGroupingCollector<T> extends SimpleCollector {
@Override
protected void doSetNextReader(LeafReaderContext readerContext) throws IOException {
docBase = readerContext.docBase;
for (int i=0; i<comparators.length; i++) {
for (int i = 0; i < comparators.length; i++) {
leafComparators[i] = comparators[i].getLeafComparator(readerContext);
}
groupSelector.setNextReader(readerContext);
}
/**
* @return the GroupSelector used for this Collector
*/
/** @return the GroupSelector used for this Collector */
public GroupSelector<T> getGroupSelector() {
return groupSelector;
}
}

View File

@ -19,39 +19,43 @@ package org.apache.lucene.search.grouping;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TotalHits;
/** Represents one group in the results.
*
* @lucene.experimental */
/**
* Represents one group in the results.
*
* @lucene.experimental
*/
public class GroupDocs<T> {
/** The groupField value for all docs in this group; this
* may be null if hits did not have the groupField. */
/**
* The groupField value for all docs in this group; this may be null if hits did not have the
* groupField.
*/
public final T groupValue;
/** Max score in this group */
public final float maxScore;
/** Overall aggregated score of this group (currently only
* set by join queries). */
/** Overall aggregated score of this group (currently only set by join queries). */
public final float score;
/** Hits; this may be {@link
* org.apache.lucene.search.FieldDoc} instances if the
* withinGroupSort sorted by fields. */
/**
* Hits; this may be {@link org.apache.lucene.search.FieldDoc} instances if the withinGroupSort
* sorted by fields.
*/
public final ScoreDoc[] scoreDocs;
/** Total hits within this group */
public final TotalHits totalHits;
/** Matches the groupSort passed to {@link
* FirstPassGroupingCollector}. */
/** Matches the groupSort passed to {@link FirstPassGroupingCollector}. */
public final Object[] groupSortValues;
public GroupDocs(float score,
float maxScore,
TotalHits totalHits,
ScoreDoc[] scoreDocs,
T groupValue,
Object[] groupSortValues) {
public GroupDocs(
float score,
float maxScore,
TotalHits totalHits,
ScoreDoc[] scoreDocs,
T groupValue,
Object[] groupSortValues) {
this.score = score;
this.maxScore = maxScore;
this.totalHits = totalHits;

View File

@ -23,7 +23,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.NavigableSet;
import java.util.TreeSet;
import org.apache.lucene.search.Scorable;
import org.apache.lucene.search.ScoreMode;
import org.apache.lucene.search.SimpleCollector;
@ -55,17 +54,19 @@ public abstract class GroupFacetCollector extends SimpleCollector {
}
/**
* Returns grouped facet results that were computed over zero or more segments.
* Grouped facet counts are merged from zero or more segment results.
* Returns grouped facet results that were computed over zero or more segments. Grouped facet
* counts are merged from zero or more segment results.
*
* @param size The total number of facets to include. This is typically offset + limit
* @param minCount The minimum count a facet entry should have to be included in the grouped facet result
* @param orderByCount Whether to sort the facet entries by facet entry count. If <code>false</code> then the facets
* are sorted lexicographically in ascending order.
* @param minCount The minimum count a facet entry should have to be included in the grouped facet
* result
* @param orderByCount Whether to sort the facet entries by facet entry count. If <code>false
* </code> then the facets are sorted lexicographically in ascending order.
* @return grouped facet results
* @throws IOException If I/O related errors occur during merging segment grouped facet counts.
*/
public GroupedFacetResult mergeSegmentResults(int size, int minCount, boolean orderByCount) throws IOException {
public GroupedFacetResult mergeSegmentResults(int size, int minCount, boolean orderByCount)
throws IOException {
if (segmentFacetCounts != null) {
segmentResults.add(createSegmentResult());
segmentFacetCounts = null; // reset
@ -83,7 +84,8 @@ public abstract class GroupFacetCollector extends SimpleCollector {
segments.add(segmentResult);
}
GroupedFacetResult facetResult = new GroupedFacetResult(size, minCount, orderByCount, totalCount, missingCount);
GroupedFacetResult facetResult =
new GroupedFacetResult(size, minCount, orderByCount, totalCount, missingCount);
while (segments.size() > 0) {
SegmentResult segmentResult = segments.top();
BytesRef currentFacetValue = BytesRef.deepCopyOf(segmentResult.mergeTerm);
@ -110,8 +112,7 @@ public abstract class GroupFacetCollector extends SimpleCollector {
protected abstract SegmentResult createSegmentResult() throws IOException;
@Override
public void setScorer(Scorable scorer) throws IOException {
}
public void setScorer(Scorable scorer) throws IOException {}
@Override
public ScoreMode scoreMode() {
@ -119,31 +120,32 @@ public abstract class GroupFacetCollector extends SimpleCollector {
}
/**
* The grouped facet result. Containing grouped facet entries, total count and total missing count.
* The grouped facet result. Containing grouped facet entries, total count and total missing
* count.
*/
public static class GroupedFacetResult {
private final static Comparator<FacetEntry> orderByCountAndValue = new Comparator<FacetEntry>() {
private static final Comparator<FacetEntry> orderByCountAndValue =
new Comparator<FacetEntry>() {
@Override
public int compare(FacetEntry a, FacetEntry b) {
int cmp = b.count - a.count; // Highest count first!
if (cmp != 0) {
return cmp;
}
return a.value.compareTo(b.value);
}
@Override
public int compare(FacetEntry a, FacetEntry b) {
int cmp = b.count - a.count; // Highest count first!
if (cmp != 0) {
return cmp;
}
return a.value.compareTo(b.value);
}
};
};
private static final Comparator<FacetEntry> orderByValue =
new Comparator<FacetEntry>() {
private final static Comparator<FacetEntry> orderByValue = new Comparator<FacetEntry>() {
@Override
public int compare(FacetEntry a, FacetEntry b) {
return a.value.compareTo(b.value);
}
};
@Override
public int compare(FacetEntry a, FacetEntry b) {
return a.value.compareTo(b.value);
}
};
private final int maxSize;
private final NavigableSet<FacetEntry> facetEntries;
@ -152,7 +154,8 @@ public abstract class GroupFacetCollector extends SimpleCollector {
private int currentMin;
public GroupedFacetResult(int size, int minCount, boolean orderByCount, int totalCount, int totalMissingCount) {
public GroupedFacetResult(
int size, int minCount, boolean orderByCount, int totalCount, int totalMissingCount) {
this.facetEntries = new TreeSet<>(orderByCount ? orderByCountAndValue : orderByValue);
this.totalMissingCount = totalMissingCount;
this.totalCount = totalCount;
@ -180,8 +183,8 @@ public abstract class GroupFacetCollector extends SimpleCollector {
}
/**
* Returns a list of facet entries to be rendered based on the specified offset and limit.
* The facet entries are retrieved from the facet entries collected during merging.
* Returns a list of facet entries to be rendered based on the specified offset and limit. The
* facet entries are retrieved from the facet entries collected during merging.
*
* @param offset The offset in the collected facet entries during merging
* @param limit The number of facets to return starting from the offset.
@ -224,9 +227,7 @@ public abstract class GroupFacetCollector extends SimpleCollector {
}
}
/**
* Represents a facet entry with a value and a count.
*/
/** Represents a facet entry with a value and a count. */
public static class FacetEntry {
private final BytesRef value;
@ -259,30 +260,23 @@ public abstract class GroupFacetCollector extends SimpleCollector {
@Override
public String toString() {
return "FacetEntry{" +
"value=" + value.utf8ToString() +
", count=" + count +
'}';
return "FacetEntry{" + "value=" + value.utf8ToString() + ", count=" + count + '}';
}
/**
* @return The value of this facet entry
*/
/** @return The value of this facet entry */
public BytesRef getValue() {
return value;
}
/**
* @return The count (number of groups) of this facet entry.
*/
/** @return The count (number of groups) of this facet entry. */
public int getCount() {
return count;
}
}
/**
* Contains the local grouped segment counts for a particular segment.
* Each <code>SegmentResult</code> must be added together.
* Contains the local grouped segment counts for a particular segment. Each <code>SegmentResult
* </code> must be added together.
*/
protected abstract static class SegmentResult {
@ -302,12 +296,12 @@ public abstract class GroupFacetCollector extends SimpleCollector {
}
/**
* Go to next term in this <code>SegmentResult</code> in order to retrieve the grouped facet counts.
* Go to next term in this <code>SegmentResult</code> in order to retrieve the grouped facet
* counts.
*
* @throws IOException If I/O related errors occur
*/
protected abstract void nextTerm() throws IOException;
}
private static class SegmentResultPriorityQueue extends PriorityQueue<SegmentResult> {
@ -321,5 +315,4 @@ public abstract class GroupFacetCollector extends SimpleCollector {
return a.mergeTerm.compareTo(b.mergeTerm) < 0;
}
}
}

View File

@ -21,22 +21,19 @@ import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.Collector;
import org.apache.lucene.search.LeafCollector;
import org.apache.lucene.search.Scorable;
/**
* Concrete implementations of this class define what to collect for individual
* groups during the second-pass of a grouping search.
* Concrete implementations of this class define what to collect for individual groups during the
* second-pass of a grouping search.
*
* Each group is assigned a Collector returned by {@link #newCollector()}, and
* {@link LeafCollector#collect(int)} is called for each document that is in
* a group
* <p>Each group is assigned a Collector returned by {@link #newCollector()}, and {@link
* LeafCollector#collect(int)} is called for each document that is in a group
*
* @see SecondPassGroupingCollector
*
* @param <T> the type of the value used for grouping
* @param <C> the type of {@link Collector} used to reduce each group
*/
@ -47,7 +44,7 @@ public abstract class GroupReducer<T, C extends Collector> {
/**
* Define which groups should be reduced.
*
* Called by {@link SecondPassGroupingCollector}
* <p>Called by {@link SecondPassGroupingCollector}
*/
public void setGroups(Collection<SearchGroup<T>> groups) {
for (SearchGroup<T> group : groups) {
@ -55,25 +52,20 @@ public abstract class GroupReducer<T, C extends Collector> {
}
}
/**
* Whether or not this reducer requires collected documents to be scored
*/
/** Whether or not this reducer requires collected documents to be scored */
public abstract boolean needsScores();
/**
* Creates a new Collector for each group
*/
/** Creates a new Collector for each group */
protected abstract C newCollector();
/**
* Get the Collector for a given group
*/
/** Get the Collector for a given group */
public final C getCollector(T value) {
return groups.get(value).collector;
}
/**
* Collect a given document into a given group
*
* @throws IOException on error
*/
public final void collect(T value, int doc) throws IOException {
@ -81,18 +73,14 @@ public abstract class GroupReducer<T, C extends Collector> {
collector.leafCollector.collect(doc);
}
/**
* Set the Scorer on all group collectors
*/
/** Set the Scorer on all group collectors */
public final void setScorer(Scorable scorer) throws IOException {
for (GroupCollector<C> collector : groups.values()) {
collector.leafCollector.setScorer(scorer);
}
}
/**
* Called when the parent {@link SecondPassGroupingCollector} moves to a new segment
*/
/** Called when the parent {@link SecondPassGroupingCollector} moves to a new segment */
public final void setNextReader(LeafReaderContext ctx) throws IOException {
for (GroupCollector<C> collector : groups.values()) {
collector.leafCollector = collector.collector.getLeafCollector(ctx);
@ -108,5 +96,4 @@ public abstract class GroupReducer<T, C extends Collector> {
this.collector = collector;
}
}
}

View File

@ -19,61 +19,52 @@ package org.apache.lucene.search.grouping;
import java.io.IOException;
import java.util.Collection;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.Scorable;
/**
* Defines a group, for use by grouping collectors
*
* A GroupSelector acts as an iterator over documents. For each segment, clients
* should call {@link #setNextReader(LeafReaderContext)}, and then {@link #advanceTo(int)}
* for each matching document.
* <p>A GroupSelector acts as an iterator over documents. For each segment, clients should call
* {@link #setNextReader(LeafReaderContext)}, and then {@link #advanceTo(int)} for each matching
* document.
*
* @param <T> the type of the group value
*/
public abstract class GroupSelector<T> {
/**
* What to do with the current value
*/
public enum State { SKIP, ACCEPT }
/** What to do with the current value */
public enum State {
SKIP,
ACCEPT
}
/**
* Set the LeafReaderContext
*/
/** Set the LeafReaderContext */
public abstract void setNextReader(LeafReaderContext readerContext) throws IOException;
/**
* Set the current Scorer
*/
/** Set the current Scorer */
public abstract void setScorer(Scorable scorer) throws IOException;
/**
* Advance the GroupSelector's iterator to the given document
*/
/** Advance the GroupSelector's iterator to the given document */
public abstract State advanceTo(int doc) throws IOException;
/**
* Get the group value of the current document
*
* N.B. this object may be reused, for a persistent version use {@link #copyValue()}
* <p>N.B. this object may be reused, for a persistent version use {@link #copyValue()}
*/
public abstract T currentValue() throws IOException;
/**
* @return a copy of the group value of the current document
*/
/** @return a copy of the group value of the current document */
public abstract T copyValue() throws IOException;
/**
* Set a restriction on the group values returned by this selector
*
* If the selector is positioned on a document whose group value is not contained
* within this set, then {@link #advanceTo(int)} will return {@link State#SKIP}
* <p>If the selector is positioned on a document whose group value is not contained within this
* set, then {@link #advanceTo(int)} will return {@link State#SKIP}
*
* @param groups a set of {@link SearchGroup} objects to limit selections to
*/
public abstract void setGroups(Collection<SearchGroup<T>> groups);
}

View File

@ -20,7 +20,6 @@ import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.CachingCollector;
import org.apache.lucene.search.Collector;
@ -62,8 +61,9 @@ public class GroupingSearch {
private Bits matchingGroupHeads;
/**
* Constructs a <code>GroupingSearch</code> instance that groups documents by index terms using DocValues.
* The group field can only have one token per document. This means that the field must not be analysed.
* Constructs a <code>GroupingSearch</code> instance that groups documents by index terms using
* DocValues. The group field can only have one token per document. This means that the field must
* not be analysed.
*
* @param groupField The name of the field to group by.
*/
@ -72,7 +72,9 @@ public class GroupingSearch {
}
/**
* Constructs a <code>GroupingSearch</code> instance that groups documents using a {@link GroupSelector}
* Constructs a <code>GroupingSearch</code> instance that groups documents using a {@link
* GroupSelector}
*
* @param groupSelector a {@link GroupSelector} that defines groups for this GroupingSearch
*/
public GroupingSearch(GroupSelector<?> groupSelector) {
@ -80,10 +82,10 @@ public class GroupingSearch {
}
/**
* Constructs a <code>GroupingSearch</code> instance that groups documents by function using a {@link ValueSource}
* instance.
* Constructs a <code>GroupingSearch</code> instance that groups documents by function using a
* {@link ValueSource} instance.
*
* @param groupFunction The function to group by specified as {@link ValueSource}
* @param groupFunction The function to group by specified as {@link ValueSource}
* @param valueSourceContext The context of the specified groupFunction
*/
public GroupingSearch(ValueSource groupFunction, Map<Object, Object> valueSourceContext) {
@ -91,8 +93,8 @@ public class GroupingSearch {
}
/**
* Constructor for grouping documents by doc block.
* This constructor can only be used when documents belonging in a group are indexed in one block.
* Constructor for grouping documents by doc block. This constructor can only be used when
* documents belonging in a group are indexed in one block.
*
* @param groupEndDocs The query that marks the last document in all doc blocks
*/
@ -106,36 +108,44 @@ public class GroupingSearch {
}
/**
* Executes a grouped search. Both the first pass and second pass are executed on the specified searcher.
* Executes a grouped search. Both the first pass and second pass are executed on the specified
* searcher.
*
* @param searcher The {@link org.apache.lucene.search.IndexSearcher} instance to execute the grouped search on.
* @param query The query to execute with the grouping
* @param searcher The {@link org.apache.lucene.search.IndexSearcher} instance to execute the
* grouped search on.
* @param query The query to execute with the grouping
* @param groupOffset The group offset
* @param groupLimit The number of groups to return from the specified group offset
* @param groupLimit The number of groups to return from the specified group offset
* @return the grouped result as a {@link TopGroups} instance
* @throws IOException If any I/O related errors occur
*/
@SuppressWarnings("unchecked")
public <T> TopGroups<T> search(IndexSearcher searcher, Query query, int groupOffset, int groupLimit) throws IOException {
public <T> TopGroups<T> search(
IndexSearcher searcher, Query query, int groupOffset, int groupLimit) throws IOException {
if (grouper != null) {
return groupByFieldOrFunction(searcher, query, groupOffset, groupLimit);
} else if (groupEndDocs != null) {
return (TopGroups<T>) groupByDocBlock(searcher, query, groupOffset, groupLimit);
} else {
throw new IllegalStateException("Either groupField, groupFunction or groupEndDocs must be set."); // This can't happen...
throw new IllegalStateException(
"Either groupField, groupFunction or groupEndDocs must be set."); // This can't happen...
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
protected TopGroups groupByFieldOrFunction(IndexSearcher searcher, Query query, int groupOffset, int groupLimit) throws IOException {
protected TopGroups groupByFieldOrFunction(
IndexSearcher searcher, Query query, int groupOffset, int groupLimit) throws IOException {
int topN = groupOffset + groupLimit;
final FirstPassGroupingCollector firstPassCollector = new FirstPassGroupingCollector(grouper, groupSort, topN);
final AllGroupsCollector allGroupsCollector = allGroups ? new AllGroupsCollector(grouper) : null;
final AllGroupHeadsCollector allGroupHeadsCollector
= allGroupHeads ? AllGroupHeadsCollector.newCollector(grouper, sortWithinGroup) : null;
final FirstPassGroupingCollector firstPassCollector =
new FirstPassGroupingCollector(grouper, groupSort, topN);
final AllGroupsCollector allGroupsCollector =
allGroups ? new AllGroupsCollector(grouper) : null;
final AllGroupHeadsCollector allGroupHeadsCollector =
allGroupHeads ? AllGroupHeadsCollector.newCollector(grouper, sortWithinGroup) : null;
final Collector firstRound = MultiCollector.wrap(firstPassCollector, allGroupsCollector, allGroupHeadsCollector);
final Collector firstRound =
MultiCollector.wrap(firstPassCollector, allGroupsCollector, allGroupHeadsCollector);
CachingCollector cachedCollector = null;
if (maxCacheRAMMB != null || maxDocsToCache != null) {
@ -150,8 +160,10 @@ public class GroupingSearch {
}
matchingGroups = allGroups ? allGroupsCollector.getGroups() : Collections.emptyList();
matchingGroupHeads = allGroupHeads ? allGroupHeadsCollector.retrieveGroupHeads(searcher.getIndexReader().maxDoc())
: new Bits.MatchNoBits(searcher.getIndexReader().maxDoc());
matchingGroupHeads =
allGroupHeads
? allGroupHeadsCollector.retrieveGroupHeads(searcher.getIndexReader().maxDoc())
: new Bits.MatchNoBits(searcher.getIndexReader().maxDoc());
Collection<SearchGroup> topSearchGroups = firstPassCollector.getTopGroups(groupOffset);
if (topSearchGroups == null) {
@ -159,8 +171,9 @@ public class GroupingSearch {
}
int topNInsideGroup = groupDocsOffset + groupDocsLimit;
TopGroupsCollector secondPassCollector
= new TopGroupsCollector(grouper, topSearchGroups, groupSort, sortWithinGroup, topNInsideGroup, includeMaxScore);
TopGroupsCollector secondPassCollector =
new TopGroupsCollector(
grouper, topSearchGroups, groupSort, sortWithinGroup, topNInsideGroup, includeMaxScore);
if (cachedCollector != null && cachedCollector.isCached()) {
cachedCollector.replay(secondPassCollector);
@ -169,29 +182,38 @@ public class GroupingSearch {
}
if (allGroups) {
return new TopGroups(secondPassCollector.getTopGroups(groupDocsOffset), matchingGroups.size());
return new TopGroups(
secondPassCollector.getTopGroups(groupDocsOffset), matchingGroups.size());
} else {
return secondPassCollector.getTopGroups(groupDocsOffset);
}
}
protected TopGroups<?> groupByDocBlock(IndexSearcher searcher, Query query, int groupOffset, int groupLimit) throws IOException {
protected TopGroups<?> groupByDocBlock(
IndexSearcher searcher, Query query, int groupOffset, int groupLimit) throws IOException {
int topN = groupOffset + groupLimit;
final Query endDocsQuery = searcher.rewrite(this.groupEndDocs);
final Weight groupEndDocs = searcher.createWeight(endDocsQuery, ScoreMode.COMPLETE_NO_SCORES, 1);
BlockGroupingCollector c = new BlockGroupingCollector(groupSort, topN, groupSort.needsScores() || sortWithinGroup.needsScores(), groupEndDocs);
final Weight groupEndDocs =
searcher.createWeight(endDocsQuery, ScoreMode.COMPLETE_NO_SCORES, 1);
BlockGroupingCollector c =
new BlockGroupingCollector(
groupSort,
topN,
groupSort.needsScores() || sortWithinGroup.needsScores(),
groupEndDocs);
searcher.search(query, c);
int topNInsideGroup = groupDocsOffset + groupDocsLimit;
return c.getTopGroups(sortWithinGroup, groupOffset, groupDocsOffset, topNInsideGroup);
}
/**
* Enables caching for the second pass search. The cache will not grow over a specified limit in MB.
* The cache is filled during the first pass searched and then replayed during the second pass searched.
* If the cache grows beyond the specified limit, then the cache is purged and not used in the second pass search.
* Enables caching for the second pass search. The cache will not grow over a specified limit in
* MB. The cache is filled during the first pass searched and then replayed during the second pass
* searched. If the cache grows beyond the specified limit, then the cache is purged and not used
* in the second pass search.
*
* @param maxCacheRAMMB The maximum amount in MB the cache is allowed to hold
* @param cacheScores Whether to cache the scores
* @param cacheScores Whether to cache the scores
* @return <code>this</code>
*/
public GroupingSearch setCachingInMB(double maxCacheRAMMB, boolean cacheScores) {
@ -202,12 +224,13 @@ public class GroupingSearch {
}
/**
* Enables caching for the second pass search. The cache will not contain more than the maximum specified documents.
* The cache is filled during the first pass searched and then replayed during the second pass searched.
* If the cache grows beyond the specified limit, then the cache is purged and not used in the second pass search.
* Enables caching for the second pass search. The cache will not contain more than the maximum
* specified documents. The cache is filled during the first pass searched and then replayed
* during the second pass searched. If the cache grows beyond the specified limit, then the cache
* is purged and not used in the second pass search.
*
* @param maxDocsToCache The maximum number of documents the cache is allowed to hold
* @param cacheScores Whether to cache the scores
* @param cacheScores Whether to cache the scores
* @return <code>this</code>
*/
public GroupingSearch setCaching(int maxDocsToCache, boolean cacheScores) {
@ -229,8 +252,7 @@ public class GroupingSearch {
}
/**
* Specifies how groups are sorted.
* Defaults to {@link Sort#RELEVANCE}.
* Specifies how groups are sorted. Defaults to {@link Sort#RELEVANCE}.
*
* @param groupSort The sort for the groups.
* @return <code>this</code>
@ -241,8 +263,7 @@ public class GroupingSearch {
}
/**
* Specified how documents inside a group are sorted.
* Defaults to {@link Sort#RELEVANCE}.
* Specified how documents inside a group are sorted. Defaults to {@link Sort#RELEVANCE}.
*
* @param sortWithinGroup The sort for documents inside a group
* @return <code>this</code>
@ -286,11 +307,11 @@ public class GroupingSearch {
}
/**
* Whether to also compute all groups matching the query.
* This can be used to determine the number of groups, which can be used for accurate pagination.
* <p>
* When grouping by doc block the number of groups are automatically included in the {@link TopGroups} and this
* option doesn't have any influence.
* Whether to also compute all groups matching the query. This can be used to determine the number
* of groups, which can be used for accurate pagination.
*
* <p>When grouping by doc block the number of groups are automatically included in the {@link
* TopGroups} and this option doesn't have any influence.
*
* @param allGroups to also compute all groups matching the query
* @return <code>this</code>
@ -301,11 +322,11 @@ public class GroupingSearch {
}
/**
* If {@link #setAllGroups(boolean)} was set to <code>true</code> then all matching groups are returned, otherwise
* an empty collection is returned.
* If {@link #setAllGroups(boolean)} was set to <code>true</code> then all matching groups are
* returned, otherwise an empty collection is returned.
*
* @param <T> The group value type. This can be a {@link BytesRef} or a {@link MutableValue} instance. If grouping
* by doc block this the group value is always <code>null</code>.
* @param <T> The group value type. This can be a {@link BytesRef} or a {@link MutableValue}
* instance. If grouping by doc block this the group value is always <code>null</code>.
* @return all matching groups are returned, or an empty collection
*/
@SuppressWarnings({"unchecked", "rawtypes"})
@ -315,10 +336,11 @@ public class GroupingSearch {
/**
* Whether to compute all group heads (most relevant document per group) matching the query.
* <p>
* This feature isn't enabled when grouping by doc block.
*
* @param allGroupHeads Whether to compute all group heads (most relevant document per group) matching the query
* <p>This feature isn't enabled when grouping by doc block.
*
* @param allGroupHeads Whether to compute all group heads (most relevant document per group)
* matching the query
* @return <code>this</code>
*/
public GroupingSearch setAllGroupHeads(boolean allGroupHeads) {
@ -327,12 +349,13 @@ public class GroupingSearch {
}
/**
* Returns the matching group heads if {@link #setAllGroupHeads(boolean)} was set to true or an empty bit set.
* Returns the matching group heads if {@link #setAllGroupHeads(boolean)} was set to true or an
* empty bit set.
*
* @return The matching group heads if {@link #setAllGroupHeads(boolean)} was set to true or an empty bit set
* @return The matching group heads if {@link #setAllGroupHeads(boolean)} was set to true or an
* empty bit set
*/
public Bits getAllGroupHeads() {
return matchingGroupHeads;
}
}

View File

@ -19,10 +19,7 @@ package org.apache.lucene.search.grouping;
import java.util.Objects;
/**
* Represents a contiguous range of long values, with an inclusive minimum and
* exclusive maximum
*/
/** Represents a contiguous range of long values, with an inclusive minimum and exclusive maximum */
public class LongRange {
/** The inclusive minimum value of this range */
@ -30,9 +27,7 @@ public class LongRange {
/** The exclusive maximum value of this range */
public long max;
/**
* Creates a new double range, running from {@code min} inclusive to {@code max} exclusive
*/
/** Creates a new double range, running from {@code min} inclusive to {@code max} exclusive */
public LongRange(long min, long max) {
this.min = min;
this.max = max;

View File

@ -17,9 +17,7 @@
package org.apache.lucene.search.grouping;
/**
* Groups double values into ranges
*/
/** Groups double values into ranges */
public class LongRangeFactory {
private final long min;
@ -28,11 +26,12 @@ public class LongRangeFactory {
/**
* Creates a new LongRangeFactory
* @param min a minimum value; all longs below this value are grouped into a single range
* @param width a standard width; all ranges between {@code min} and {@code max} are this wide,
* with the exception of the final range which may be up to this width. Ranges
* are inclusive at the lower end, and exclusive at the upper end.
* @param max a maximum value; all longs above this value are grouped into a single range
*
* @param min a minimum value; all longs below this value are grouped into a single range
* @param width a standard width; all ranges between {@code min} and {@code max} are this wide,
* with the exception of the final range which may be up to this width. Ranges are inclusive
* at the lower end, and exclusive at the upper end.
* @param max a maximum value; all longs above this value are grouped into a single range
*/
public LongRangeFactory(long min, long width, long max) {
this.min = min;
@ -42,12 +41,14 @@ public class LongRangeFactory {
/**
* Finds the LongRange that a value should be grouped into
*
* @param value the value to group
* @param reuse an existing LongRange object to reuse
*/
public LongRange getRange(long value, LongRange reuse) {
if (reuse == null)
if (reuse == null) {
reuse = new LongRange(Long.MIN_VALUE, Long.MAX_VALUE);
}
if (value < min) {
reuse.max = min;
reuse.min = Long.MIN_VALUE;
@ -63,5 +64,4 @@ public class LongRangeFactory {
reuse.max = reuse.min + width;
return reuse;
}
}

View File

@ -21,16 +21,13 @@ import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.LongValues;
import org.apache.lucene.search.LongValuesSource;
import org.apache.lucene.search.Scorable;
/**
* A GroupSelector implementation that groups documents by long values
*/
/** A GroupSelector implementation that groups documents by long values */
public class LongRangeGroupSelector extends GroupSelector<LongRange> {
private final LongValuesSource source;
@ -46,8 +43,10 @@ public class LongRangeGroupSelector extends GroupSelector<LongRange> {
/**
* Creates a new LongRangeGroupSelector
* @param source a LongValuesSource to retrieve long values per document
* @param rangeFactory a LongRangeFactory that defines how to group the long values into range buckets
*
* @param source a LongValuesSource to retrieve long values per document
* @param rangeFactory a LongRangeFactory that defines how to group the long values into range
* buckets
*/
public LongRangeGroupSelector(LongValuesSource source, LongRangeFactory rangeFactory) {
this.source = source;
@ -92,10 +91,11 @@ public class LongRangeGroupSelector extends GroupSelector<LongRange> {
inSecondPass = new HashSet<>();
includeEmpty = false;
for (SearchGroup<LongRange> group : searchGroups) {
if (group.groupValue == null)
if (group.groupValue == null) {
includeEmpty = true;
else
} else {
inSecondPass.add(group.groupValue);
}
}
}
}

View File

@ -26,7 +26,6 @@ import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.TreeSet;
import org.apache.lucene.search.FieldComparator;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
@ -38,19 +37,23 @@ import org.apache.lucene.search.SortField;
*/
public class SearchGroup<T> {
/** The value that defines this group */
/** The value that defines this group */
public T groupValue;
/** The sort values used during sorting. These are the
* groupSort field values of the highest rank document
* (by the groupSort) within the group. Can be
* <code>null</code> if <code>fillFields=false</code> had
* been passed to {@link FirstPassGroupingCollector#getTopGroups} */
/**
* The sort values used during sorting. These are the groupSort field values of the highest rank
* document (by the groupSort) within the group. Can be <code>null</code> if <code>
* fillFields=false</code> had been passed to {@link FirstPassGroupingCollector#getTopGroups}
*/
public Object[] sortValues;
@Override
public String toString() {
return("SearchGroup(groupValue=" + groupValue + " sortValues=" + Arrays.toString(sortValues) + ")");
return ("SearchGroup(groupValue="
+ groupValue
+ " sortValues="
+ Arrays.toString(sortValues)
+ ")");
}
@Override
@ -90,11 +93,12 @@ public class SearchGroup<T> {
assert iter.hasNext();
final SearchGroup<T> group = iter.next();
if (group.sortValues == null) {
throw new IllegalArgumentException("group.sortValues is null; you must pass fillFields=true to the first pass collector");
throw new IllegalArgumentException(
"group.sortValues is null; you must pass fillFields=true to the first pass collector");
}
return group;
}
@Override
public String toString() {
return "ShardIter(shard=" + shardIndex + ")";
@ -162,7 +166,7 @@ public class SearchGroup<T> {
@SuppressWarnings("rawtypes")
public final FieldComparator[] comparators;
public final int[] reversed;
@SuppressWarnings({"unchecked", "rawtypes"})
@ -178,18 +182,19 @@ public class SearchGroup<T> {
}
@Override
@SuppressWarnings({"unchecked","rawtypes"})
@SuppressWarnings({"unchecked", "rawtypes"})
public int compare(MergedGroup<T> group, MergedGroup<T> other) {
if (group == other) {
return 0;
}
//System.out.println("compare group=" + group + " other=" + other);
// System.out.println("compare group=" + group + " other=" + other);
final Object[] groupValues = group.topValues;
final Object[] otherValues = other.topValues;
//System.out.println(" groupValues=" + groupValues + " otherValues=" + otherValues);
for (int compIDX = 0;compIDX < comparators.length; compIDX++) {
final int c = reversed[compIDX] * comparators[compIDX].compareValues(groupValues[compIDX],
otherValues[compIDX]);
// System.out.println(" groupValues=" + groupValues + " otherValues=" + otherValues);
for (int compIDX = 0; compIDX < comparators.length; compIDX++) {
final int c =
reversed[compIDX]
* comparators[compIDX].compareValues(groupValues[compIDX], otherValues[compIDX]);
if (c != 0) {
return c;
}
@ -205,7 +210,7 @@ public class SearchGroup<T> {
private final GroupComparator<T> groupComp;
private final NavigableSet<MergedGroup<T>> queue;
private final Map<T,MergedGroup<T>> groupsSeen;
private final Map<T, MergedGroup<T>> groupsSeen;
public GroupMerger(Sort groupSort) {
groupComp = new GroupComparator<>(groupSort);
@ -213,17 +218,18 @@ public class SearchGroup<T> {
groupsSeen = new HashMap<>();
}
@SuppressWarnings({"unchecked","rawtypes"})
@SuppressWarnings({"unchecked", "rawtypes"})
private void updateNextGroup(int topN, ShardIter<T> shard) {
while(shard.iter.hasNext()) {
while (shard.iter.hasNext()) {
final SearchGroup<T> group = shard.next();
MergedGroup<T> mergedGroup = groupsSeen.get(group.groupValue);
final boolean isNew = mergedGroup == null;
//System.out.println(" next group=" + (group.groupValue == null ? "null" : ((BytesRef) group.groupValue).utf8ToString()) + " sort=" + Arrays.toString(group.sortValues));
// System.out.println(" next group=" + (group.groupValue == null ? "null" : ((BytesRef)
// group.groupValue).utf8ToString()) + " sort=" + Arrays.toString(group.sortValues));
if (isNew) {
// Start a new group:
//System.out.println(" new");
// System.out.println(" new");
mergedGroup = new MergedGroup<>(group.groupValue);
mergedGroup.minShardIndex = shard.shardIndex;
assert group.sortValues != null;
@ -236,11 +242,13 @@ public class SearchGroup<T> {
// processed; move on to next group...
continue;
} else {
//System.out.println(" old");
// System.out.println(" old");
boolean competes = false;
for(int compIDX=0;compIDX<groupComp.comparators.length;compIDX++) {
final int cmp = groupComp.reversed[compIDX] * groupComp.comparators[compIDX].compareValues(group.sortValues[compIDX],
mergedGroup.topValues[compIDX]);
for (int compIDX = 0; compIDX < groupComp.comparators.length; compIDX++) {
final int cmp =
groupComp.reversed[compIDX]
* groupComp.comparators[compIDX].compareValues(
group.sortValues[compIDX], mergedGroup.topValues[compIDX]);
if (cmp < 0) {
// Definitely competes
competes = true;
@ -248,14 +256,14 @@ public class SearchGroup<T> {
} else if (cmp > 0) {
// Definitely does not compete
break;
} else if (compIDX == groupComp.comparators.length-1) {
} else if (compIDX == groupComp.comparators.length - 1) {
if (shard.shardIndex < mergedGroup.minShardIndex) {
competes = true;
}
}
}
//System.out.println(" competes=" + competes);
// System.out.println(" competes=" + competes);
if (competes) {
// Group's sort changed -- remove & re-insert
@ -274,23 +282,24 @@ public class SearchGroup<T> {
}
// Prune un-competitive groups:
while(queue.size() > topN) {
while (queue.size() > topN) {
final MergedGroup<T> group = queue.pollLast();
//System.out.println("PRUNE: " + group);
// System.out.println("PRUNE: " + group);
group.inQueue = false;
}
}
public Collection<SearchGroup<T>> merge(List<Collection<SearchGroup<T>>> shards, int offset, int topN) {
public Collection<SearchGroup<T>> merge(
List<Collection<SearchGroup<T>>> shards, int offset, int topN) {
final int maxQueueSize = offset + topN;
//System.out.println("merge");
// System.out.println("merge");
// Init queue:
for(int shardIDX=0;shardIDX<shards.size();shardIDX++) {
for (int shardIDX = 0; shardIDX < shards.size(); shardIDX++) {
final Collection<SearchGroup<T>> shard = shards.get(shardIDX);
if (!shard.isEmpty()) {
//System.out.println(" insert shard=" + shardIDX);
// System.out.println(" insert shard=" + shardIDX);
updateNextGroup(maxQueueSize, new ShardIter<>(shard, shardIDX));
}
}
@ -300,10 +309,12 @@ public class SearchGroup<T> {
int count = 0;
while(!queue.isEmpty()) {
while (!queue.isEmpty()) {
final MergedGroup<T> group = queue.pollFirst();
group.processed = true;
//System.out.println(" pop: shards=" + group.shards + " group=" + (group.groupValue == null ? "null" : (((BytesRef) group.groupValue).utf8ToString())) + " sortValues=" + Arrays.toString(group.topValues));
// System.out.println(" pop: shards=" + group.shards + " group=" + (group.groupValue ==
// null ? "null" : (((BytesRef) group.groupValue).utf8ToString())) + " sortValues=" +
// Arrays.toString(group.topValues));
if (count++ >= offset) {
final SearchGroup<T> newGroup = new SearchGroup<>();
newGroup.groupValue = group.groupValue;
@ -312,12 +323,12 @@ public class SearchGroup<T> {
if (newTopGroups.size() == topN) {
break;
}
//} else {
// System.out.println(" skip < offset");
// } else {
// System.out.println(" skip < offset");
}
// Advance all iters in this group:
for(ShardIter<T> shardIter : group.shards) {
for (ShardIter<T> shardIter : group.shards) {
updateNextGroup(maxQueueSize, shardIter);
}
}
@ -330,16 +341,16 @@ public class SearchGroup<T> {
}
}
/** Merges multiple collections of top groups, for example
* obtained from separate index shards. The provided
* groupSort must match how the groups were sorted, and
* the provided SearchGroups must have been computed
* with fillFields=true passed to {@link
* FirstPassGroupingCollector#getTopGroups}.
/**
* Merges multiple collections of top groups, for example obtained from separate index shards. The
* provided groupSort must match how the groups were sorted, and the provided SearchGroups must
* have been computed with fillFields=true passed to {@link
* FirstPassGroupingCollector#getTopGroups}.
*
* <p>NOTE: this returns null if the topGroups is empty.
*/
public static <T> Collection<SearchGroup<T>> merge(List<Collection<SearchGroup<T>>> topGroups, int offset, int topN, Sort groupSort) {
public static <T> Collection<SearchGroup<T>> merge(
List<Collection<SearchGroup<T>>> topGroups, int offset, int topN, Sort groupSort) {
if (topGroups.isEmpty()) {
return null;
} else {

View File

@ -19,19 +19,17 @@ package org.apache.lucene.search.grouping;
import java.io.IOException;
import java.util.Collection;
import java.util.Objects;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.Scorable;
import org.apache.lucene.search.ScoreMode;
import org.apache.lucene.search.SimpleCollector;
/**
* SecondPassGroupingCollector runs over an already collected set of
* groups, further applying a {@link GroupReducer} to each group
* SecondPassGroupingCollector runs over an already collected set of groups, further applying a
* {@link GroupReducer} to each group
*
* @see TopGroupsCollector
* @see DistinctValuesCollector
*
* @lucene.experimental
*/
public class SecondPassGroupingCollector<T> extends SimpleCollector {
@ -45,13 +43,17 @@ public class SecondPassGroupingCollector<T> extends SimpleCollector {
/**
* Create a new SecondPassGroupingCollector
* @param groupSelector the GroupSelector that defines groups for this search
* @param groups the groups to collect documents for
* @param reducer the reducer to apply to each group
*
* @param groupSelector the GroupSelector that defines groups for this search
* @param groups the groups to collect documents for
* @param reducer the reducer to apply to each group
*/
public SecondPassGroupingCollector(GroupSelector<T> groupSelector, Collection<SearchGroup<T>> groups, GroupReducer<T, ?> reducer) {
public SecondPassGroupingCollector(
GroupSelector<T> groupSelector,
Collection<SearchGroup<T>> groups,
GroupReducer<T, ?> reducer) {
//System.out.println("SP init");
// System.out.println("SP init");
if (groups.isEmpty()) {
throw new IllegalArgumentException("no groups to collect (groups is empty)");
}
@ -64,9 +66,7 @@ public class SecondPassGroupingCollector<T> extends SimpleCollector {
reducer.setGroups(groups);
}
/**
* @return the GroupSelector used in this collector
*/
/** @return the GroupSelector used in this collector */
public GroupSelector<T> getGroupSelector() {
return groupSelector;
}
@ -85,8 +85,9 @@ public class SecondPassGroupingCollector<T> extends SimpleCollector {
@Override
public void collect(int doc) throws IOException {
totalHitCount++;
if (groupSelector.advanceTo(doc) == GroupSelector.State.SKIP)
if (groupSelector.advanceTo(doc) == GroupSelector.State.SKIP) {
return;
}
totalGroupedHitCount++;
T value = groupSelector.currentValue();
groupReducer.collect(value, doc);
@ -97,5 +98,4 @@ public class SecondPassGroupingCollector<T> extends SimpleCollector {
groupReducer.setNextReader(readerContext);
groupSelector.setNextReader(readerContext);
}
}

View File

@ -19,7 +19,6 @@ package org.apache.lucene.search.grouping;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedDocValues;
@ -31,8 +30,8 @@ import org.apache.lucene.util.SentinelIntSet;
import org.apache.lucene.util.UnicodeUtil;
/**
* An implementation of {@link GroupFacetCollector} that computes grouped facets based on the indexed terms
* from DocValues.
* An implementation of {@link GroupFacetCollector} that computes grouped facets based on the
* indexed terms from DocValues.
*
* @lucene.experimental
*/
@ -44,23 +43,24 @@ public abstract class TermGroupFacetCollector extends GroupFacetCollector {
SortedDocValues groupFieldTermsIndex;
/**
* Factory method for creating the right implementation based on the fact whether the facet field contains
* multiple tokens per documents.
* Factory method for creating the right implementation based on the fact whether the facet field
* contains multiple tokens per documents.
*
* @param groupField The group field
* @param facetField The facet field
* @param facetFieldMultivalued Whether the facet field has multiple tokens per document
* @param facetPrefix The facet prefix a facet entry should start with to be included.
* @param initialSize The initial allocation size of the internal int set and group facet list which should roughly
* match the total number of expected unique groups. Be aware that the heap usage is
* 4 bytes * initialSize.
* @param initialSize The initial allocation size of the internal int set and group facet list
* which should roughly match the total number of expected unique groups. Be aware that the
* heap usage is 4 bytes * initialSize.
* @return <code>TermGroupFacetCollector</code> implementation
*/
public static TermGroupFacetCollector createTermGroupFacetCollector(String groupField,
String facetField,
boolean facetFieldMultivalued,
BytesRef facetPrefix,
int initialSize) {
public static TermGroupFacetCollector createTermGroupFacetCollector(
String groupField,
String facetField,
boolean facetFieldMultivalued,
BytesRef facetPrefix,
int initialSize) {
if (facetFieldMultivalued) {
return new MV(groupField, facetField, facetPrefix, initialSize);
} else {
@ -68,7 +68,8 @@ public abstract class TermGroupFacetCollector extends GroupFacetCollector {
}
}
TermGroupFacetCollector(String groupField, String facetField, BytesRef facetPrefix, int initialSize) {
TermGroupFacetCollector(
String groupField, String facetField, BytesRef facetPrefix, int initialSize) {
super(groupField, facetField, facetPrefix);
groupedFacetHits = new ArrayList<>(initialSize);
segmentGroupedFacetHits = new SentinelIntSet(initialSize, Integer.MIN_VALUE);
@ -95,7 +96,7 @@ public abstract class TermGroupFacetCollector extends GroupFacetCollector {
} else {
facetOrd = -1;
}
if (facetOrd < startFacetOrd || facetOrd >= endFacetOrd) {
return;
}
@ -110,13 +111,14 @@ public abstract class TermGroupFacetCollector extends GroupFacetCollector {
} else {
groupOrd = -1;
}
int segmentGroupedFacetsIndex = groupOrd * (facetFieldTermsIndex.getValueCount()+1) + facetOrd;
int segmentGroupedFacetsIndex =
groupOrd * (facetFieldTermsIndex.getValueCount() + 1) + facetOrd;
if (segmentGroupedFacetHits.exists(segmentGroupedFacetsIndex)) {
return;
}
segmentTotalCount++;
segmentFacetCounts[facetOrd+1]++;
segmentFacetCounts[facetOrd + 1]++;
segmentGroupedFacetHits.put(segmentGroupedFacetsIndex);
@ -147,22 +149,29 @@ public abstract class TermGroupFacetCollector extends GroupFacetCollector {
facetFieldTermsIndex = DocValues.getSorted(context.reader(), facetField);
// 1+ to allow for the -1 "not set":
segmentFacetCounts = new int[facetFieldTermsIndex.getValueCount()+1];
segmentFacetCounts = new int[facetFieldTermsIndex.getValueCount() + 1];
segmentTotalCount = 0;
segmentGroupedFacetHits.clear();
for (GroupedFacetHit groupedFacetHit : groupedFacetHits) {
int facetOrd = groupedFacetHit.facetValue == null ? -1 : facetFieldTermsIndex.lookupTerm(groupedFacetHit.facetValue);
int facetOrd =
groupedFacetHit.facetValue == null
? -1
: facetFieldTermsIndex.lookupTerm(groupedFacetHit.facetValue);
if (groupedFacetHit.facetValue != null && facetOrd < 0) {
continue;
}
int groupOrd = groupedFacetHit.groupValue == null ? -1 : groupFieldTermsIndex.lookupTerm(groupedFacetHit.groupValue);
int groupOrd =
groupedFacetHit.groupValue == null
? -1
: groupFieldTermsIndex.lookupTerm(groupedFacetHit.groupValue);
if (groupedFacetHit.groupValue != null && groupOrd < 0) {
continue;
}
int segmentGroupedFacetsIndex = groupOrd * (facetFieldTermsIndex.getValueCount()+1) + facetOrd;
int segmentGroupedFacetsIndex =
groupOrd * (facetFieldTermsIndex.getValueCount() + 1) + facetOrd;
segmentGroupedFacetHits.put(segmentGroupedFacetsIndex);
}
@ -186,17 +195,23 @@ public abstract class TermGroupFacetCollector extends GroupFacetCollector {
@Override
protected SegmentResult createSegmentResult() throws IOException {
return new SegmentResult(segmentFacetCounts, segmentTotalCount, facetFieldTermsIndex.termsEnum(), startFacetOrd, endFacetOrd);
return new SegmentResult(
segmentFacetCounts,
segmentTotalCount,
facetFieldTermsIndex.termsEnum(),
startFacetOrd,
endFacetOrd);
}
private static class SegmentResult extends GroupFacetCollector.SegmentResult {
final TermsEnum tenum;
SegmentResult(int[] counts, int total, TermsEnum tenum, int startFacetOrd, int endFacetOrd) throws IOException {
super(counts, total - counts[0], counts[0], endFacetOrd+1);
SegmentResult(int[] counts, int total, TermsEnum tenum, int startFacetOrd, int endFacetOrd)
throws IOException {
super(counts, total - counts[0], counts[0], endFacetOrd + 1);
this.tenum = tenum;
this.mergePos = startFacetOrd == -1 ? 1 : startFacetOrd+1;
this.mergePos = startFacetOrd == -1 ? 1 : startFacetOrd + 1;
if (mergePos < maxTermPos) {
assert tenum != null;
tenum.seekExact(startFacetOrd == -1 ? 0 : startFacetOrd);
@ -234,7 +249,7 @@ public abstract class TermGroupFacetCollector extends GroupFacetCollector {
} else {
groupOrd = -1;
}
if (facetFieldNumTerms == 0) {
int segmentGroupedFacetsIndex = groupOrd * (facetFieldNumTerms + 1);
if (facetPrefix != null || segmentGroupedFacetHits.exists(segmentGroupedFacetsIndex)) {
@ -266,12 +281,14 @@ public abstract class TermGroupFacetCollector extends GroupFacetCollector {
empty = false;
}
}
if (empty) {
process(groupOrd, facetFieldNumTerms); // this facet ord is reserved for docs not containing facet field.
process(
groupOrd,
facetFieldNumTerms); // this facet ord is reserved for docs not containing facet field.
}
}
private void process(int groupOrd, int facetOrd) throws IOException {
if (facetOrd < startFacetOrd || facetOrd >= endFacetOrd) {
return;
@ -317,20 +334,25 @@ public abstract class TermGroupFacetCollector extends GroupFacetCollector {
} else {
facetOrdTermsEnum = facetFieldDocTermOrds.termsEnum();
}
// [facetFieldNumTerms() + 1] for all possible facet values and docs not containing facet field
// [facetFieldNumTerms() + 1] for all possible facet values and docs not containing facet
// field
segmentFacetCounts = new int[facetFieldNumTerms + 1];
segmentTotalCount = 0;
segmentGroupedFacetHits.clear();
for (GroupedFacetHit groupedFacetHit : groupedFacetHits) {
int groupOrd = groupedFacetHit.groupValue == null ? -1 : groupFieldTermsIndex.lookupTerm(groupedFacetHit.groupValue);
int groupOrd =
groupedFacetHit.groupValue == null
? -1
: groupFieldTermsIndex.lookupTerm(groupedFacetHit.groupValue);
if (groupedFacetHit.groupValue != null && groupOrd < 0) {
continue;
}
int facetOrd;
if (groupedFacetHit.facetValue != null) {
if (facetOrdTermsEnum == null || !facetOrdTermsEnum.seekExact(groupedFacetHit.facetValue)) {
if (facetOrdTermsEnum == null
|| !facetOrdTermsEnum.seekExact(groupedFacetHit.facetValue)) {
continue;
}
facetOrd = (int) facetOrdTermsEnum.ord();
@ -338,7 +360,8 @@ public abstract class TermGroupFacetCollector extends GroupFacetCollector {
facetOrd = facetFieldNumTerms;
}
// (facetFieldDocTermOrds.numTerms() + 1) for all possible facet values and docs not containing facet field
// (facetFieldDocTermOrds.numTerms() + 1) for all possible facet values and docs not
// containing facet field
int segmentGroupedFacetsIndex = groupOrd * (facetFieldNumTerms + 1) + facetOrd;
segmentGroupedFacetHits.put(segmentGroupedFacetsIndex);
}
@ -376,16 +399,32 @@ public abstract class TermGroupFacetCollector extends GroupFacetCollector {
@Override
protected SegmentResult createSegmentResult() throws IOException {
return new SegmentResult(segmentFacetCounts, segmentTotalCount, facetFieldNumTerms, facetOrdTermsEnum, startFacetOrd, endFacetOrd);
return new SegmentResult(
segmentFacetCounts,
segmentTotalCount,
facetFieldNumTerms,
facetOrdTermsEnum,
startFacetOrd,
endFacetOrd);
}
private static class SegmentResult extends GroupFacetCollector.SegmentResult {
final TermsEnum tenum;
SegmentResult(int[] counts, int total, int missingCountIndex, TermsEnum tenum, int startFacetOrd, int endFacetOrd) throws IOException {
super(counts, total - counts[missingCountIndex], counts[missingCountIndex],
endFacetOrd == missingCountIndex + 1 ? missingCountIndex : endFacetOrd);
SegmentResult(
int[] counts,
int total,
int missingCountIndex,
TermsEnum tenum,
int startFacetOrd,
int endFacetOrd)
throws IOException {
super(
counts,
total - counts[missingCountIndex],
counts[missingCountIndex],
endFacetOrd == missingCountIndex + 1 ? missingCountIndex : endFacetOrd);
this.tenum = tenum;
this.mergePos = startFacetOrd;
if (tenum != null) {

View File

@ -21,7 +21,6 @@ import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedDocValues;
@ -29,9 +28,7 @@ import org.apache.lucene.search.Scorable;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefHash;
/**
* A GroupSelector implementation that groups via SortedDocValues
*/
/** A GroupSelector implementation that groups via SortedDocValues */
public class TermGroupSelector extends GroupSelector<BytesRef> {
private final String field;
@ -46,6 +43,7 @@ public class TermGroupSelector extends GroupSelector<BytesRef> {
/**
* Create a new TermGroupSelector
*
* @param field the SortedDocValues field to use for grouping
*/
public TermGroupSelector(String field) {
@ -60,13 +58,14 @@ public class TermGroupSelector extends GroupSelector<BytesRef> {
for (int i = 0; i < values.size(); i++) {
values.get(i, scratch);
int ord = this.docValues.lookupTerm(scratch);
if (ord >= 0)
if (ord >= 0) {
ordsToGroupIds.put(ord, i);
}
}
}
@Override
public void setScorer(Scorable scorer) throws IOException { }
public void setScorer(Scorable scorer) throws IOException {}
@Override
public State advanceTo(int doc) throws IOException {
@ -79,8 +78,9 @@ public class TermGroupSelector extends GroupSelector<BytesRef> {
groupId = ordsToGroupIds.get(ord);
return State.ACCEPT;
}
if (secondPass)
if (secondPass) {
return State.SKIP;
}
groupId = values.add(docValues.binaryValue());
ordsToGroupIds.put(ord, groupId);
return State.ACCEPT;
@ -90,16 +90,18 @@ public class TermGroupSelector extends GroupSelector<BytesRef> {
@Override
public BytesRef currentValue() {
if (groupId == -1)
if (groupId == -1) {
return null;
}
values.get(groupId, scratch);
return scratch;
}
@Override
public BytesRef copyValue() {
if (groupId == -1)
if (groupId == -1) {
return null;
}
return BytesRef.deepCopyOf(currentValue());
}
@ -108,10 +110,11 @@ public class TermGroupSelector extends GroupSelector<BytesRef> {
this.values.clear();
this.values.reinit();
for (SearchGroup<BytesRef> sg : searchGroups) {
if (sg.groupValue == null)
if (sg.groupValue == null) {
includeEmpty = true;
else
} else {
this.values.add(sg.groupValue);
}
}
this.secondPass = true;
}

View File

@ -24,9 +24,11 @@ import org.apache.lucene.search.TopFieldDocs;
import org.apache.lucene.search.TotalHits;
import org.apache.lucene.search.TotalHits.Relation;
/** Represents result returned by a grouping search.
/**
* Represents result returned by a grouping search.
*
* @lucene.experimental */
* @lucene.experimental
*/
public class TopGroups<T> {
/** Number of documents matching the search */
public final int totalHitCount;
@ -46,11 +48,16 @@ public class TopGroups<T> {
/** How docs are sorted within each group */
public final SortField[] withinGroupSort;
/** Highest score across all hits, or
* <code>Float.NaN</code> if scores were not computed. */
/** Highest score across all hits, or <code>Float.NaN</code> if scores were not computed. */
public final float maxScore;
public TopGroups(SortField[] groupSort, SortField[] withinGroupSort, int totalHitCount, int totalGroupedHitCount, GroupDocs<T>[] groups, float maxScore) {
public TopGroups(
SortField[] groupSort,
SortField[] withinGroupSort,
int totalHitCount,
int totalGroupedHitCount,
GroupDocs<T>[] groups,
float maxScore) {
this.groupSort = groupSort;
this.withinGroupSort = withinGroupSort;
this.totalHitCount = totalHitCount;
@ -73,7 +80,7 @@ public class TopGroups<T> {
/** How the GroupDocs score (if any) should be merged. */
public enum ScoreMergeMode {
/** Set score to Float.NaN */
None,
None,
/* Sum score across all shards for this group. */
Total,
/* Avg score across all shards for this group. */
@ -81,8 +88,9 @@ public class TopGroups<T> {
}
/**
* If either value is NaN then return the other value, otherwise
* return the greater of the two values by calling Math.max.
* If either value is NaN then return the other value, otherwise return the greater of the two
* values by calling Math.max.
*
* @param a - one value
* @param b - another value
* @return ignoring any NaN return the greater of a and b
@ -93,26 +101,27 @@ public class TopGroups<T> {
return Math.max(a, b);
}
/** Merges an array of TopGroups, for example obtained
* from the second-pass collector across multiple
* shards. Each TopGroups must have been sorted by the
* same groupSort and docSort, and the top groups passed
* to all second-pass collectors must be the same.
/**
* Merges an array of TopGroups, for example obtained from the second-pass collector across
* multiple shards. Each TopGroups must have been sorted by the same groupSort and docSort, and
* the top groups passed to all second-pass collectors must be the same.
*
* <b>NOTE</b>: We can't always compute an exact totalGroupCount.
* Documents belonging to a group may occur on more than
* one shard and thus the merged totalGroupCount can be
* higher than the actual totalGroupCount. In this case the
* totalGroupCount represents a upper bound. If the documents
* of one group do only reside in one shard then the
* totalGroupCount is exact.
* <p><b>NOTE</b>: We can't always compute an exact totalGroupCount. Documents belonging to a
* group may occur on more than one shard and thus the merged totalGroupCount can be higher than
* the actual totalGroupCount. In this case the totalGroupCount represents a upper bound. If the
* documents of one group do only reside in one shard then the totalGroupCount is exact.
*
* <b>NOTE</b>: the topDocs in each GroupDocs is actually
* an instance of TopDocsAndShards
* <p><b>NOTE</b>: the topDocs in each GroupDocs is actually an instance of TopDocsAndShards
*/
public static <T> TopGroups<T> merge(TopGroups<T>[] shardGroups, Sort groupSort, Sort docSort, int docOffset, int docTopN, ScoreMergeMode scoreMergeMode) {
public static <T> TopGroups<T> merge(
TopGroups<T>[] shardGroups,
Sort groupSort,
Sort docSort,
int docOffset,
int docTopN,
ScoreMergeMode scoreMergeMode) {
//System.out.println("TopGroups.merge");
// System.out.println("TopGroups.merge");
if (shardGroups.length == 0) {
return null;
@ -124,9 +133,10 @@ public class TopGroups<T> {
Integer totalGroupCount = null;
final int numGroups = shardGroups[0].groups.length;
for(TopGroups<T> shard : shardGroups) {
for (TopGroups<T> shard : shardGroups) {
if (numGroups != shard.groups.length) {
throw new IllegalArgumentException("number of groups differs across shards; you must pass same top groups to all shards' second-pass collector");
throw new IllegalArgumentException(
"number of groups differs across shards; you must pass same top groups to all shards' second-pass collector");
}
totalHitCount += shard.totalHitCount;
totalGroupedHitCount += shard.totalGroupedHitCount;
@ -139,7 +149,7 @@ public class TopGroups<T> {
}
}
@SuppressWarnings({"unchecked","rawtypes"})
@SuppressWarnings({"unchecked", "rawtypes"})
final GroupDocs<T>[] mergedGroupDocs = new GroupDocs[numGroups];
final TopDocs[] shardTopDocs;
@ -150,22 +160,25 @@ public class TopGroups<T> {
}
float totalMaxScore = Float.NaN;
for(int groupIDX=0;groupIDX<numGroups;groupIDX++) {
for (int groupIDX = 0; groupIDX < numGroups; groupIDX++) {
final T groupValue = shardGroups[0].groups[groupIDX].groupValue;
//System.out.println(" merge groupValue=" + groupValue + " sortValues=" + Arrays.toString(shardGroups[0].groups[groupIDX].groupSortValues));
// System.out.println(" merge groupValue=" + groupValue + " sortValues=" +
// Arrays.toString(shardGroups[0].groups[groupIDX].groupSortValues));
float maxScore = Float.NaN;
int totalHits = 0;
double scoreSum = 0.0;
for(int shardIDX=0;shardIDX<shardGroups.length;shardIDX++) {
//System.out.println(" shard=" + shardIDX);
for (int shardIDX = 0; shardIDX < shardGroups.length; shardIDX++) {
// System.out.println(" shard=" + shardIDX);
final TopGroups<T> shard = shardGroups[shardIDX];
final GroupDocs<?> shardGroupDocs = shard.groups[groupIDX];
if (groupValue == null) {
if (shardGroupDocs.groupValue != null) {
throw new IllegalArgumentException("group values differ across shards; you must pass same top groups to all shards' second-pass collector");
throw new IllegalArgumentException(
"group values differ across shards; you must pass same top groups to all shards' second-pass collector");
}
} else if (!groupValue.equals(shardGroupDocs.groupValue)) {
throw new IllegalArgumentException("group values differ across shards; you must pass same top groups to all shards' second-pass collector");
throw new IllegalArgumentException(
"group values differ across shards; you must pass same top groups to all shards' second-pass collector");
}
/*
@ -175,19 +188,18 @@ public class TopGroups<T> {
*/
if (docSort.equals(Sort.RELEVANCE)) {
shardTopDocs[shardIDX] = new TopDocs(shardGroupDocs.totalHits,
shardGroupDocs.scoreDocs);
shardTopDocs[shardIDX] = new TopDocs(shardGroupDocs.totalHits, shardGroupDocs.scoreDocs);
} else {
shardTopDocs[shardIDX] = new TopFieldDocs(shardGroupDocs.totalHits,
shardGroupDocs.scoreDocs,
docSort.getSort());
shardTopDocs[shardIDX] =
new TopFieldDocs(
shardGroupDocs.totalHits, shardGroupDocs.scoreDocs, docSort.getSort());
}
for (int i = 0; i < shardTopDocs[shardIDX].scoreDocs.length; i++) {
shardTopDocs[shardIDX].scoreDocs[i].shardIndex = shardIDX;
}
maxScore = nonNANmax(maxScore, shardGroupDocs.maxScore);
maxScore = nonNANmax(maxScore, shardGroupDocs.maxScore);
assert shardGroupDocs.totalHits.relation == Relation.EQUAL_TO;
totalHits += shardGroupDocs.totalHits.value;
scoreSum += shardGroupDocs.score;
@ -208,57 +220,63 @@ public class TopGroups<T> {
mergedScoreDocs = new ScoreDoc[0];
} else {
mergedScoreDocs = new ScoreDoc[mergedTopDocs.scoreDocs.length - docOffset];
System.arraycopy(mergedTopDocs.scoreDocs,
docOffset,
mergedScoreDocs,
0,
mergedTopDocs.scoreDocs.length - docOffset);
System.arraycopy(
mergedTopDocs.scoreDocs,
docOffset,
mergedScoreDocs,
0,
mergedTopDocs.scoreDocs.length - docOffset);
}
final float groupScore;
switch(scoreMergeMode) {
case None:
groupScore = Float.NaN;
break;
case Avg:
if (totalHits > 0) {
groupScore = (float) (scoreSum / totalHits);
} else {
switch (scoreMergeMode) {
case None:
groupScore = Float.NaN;
}
break;
case Total:
groupScore = (float) scoreSum;
break;
default:
throw new IllegalArgumentException("can't handle ScoreMergeMode " + scoreMergeMode);
break;
case Avg:
if (totalHits > 0) {
groupScore = (float) (scoreSum / totalHits);
} else {
groupScore = Float.NaN;
}
break;
case Total:
groupScore = (float) scoreSum;
break;
default:
throw new IllegalArgumentException("can't handle ScoreMergeMode " + scoreMergeMode);
}
//System.out.println("SHARDS=" + Arrays.toString(mergedTopDocs.shardIndex));
mergedGroupDocs[groupIDX] = new GroupDocs<>(groupScore,
maxScore,
new TotalHits(totalHits, TotalHits.Relation.EQUAL_TO),
mergedScoreDocs,
groupValue,
shardGroups[0].groups[groupIDX].groupSortValues);
// System.out.println("SHARDS=" + Arrays.toString(mergedTopDocs.shardIndex));
mergedGroupDocs[groupIDX] =
new GroupDocs<>(
groupScore,
maxScore,
new TotalHits(totalHits, TotalHits.Relation.EQUAL_TO),
mergedScoreDocs,
groupValue,
shardGroups[0].groups[groupIDX].groupSortValues);
totalMaxScore = nonNANmax(totalMaxScore, maxScore);
}
if (totalGroupCount != null) {
TopGroups<T> result = new TopGroups<>(groupSort.getSort(),
docSort.getSort(),
totalHitCount,
totalGroupedHitCount,
mergedGroupDocs,
totalMaxScore);
TopGroups<T> result =
new TopGroups<>(
groupSort.getSort(),
docSort.getSort(),
totalHitCount,
totalGroupedHitCount,
mergedGroupDocs,
totalMaxScore);
return new TopGroups<>(result, totalGroupCount);
} else {
return new TopGroups<>(groupSort.getSort(),
docSort.getSort(),
totalHitCount,
totalGroupedHitCount,
mergedGroupDocs,
totalMaxScore);
return new TopGroups<>(
groupSort.getSort(),
docSort.getSort(),
totalHitCount,
totalGroupedHitCount,
mergedGroupDocs,
totalMaxScore);
}
}
}

View File

@ -21,7 +21,6 @@ import java.io.IOException;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Supplier;
import org.apache.lucene.search.FilterCollector;
import org.apache.lucene.search.MultiCollector;
import org.apache.lucene.search.Scorable;
@ -36,8 +35,8 @@ import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.util.ArrayUtil;
/**
* A second-pass collector that collects the TopDocs for each group, and
* returns them as a {@link TopGroups} object
* A second-pass collector that collects the TopDocs for each group, and returns them as a {@link
* TopGroups} object
*
* @param <T> the type of the group value
*/
@ -49,21 +48,28 @@ public class TopGroupsCollector<T> extends SecondPassGroupingCollector<T> {
/**
* Create a new TopGroupsCollector
* @param groupSelector the group selector used to define groups
* @param groups the groups to collect TopDocs for
* @param groupSort the order in which groups are returned
* @param withinGroupSort the order in which documents are sorted in each group
* @param maxDocsPerGroup the maximum number of docs to collect for each group
* @param getMaxScores if true, record the maximum score for each group
*
* @param groupSelector the group selector used to define groups
* @param groups the groups to collect TopDocs for
* @param groupSort the order in which groups are returned
* @param withinGroupSort the order in which documents are sorted in each group
* @param maxDocsPerGroup the maximum number of docs to collect for each group
* @param getMaxScores if true, record the maximum score for each group
*/
public TopGroupsCollector(GroupSelector<T> groupSelector, Collection<SearchGroup<T>> groups, Sort groupSort, Sort withinGroupSort,
int maxDocsPerGroup, boolean getMaxScores) {
super(groupSelector, groups,
public TopGroupsCollector(
GroupSelector<T> groupSelector,
Collection<SearchGroup<T>> groups,
Sort groupSort,
Sort withinGroupSort,
int maxDocsPerGroup,
boolean getMaxScores) {
super(
groupSelector,
groups,
new TopDocsReducer<>(withinGroupSort, maxDocsPerGroup, getMaxScores));
this.groupSort = Objects.requireNonNull(groupSort);
this.withinGroupSort = Objects.requireNonNull(withinGroupSort);
this.maxDocsPerGroup = maxDocsPerGroup;
}
private static class MaxScoreCollector extends SimpleCollector {
@ -98,8 +104,11 @@ public class TopGroupsCollector<T> extends SecondPassGroupingCollector<T> {
private final TopDocsCollector<?> topDocsCollector;
private final MaxScoreCollector maxScoreCollector;
private final boolean sortedByScore;
public TopDocsAndMaxScoreCollector(boolean sortedByScore, TopDocsCollector<?> topDocsCollector, MaxScoreCollector maxScoreCollector) {
public TopDocsAndMaxScoreCollector(
boolean sortedByScore,
TopDocsCollector<?> topDocsCollector,
MaxScoreCollector maxScoreCollector) {
super(MultiCollector.wrap(topDocsCollector, maxScoreCollector));
this.sortedByScore = sortedByScore;
this.topDocsCollector = topDocsCollector;
@ -112,17 +121,24 @@ public class TopGroupsCollector<T> extends SecondPassGroupingCollector<T> {
private final Supplier<TopDocsAndMaxScoreCollector> supplier;
private final boolean needsScores;
TopDocsReducer(Sort withinGroupSort,
int maxDocsPerGroup, boolean getMaxScores) {
TopDocsReducer(Sort withinGroupSort, int maxDocsPerGroup, boolean getMaxScores) {
this.needsScores = getMaxScores || withinGroupSort.needsScores();
if (withinGroupSort == Sort.RELEVANCE) {
supplier = () -> new TopDocsAndMaxScoreCollector(true, TopScoreDocCollector.create(maxDocsPerGroup, Integer.MAX_VALUE), null);
supplier =
() ->
new TopDocsAndMaxScoreCollector(
true, TopScoreDocCollector.create(maxDocsPerGroup, Integer.MAX_VALUE), null);
} else {
supplier = () -> {
TopFieldCollector topDocsCollector = TopFieldCollector.create(withinGroupSort, maxDocsPerGroup, Integer.MAX_VALUE); // TODO: disable exact counts?
MaxScoreCollector maxScoreCollector = getMaxScores ? new MaxScoreCollector() : null;
return new TopDocsAndMaxScoreCollector(false, topDocsCollector, maxScoreCollector);
};
supplier =
() -> {
TopFieldCollector topDocsCollector =
TopFieldCollector.create(
withinGroupSort,
maxDocsPerGroup,
Integer.MAX_VALUE); // TODO: disable exact counts?
MaxScoreCollector maxScoreCollector = getMaxScores ? new MaxScoreCollector() : null;
return new TopDocsAndMaxScoreCollector(false, topDocsCollector, maxScoreCollector);
};
}
}
@ -139,25 +155,34 @@ public class TopGroupsCollector<T> extends SecondPassGroupingCollector<T> {
/**
* Get the TopGroups recorded by this collector
*
* @param withinGroupOffset the offset within each group to start collecting documents
*/
public TopGroups<T> getTopGroups(int withinGroupOffset) {
@SuppressWarnings({"unchecked","rawtypes"})
@SuppressWarnings({"unchecked", "rawtypes"})
final GroupDocs<T>[] groupDocsResult = (GroupDocs<T>[]) new GroupDocs[groups.size()];
int groupIDX = 0;
float maxScore = Float.MIN_VALUE;
for(SearchGroup<T> group : groups) {
TopDocsAndMaxScoreCollector collector = (TopDocsAndMaxScoreCollector) groupReducer.getCollector(group.groupValue);
for (SearchGroup<T> group : groups) {
TopDocsAndMaxScoreCollector collector =
(TopDocsAndMaxScoreCollector) groupReducer.getCollector(group.groupValue);
final TopDocs topDocs;
final float groupMaxScore;
if (collector.sortedByScore) {
TopDocs allTopDocs = collector.topDocsCollector.topDocs();
groupMaxScore = allTopDocs.scoreDocs.length == 0 ? Float.NaN : allTopDocs.scoreDocs[0].score;
groupMaxScore =
allTopDocs.scoreDocs.length == 0 ? Float.NaN : allTopDocs.scoreDocs[0].score;
if (allTopDocs.scoreDocs.length <= withinGroupOffset) {
topDocs = new TopDocs(allTopDocs.totalHits, new ScoreDoc[0]);
} else {
topDocs = new TopDocs(allTopDocs.totalHits, ArrayUtil.copyOfSubArray(allTopDocs.scoreDocs, withinGroupOffset, Math.min(allTopDocs.scoreDocs.length, withinGroupOffset + maxDocsPerGroup)));
topDocs =
new TopDocs(
allTopDocs.totalHits,
ArrayUtil.copyOfSubArray(
allTopDocs.scoreDocs,
withinGroupOffset,
Math.min(allTopDocs.scoreDocs.length, withinGroupOffset + maxDocsPerGroup)));
}
} else {
topDocs = collector.topDocsCollector.topDocs(withinGroupOffset, maxDocsPerGroup);
@ -167,21 +192,24 @@ public class TopGroupsCollector<T> extends SecondPassGroupingCollector<T> {
groupMaxScore = collector.maxScoreCollector.getMaxScore();
}
}
groupDocsResult[groupIDX++] = new GroupDocs<>(Float.NaN,
groupMaxScore,
topDocs.totalHits,
topDocs.scoreDocs,
group.groupValue,
group.sortValues);
groupDocsResult[groupIDX++] =
new GroupDocs<>(
Float.NaN,
groupMaxScore,
topDocs.totalHits,
topDocs.scoreDocs,
group.groupValue,
group.sortValues);
maxScore = Math.max(maxScore, groupMaxScore);
}
return new TopGroups<>(groupSort.getSort(),
return new TopGroups<>(
groupSort.getSort(),
withinGroupSort.getSort(),
totalHitCount, totalGroupedHitCount, groupDocsResult,
totalHitCount,
totalGroupedHitCount,
groupDocsResult,
maxScore);
}
}

View File

@ -22,16 +22,13 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.Scorable;
import org.apache.lucene.util.mutable.MutableValue;
/**
* A GroupSelector that groups via a ValueSource
*/
/** A GroupSelector that groups via a ValueSource */
public class ValueSourceGroupSelector extends GroupSelector<MutableValue> {
private final ValueSource valueSource;
@ -41,8 +38,9 @@ public class ValueSourceGroupSelector extends GroupSelector<MutableValue> {
/**
* Create a new ValueSourceGroupSelector
*
* @param valueSource the ValueSource to group by
* @param context a context map for the ValueSource
* @param context a context map for the ValueSource
*/
public ValueSourceGroupSelector(ValueSource valueSource, Map<Object, Object> context) {
this.valueSource = valueSource;
@ -58,14 +56,15 @@ public class ValueSourceGroupSelector extends GroupSelector<MutableValue> {
}
@Override
public void setScorer(Scorable scorer) throws IOException { }
public void setScorer(Scorable scorer) throws IOException {}
@Override
public State advanceTo(int doc) throws IOException {
this.filler.fillValue(doc);
if (secondPassGroups != null) {
if (secondPassGroups.contains(filler.getValue()) == false)
if (secondPassGroups.contains(filler.getValue()) == false) {
return State.SKIP;
}
}
return State.ACCEPT;
}

View File

@ -15,143 +15,125 @@
* limitations under the License.
*/
/**
/**
* Grouping.
* <p>
* This module enables search result grouping with Lucene, where hits
* with the same value in the specified single-valued group field are
* grouped together. For example, if you group by the <code>author</code>
* field, then all documents with the same value in the <code>author</code>
* field fall into a single group.
* </p>
*
* <p>Grouping requires a number of inputs:</p>
*
*
* <p>This module enables search result grouping with Lucene, where hits with the same value in the
* specified single-valued group field are grouped together. For example, if you group by the <code>
* author</code> field, then all documents with the same value in the <code>author</code> field fall
* into a single group.
*
* <p>Grouping requires a number of inputs:
*
* <ul>
* <li><code>groupSelector</code>: this defines how groups are created
* from values per-document. The grouping module ships with
* selectors for grouping by term, and by long and double ranges.
*
* <li><code>groupSort</code>: how the groups are sorted. For sorting
* purposes, each group is "represented" by the highest-sorted
* document according to the <code>groupSort</code> within it. For
* example, if you specify "price" (ascending) then the first group
* is the one with the lowest price book within it. Or if you
* specify relevance group sort, then the first group is the one
* containing the highest scoring book.
*
* <li><code>topNGroups</code>: how many top groups to keep. For
* example, 10 means the top 10 groups are computed.
*
* <li><code>groupOffset</code>: which "slice" of top groups you want to
* retrieve. For example, 3 means you'll get 7 groups back
* (assuming <code>topNGroups</code> is 10). This is useful for
* <li><code>groupSelector</code>: this defines how groups are created from values per-document.
* The grouping module ships with selectors for grouping by term, and by long and double
* ranges.
* <li><code>groupSort</code>: how the groups are sorted. For sorting purposes, each group is
* "represented" by the highest-sorted document according to the <code>groupSort</code> within
* it. For example, if you specify "price" (ascending) then the first group is the one with
* the lowest price book within it. Or if you specify relevance group sort, then the first
* group is the one containing the highest scoring book.
* <li><code>topNGroups</code>: how many top groups to keep. For example, 10 means the top 10
* groups are computed.
* <li><code>groupOffset</code>: which "slice" of top groups you want to retrieve. For example, 3
* means you'll get 7 groups back (assuming <code>topNGroups</code> is 10). This is useful for
* paging, where you might show 5 groups per page.
*
* <li><code>withinGroupSort</code>: how the documents within each group
* are sorted. This can be different from the group sort.
*
* <li><code>maxDocsPerGroup</code>: how many top documents within each
* group to keep.
*
* <li><code>withinGroupOffset</code>: which "slice" of top
* documents you want to retrieve from each group.
*
* <li><code>withinGroupSort</code>: how the documents within each group are sorted. This can be
* different from the group sort.
* <li><code>maxDocsPerGroup</code>: how many top documents within each group to keep.
* <li><code>withinGroupOffset</code>: which "slice" of top documents you want to retrieve from
* each group.
* </ul>
*
*
* <p>The implementation is two-pass: the first pass ({@link
* org.apache.lucene.search.grouping.FirstPassGroupingCollector})
* gathers the top groups, and the second pass ({@link
* org.apache.lucene.search.grouping.SecondPassGroupingCollector})
* gathers documents within those groups. If the search is costly to
* run you may want to use the {@link
* org.apache.lucene.search.CachingCollector} class, which
* caches hits and can (quickly) replay them for the second pass. This
* way you only run the query once, but you pay a RAM cost to (briefly)
* hold all hits. Results are returned as a {@link
* org.apache.lucene.search.grouping.TopGroups} instance.</p>
*
* <p>Groups are defined by {@link org.apache.lucene.search.grouping.GroupSelector}
* implementations:</p>
* <ul>
* <li>{@link org.apache.lucene.search.grouping.TermGroupSelector} groups based on
* the value of a {@link org.apache.lucene.index.SortedDocValues} field</li>
* <li>{@link org.apache.lucene.search.grouping.ValueSourceGroupSelector} groups based on
* the value of a {@link org.apache.lucene.queries.function.ValueSource}</li>
* <li>{@link org.apache.lucene.search.grouping.DoubleRangeGroupSelector} groups based on
* the value of a {@link org.apache.lucene.search.DoubleValuesSource}</li>
* <li>{@link org.apache.lucene.search.grouping.LongRangeGroupSelector} groups based on
* the value of a {@link org.apache.lucene.search.LongValuesSource}</li>
* </ul>
*
* <p>Known limitations:</p>
* org.apache.lucene.search.grouping.FirstPassGroupingCollector}) gathers the top groups, and the
* second pass ({@link org.apache.lucene.search.grouping.SecondPassGroupingCollector}) gathers
* documents within those groups. If the search is costly to run you may want to use the {@link
* org.apache.lucene.search.CachingCollector} class, which caches hits and can (quickly) replay them
* for the second pass. This way you only run the query once, but you pay a RAM cost to (briefly)
* hold all hits. Results are returned as a {@link org.apache.lucene.search.grouping.TopGroups}
* instance.
*
* <p>Groups are defined by {@link org.apache.lucene.search.grouping.GroupSelector} implementations:
*
* <ul>
* <li> Sharding is not directly supported, though is not too
* difficult, if you can merge the top groups and top documents per
* group yourself.
* <li>{@link org.apache.lucene.search.grouping.TermGroupSelector} groups based on the value of a
* {@link org.apache.lucene.index.SortedDocValues} field
* <li>{@link org.apache.lucene.search.grouping.ValueSourceGroupSelector} groups based on the
* value of a {@link org.apache.lucene.queries.function.ValueSource}
* <li>{@link org.apache.lucene.search.grouping.DoubleRangeGroupSelector} groups based on the
* value of a {@link org.apache.lucene.search.DoubleValuesSource}
* <li>{@link org.apache.lucene.search.grouping.LongRangeGroupSelector} groups based on the value
* of a {@link org.apache.lucene.search.LongValuesSource}
* </ul>
*
* <p>Typical usage for the generic two-pass grouping search looks like this using the grouping convenience utility
* (optionally using caching for the second pass search):</p>
*
*
* <p>Known limitations:
*
* <ul>
* <li>Sharding is not directly supported, though is not too difficult, if you can merge the top
* groups and top documents per group yourself.
* </ul>
*
* <p>Typical usage for the generic two-pass grouping search looks like this using the grouping
* convenience utility (optionally using caching for the second pass search):
*
* <pre class="prettyprint">
* GroupingSearch groupingSearch = new GroupingSearch("author");
* groupingSearch.setGroupSort(groupSort);
* groupingSearch.setFillSortFields(fillFields);
*
*
* if (useCache) {
* // Sets cache in MB
* groupingSearch.setCachingInMB(4.0, true);
* }
*
*
* if (requiredTotalGroupCount) {
* groupingSearch.setAllGroups(true);
* }
*
*
* TermQuery query = new TermQuery(new Term("content", searchTerm));
* TopGroups&lt;BytesRef&gt; result = groupingSearch.search(indexSearcher, query, groupOffset, groupLimit);
*
*
* // Render groupsResult...
* if (requiredTotalGroupCount) {
* int totalGroupCount = result.totalGroupCount;
* }
* </pre>
*
* <p>To use the single-pass <code>BlockGroupingCollector</code>,
* first, at indexing time, you must ensure all docs in each group
* are added as a block, and you have some way to find the last
* document of each group. One simple way to do this is to add a
* marker binary field:</p>
*
*
* <p>To use the single-pass <code>BlockGroupingCollector</code>, first, at indexing time, you must
* ensure all docs in each group are added as a block, and you have some way to find the last
* document of each group. One simple way to do this is to add a marker binary field:
*
* <pre class="prettyprint">
* // Create Documents from your source:
* List&lt;Document&gt; oneGroup = ...;
*
*
* Field groupEndField = new Field("groupEnd", "x", Field.Store.NO, Field.Index.NOT_ANALYZED);
* groupEndField.setIndexOptions(IndexOptions.DOCS_ONLY);
* groupEndField.setOmitNorms(true);
* oneGroup.get(oneGroup.size()-1).add(groupEndField);
*
*
* // You can also use writer.updateDocuments(); just be sure you
* // replace an entire previous doc block with this new one. For
* // example, each group could have a "groupID" field, with the same
* // value for all docs in this group:
* writer.addDocuments(oneGroup);
* </pre>
*
*
* Then, at search time:
*
*
* <pre class="prettyprint">
* Query groupEndDocs = new TermQuery(new Term("groupEnd", "x"));
* BlockGroupingCollector c = new BlockGroupingCollector(groupSort, groupOffset+topNGroups, needsScores, groupEndDocs);
* s.search(new TermQuery(new Term("content", searchTerm)), c);
* TopGroups groupsResult = c.getTopGroups(withinGroupSort, groupOffset, docOffset, docOffset+docsPerGroup, fillFields);
*
*
* // Render groupsResult...
* </pre>
*
*
* Or alternatively use the <code>GroupingSearch</code> convenience utility:
*
*
* <pre class="prettyprint">
* // Per search:
* GroupingSearch groupingSearch = new GroupingSearch(groupEndDocs);
@ -162,18 +144,18 @@
*
* // Render groupsResult...
* </pre>
*
* Note that the <code>groupValue</code> of each <code>GroupDocs</code>
* will be <code>null</code>, so if you need to present this value you'll
* have to separately retrieve it (for example using stored
* fields, <code>FieldCache</code>, etc.).
*
* <p>Another collector is the <code>AllGroupHeadsCollector</code> that can be used to retrieve all most relevant
* documents per group. Also known as group heads. This can be useful in situations when one wants to compute group
* based facets / statistics on the complete query result. The collector can be executed during the first or second
* phase. This collector can also be used with the <code>GroupingSearch</code> convenience utility, but when if one only
* wants to compute the most relevant documents per group it is better to just use the collector as done here below.</p>
*
*
* Note that the <code>groupValue</code> of each <code>GroupDocs</code> will be <code>null</code>,
* so if you need to present this value you'll have to separately retrieve it (for example using
* stored fields, <code>FieldCache</code>, etc.).
*
* <p>Another collector is the <code>AllGroupHeadsCollector</code> that can be used to retrieve all
* most relevant documents per group. Also known as group heads. This can be useful in situations
* when one wants to compute group based facets / statistics on the complete query result. The
* collector can be executed during the first or second phase. This collector can also be used with
* the <code>GroupingSearch</code> convenience utility, but when if one only wants to compute the
* most relevant documents per group it is better to just use the collector as done here below.
*
* <pre class="prettyprint">
* TermGroupSelector grouper = new TermGroupSelector(groupField);
* AllGroupHeadsCollector c = AllGroupHeadsCollector.newCollector(grouper, sortWithinGroup);
@ -184,6 +166,5 @@
* int maxDoc = s.maxDoc();
* FixedBitSet groupHeadsBitSet = c.retrieveGroupHeads(maxDoc)
* </pre>
*
*/
package org.apache.lucene.search.grouping;

View File

@ -18,7 +18,6 @@ package org.apache.lucene.search.grouping;
import java.io.Closeable;
import java.io.IOException;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.index.RandomIndexWriter;
import org.apache.lucene.search.IndexSearcher;
@ -28,10 +27,9 @@ import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.TestUtil;
/**
* Base class for grouping related tests.
*/
// TODO (MvG) : The grouping tests contain a lot of code duplication. Try to move the common code to this class..
/** Base class for grouping related tests. */
// TODO (MvG) : The grouping tests contain a lot of code duplication. Try to move the common code to
// this class..
public abstract class AbstractGroupingTestCase extends LuceneTestCase {
protected String generateRandomNonEmptyString() {
@ -41,7 +39,7 @@ public abstract class AbstractGroupingTestCase extends LuceneTestCase {
// For that reason we don't generate empty string
// groups.
randomValue = TestUtil.randomRealisticUnicodeString(random());
//randomValue = _TestUtil.randomSimpleString(random());
// randomValue = _TestUtil.randomSimpleString(random());
} while ("".equals(randomValue));
return randomValue;
}
@ -62,8 +60,11 @@ public abstract class AbstractGroupingTestCase extends LuceneTestCase {
Shard() throws IOException {
this.directory = newDirectory();
this.writer = new RandomIndexWriter(random(), directory,
newIndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(newLogMergePolicy()));
this.writer =
new RandomIndexWriter(
random(),
directory,
newIndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(newLogMergePolicy()));
}
IndexSearcher getIndexSearcher() throws IOException {

View File

@ -21,7 +21,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.NumericDocValuesField;
@ -54,7 +53,7 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
Shard shard = new Shard();
indexRandomDocs(shard.writer);
String[] query = new String[]{ "foo", "bar", "baz" };
String[] query = new String[] {"foo", "bar", "baz"};
Query topLevel = new TermQuery(new Term("text", query[random().nextInt(query.length)]));
IndexSearcher searcher = shard.getIndexSearcher();
@ -65,10 +64,11 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
for (int i = 0; i < topGroups.groups.length; i++) {
// Each group should have a result set equal to that returned by the top-level query,
// filtered by the group value.
Query filtered = new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(filterQuery(topGroups.groups[i].groupValue), BooleanClause.Occur.FILTER)
.build();
Query filtered =
new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(filterQuery(topGroups.groups[i].groupValue), BooleanClause.Occur.FILTER)
.build();
TopDocs td = searcher.search(filtered, 10);
assertScoreDocsEquals(topGroups.groups[i].scoreDocs, td.scoreDocs);
if (i == 0) {
@ -86,12 +86,15 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
indexRandomDocs(shard.writer);
IndexSearcher searcher = shard.getIndexSearcher();
String[] query = new String[]{ "foo", "bar", "baz" };
String[] query = new String[] {"foo", "bar", "baz"};
Query topLevel = new TermQuery(new Term("text", query[random().nextInt(query.length)]));
GroupingSearch grouper = new GroupingSearch(getGroupSelector());
grouper.setGroupDocsLimit(10);
Sort sort = new Sort(new SortField("sort1", SortField.Type.STRING), new SortField("sort2", SortField.Type.LONG));
Sort sort =
new Sort(
new SortField("sort1", SortField.Type.STRING),
new SortField("sort2", SortField.Type.LONG));
grouper.setGroupSort(sort);
TopGroups<T> topGroups = grouper.search(searcher, topLevel, 0, 5);
TopDocs topDoc = searcher.search(topLevel, 1, sort);
@ -99,10 +102,11 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
// We're sorting the groups by a defined Sort, but each group itself should be ordered
// by doc relevance, and should be equal to the results of a top-level query filtered
// by the group value
Query filtered = new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(filterQuery(topGroups.groups[i].groupValue), BooleanClause.Occur.FILTER)
.build();
Query filtered =
new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(filterQuery(topGroups.groups[i].groupValue), BooleanClause.Occur.FILTER)
.build();
TopDocs td = searcher.search(filtered, 10);
assertScoreDocsEquals(topGroups.groups[i].scoreDocs, td.scoreDocs);
// The top group should have sort values equal to the sort values of the top doc of
@ -111,7 +115,8 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
if (i > 0) {
assertSortsBefore(topGroups.groups[i - 1], topGroups.groups[i]);
} else {
assertArrayEquals(((FieldDoc)topDoc.scoreDocs[0]).fields, topGroups.groups[0].groupSortValues);
assertArrayEquals(
((FieldDoc) topDoc.scoreDocs[0]).fields, topGroups.groups[0].groupSortValues);
}
}
@ -124,12 +129,15 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
indexRandomDocs(shard.writer);
IndexSearcher searcher = shard.getIndexSearcher();
String[] query = new String[]{ "foo", "bar", "baz" };
String[] query = new String[] {"foo", "bar", "baz"};
Query topLevel = new TermQuery(new Term("text", query[random().nextInt(query.length)]));
GroupingSearch grouper = new GroupingSearch(getGroupSelector());
grouper.setGroupDocsLimit(10);
Sort sort = new Sort(new SortField("sort1", SortField.Type.STRING), new SortField("sort2", SortField.Type.LONG));
Sort sort =
new Sort(
new SortField("sort1", SortField.Type.STRING),
new SortField("sort2", SortField.Type.LONG));
grouper.setSortWithinGroup(sort);
TopGroups<T> topGroups = grouper.search(searcher, topLevel, 0, 5);
@ -146,16 +154,16 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
}
// Groups themselves are ordered by a defined Sort, and each should give the same result as
// the top-level query, filtered by the group value, with the same Sort
Query filtered = new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(filterQuery(topGroups.groups[i].groupValue), BooleanClause.Occur.FILTER)
.build();
Query filtered =
new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(filterQuery(topGroups.groups[i].groupValue), BooleanClause.Occur.FILTER)
.build();
TopDocs td = searcher.search(filtered, 10, sort);
assertScoreDocsEquals(td.scoreDocs, topGroups.groups[i].scoreDocs);
}
shard.close();
}
public void testGroupHeads() throws IOException {
@ -164,7 +172,7 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
indexRandomDocs(shard.writer);
IndexSearcher searcher = shard.getIndexSearcher();
String[] query = new String[]{ "foo", "bar", "baz" };
String[] query = new String[] {"foo", "bar", "baz"};
Query topLevel = new TermQuery(new Term("text", query[random().nextInt(query.length)]));
GroupSelector<T> groupSelector = getGroupSelector();
@ -180,10 +188,11 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
int totalHits = searcher.count(topLevel);
int groupHits = 0;
for (T groupValue : matchingGroups) {
Query filtered = new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(filterQuery(groupValue), BooleanClause.Occur.FILTER)
.build();
Query filtered =
new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(filterQuery(groupValue), BooleanClause.Occur.FILTER)
.build();
groupHits += searcher.count(filtered);
}
assertEquals(totalHits, groupHits);
@ -195,15 +204,17 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
cardinality++;
}
}
assertEquals(matchingGroups.size(), cardinality); // We should have one set bit per matching group
assertEquals(
matchingGroups.size(), cardinality); // We should have one set bit per matching group
// Each group head should correspond to the topdoc of a search filtered by
// that group
for (T groupValue : matchingGroups) {
Query filtered = new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(filterQuery(groupValue), BooleanClause.Occur.FILTER)
.build();
Query filtered =
new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(filterQuery(groupValue), BooleanClause.Occur.FILTER)
.build();
TopDocs td = searcher.search(filtered, 1);
assertTrue(groupHeads.get(td.scoreDocs[0].doc));
}
@ -217,10 +228,13 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
indexRandomDocs(shard.writer);
IndexSearcher searcher = shard.getIndexSearcher();
String[] query = new String[]{ "foo", "bar", "baz" };
String[] query = new String[] {"foo", "bar", "baz"};
Query topLevel = new TermQuery(new Term("text", query[random().nextInt(query.length)]));
Sort sort = new Sort(new SortField("sort1", SortField.Type.STRING), new SortField("sort2", SortField.Type.LONG));
Sort sort =
new Sort(
new SortField("sort1", SortField.Type.STRING),
new SortField("sort2", SortField.Type.LONG));
GroupSelector<T> groupSelector = getGroupSelector();
GroupingSearch grouping = new GroupingSearch(groupSelector);
grouping.setAllGroups(true);
@ -237,15 +251,17 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
cardinality++;
}
}
assertEquals(matchingGroups.size(), cardinality); // We should have one set bit per matching group
assertEquals(
matchingGroups.size(), cardinality); // We should have one set bit per matching group
// Each group head should correspond to the topdoc of a search filtered by
// that group using the same within-group sort
for (T groupValue : matchingGroups) {
Query filtered = new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(filterQuery(groupValue), BooleanClause.Occur.FILTER)
.build();
Query filtered =
new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(filterQuery(groupValue), BooleanClause.Occur.FILTER)
.build();
TopDocs td = searcher.search(filtered, 1, sort);
assertTrue(groupHeads.get(td.scoreDocs[0].doc));
}
@ -263,7 +279,7 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
shards[i] = new Shard();
}
String[] texts = new String[]{ "foo", "bar", "bar baz", "foo foo bar" };
String[] texts = new String[] {"foo", "bar", "bar baz", "foo foo bar"};
// Create a bunch of random documents, and index them - once into the control index,
// and once into a randomly picked shard.
@ -282,29 +298,35 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
shards[shard].writer.addDocument(doc);
}
String[] query = new String[]{ "foo", "bar", "baz" };
String[] query = new String[] {"foo", "bar", "baz"};
Query topLevel = new TermQuery(new Term("text", query[random().nextInt(query.length)]));
Sort sort = new Sort(new SortField("sort1", SortField.Type.STRING), new SortField("sort2", SortField.Type.LONG));
Sort sort =
new Sort(
new SortField("sort1", SortField.Type.STRING),
new SortField("sort2", SortField.Type.LONG));
// A grouped query run in two phases against the control should give us the same
// result as the query run against shards and merged back together after each phase.
FirstPassGroupingCollector<T> singletonFirstPass = new FirstPassGroupingCollector<>(getGroupSelector(), sort, 5);
FirstPassGroupingCollector<T> singletonFirstPass =
new FirstPassGroupingCollector<>(getGroupSelector(), sort, 5);
control.getIndexSearcher().search(topLevel, singletonFirstPass);
Collection<SearchGroup<T>> singletonGroups = singletonFirstPass.getTopGroups(0);
List<Collection<SearchGroup<T>>> shardGroups = new ArrayList<>();
for (Shard shard : shards) {
FirstPassGroupingCollector<T> fc = new FirstPassGroupingCollector<>(getGroupSelector(), sort, 5);
FirstPassGroupingCollector<T> fc =
new FirstPassGroupingCollector<>(getGroupSelector(), sort, 5);
shard.getIndexSearcher().search(topLevel, fc);
shardGroups.add(fc.getTopGroups(0));
}
Collection<SearchGroup<T>> mergedGroups = SearchGroup.merge(shardGroups, 0, 5, sort);
assertEquals(singletonGroups, mergedGroups);
TopGroupsCollector<T> singletonSecondPass = new TopGroupsCollector<>(getGroupSelector(), singletonGroups, sort,
Sort.RELEVANCE, 5, true);
TopGroupsCollector<T> singletonSecondPass =
new TopGroupsCollector<>(
getGroupSelector(), singletonGroups, sort, Sort.RELEVANCE, 5, true);
control.getIndexSearcher().search(topLevel, singletonSecondPass);
TopGroups<T> singletonTopGroups = singletonSecondPass.getTopGroups(0);
@ -313,12 +335,14 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
TopGroups<T>[] shardTopGroups = (TopGroups<T>[]) new TopGroups<?>[shards.length];
int j = 0;
for (Shard shard : shards) {
TopGroupsCollector<T> sc = new TopGroupsCollector<>(getGroupSelector(), mergedGroups, sort, Sort.RELEVANCE, 5, true);
TopGroupsCollector<T> sc =
new TopGroupsCollector<>(getGroupSelector(), mergedGroups, sort, Sort.RELEVANCE, 5, true);
shard.getIndexSearcher().search(topLevel, sc);
shardTopGroups[j] = sc.getTopGroups(0);
j++;
}
TopGroups<T> mergedTopGroups = TopGroups.merge(shardTopGroups, sort, Sort.RELEVANCE, 0, 5, TopGroups.ScoreMergeMode.None);
TopGroups<T> mergedTopGroups =
TopGroups.merge(shardTopGroups, sort, Sort.RELEVANCE, 0, 5, TopGroups.ScoreMergeMode.None);
assertNotNull(mergedTopGroups);
assertEquals(singletonTopGroups.totalGroupedHitCount, mergedTopGroups.totalGroupedHitCount);
@ -327,18 +351,19 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
assertEquals(singletonTopGroups.groups.length, mergedTopGroups.groups.length);
for (int i = 0; i < singletonTopGroups.groups.length; i++) {
assertEquals(singletonTopGroups.groups[i].groupValue, mergedTopGroups.groups[i].groupValue);
assertEquals(singletonTopGroups.groups[i].scoreDocs.length, mergedTopGroups.groups[i].scoreDocs.length);
assertEquals(
singletonTopGroups.groups[i].scoreDocs.length,
mergedTopGroups.groups[i].scoreDocs.length);
}
control.close();
for (Shard shard : shards) {
shard.close();
}
}
private void indexRandomDocs(RandomIndexWriter w) throws IOException {
String[] texts = new String[]{ "foo", "bar", "bar baz", "foo foo bar" };
String[] texts = new String[] {"foo", "bar", "bar baz", "foo foo bar"};
int numDocs = atLeast(200);
for (int i = 0; i < numDocs; i++) {
@ -356,10 +381,9 @@ public abstract class BaseGroupSelectorTestCase<T> extends AbstractGroupingTestC
private void assertSortsBefore(GroupDocs<T> first, GroupDocs<T> second) {
Object[] groupSortValues = second.groupSortValues;
Object[] prevSortValues = first.groupSortValues;
assertTrue(((BytesRef)prevSortValues[0]).compareTo((BytesRef)groupSortValues[0]) <= 0);
assertTrue(((BytesRef) prevSortValues[0]).compareTo((BytesRef) groupSortValues[0]) <= 0);
if (prevSortValues[0].equals(groupSortValues[0])) {
assertTrue((long)prevSortValues[1] <= (long)groupSortValues[1]);
assertTrue((long) prevSortValues[1] <= (long) groupSortValues[1]);
}
}
}

View File

@ -27,7 +27,6 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.document.BinaryDocValuesField;
import org.apache.lucene.document.Document;
@ -61,10 +60,11 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
public void testBasic() throws Exception {
final String groupField = "author";
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(
random(),
dir,
newIndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(newLogMergePolicy()));
RandomIndexWriter w =
new RandomIndexWriter(
random(),
dir,
newIndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(newLogMergePolicy()));
DocValuesType valueType = DocValuesType.SORTED;
// 0
@ -137,34 +137,45 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
int maxDoc = reader.maxDoc();
Sort sortWithinGroup = new Sort(new SortField("id_1", SortField.Type.INT, true));
AllGroupHeadsCollector<?> allGroupHeadsCollector = createRandomCollector(groupField, sortWithinGroup);
AllGroupHeadsCollector<?> allGroupHeadsCollector =
createRandomCollector(groupField, sortWithinGroup);
indexSearcher.search(new TermQuery(new Term("content", "random")), allGroupHeadsCollector);
assertTrue(arrayContains(new int[]{2, 3, 5, 7}, allGroupHeadsCollector.retrieveGroupHeads()));
assertTrue(openBitSetContains(new int[]{2, 3, 5, 7}, allGroupHeadsCollector.retrieveGroupHeads(maxDoc), maxDoc));
assertTrue(arrayContains(new int[] {2, 3, 5, 7}, allGroupHeadsCollector.retrieveGroupHeads()));
assertTrue(
openBitSetContains(
new int[] {2, 3, 5, 7}, allGroupHeadsCollector.retrieveGroupHeads(maxDoc), maxDoc));
allGroupHeadsCollector = createRandomCollector(groupField, sortWithinGroup);
indexSearcher.search(new TermQuery(new Term("content", "some")), allGroupHeadsCollector);
assertTrue(arrayContains(new int[]{2, 3, 4}, allGroupHeadsCollector.retrieveGroupHeads()));
assertTrue(openBitSetContains(new int[]{2, 3, 4}, allGroupHeadsCollector.retrieveGroupHeads(maxDoc), maxDoc));
assertTrue(arrayContains(new int[] {2, 3, 4}, allGroupHeadsCollector.retrieveGroupHeads()));
assertTrue(
openBitSetContains(
new int[] {2, 3, 4}, allGroupHeadsCollector.retrieveGroupHeads(maxDoc), maxDoc));
allGroupHeadsCollector = createRandomCollector(groupField, sortWithinGroup);
indexSearcher.search(new TermQuery(new Term("content", "blob")), allGroupHeadsCollector);
assertTrue(arrayContains(new int[]{1, 5}, allGroupHeadsCollector.retrieveGroupHeads()));
assertTrue(openBitSetContains(new int[]{1, 5}, allGroupHeadsCollector.retrieveGroupHeads(maxDoc), maxDoc));
assertTrue(arrayContains(new int[] {1, 5}, allGroupHeadsCollector.retrieveGroupHeads()));
assertTrue(
openBitSetContains(
new int[] {1, 5}, allGroupHeadsCollector.retrieveGroupHeads(maxDoc), maxDoc));
// STRING sort type triggers different implementation
Sort sortWithinGroup2 = new Sort(new SortField("id_2", SortField.Type.STRING, true));
allGroupHeadsCollector = createRandomCollector(groupField, sortWithinGroup2);
indexSearcher.search(new TermQuery(new Term("content", "random")), allGroupHeadsCollector);
assertTrue(arrayContains(new int[]{2, 3, 5, 7}, allGroupHeadsCollector.retrieveGroupHeads()));
assertTrue(openBitSetContains(new int[]{2, 3, 5, 7}, allGroupHeadsCollector.retrieveGroupHeads(maxDoc), maxDoc));
assertTrue(arrayContains(new int[] {2, 3, 5, 7}, allGroupHeadsCollector.retrieveGroupHeads()));
assertTrue(
openBitSetContains(
new int[] {2, 3, 5, 7}, allGroupHeadsCollector.retrieveGroupHeads(maxDoc), maxDoc));
Sort sortWithinGroup3 = new Sort(new SortField("id_2", SortField.Type.STRING, false));
allGroupHeadsCollector = createRandomCollector(groupField, sortWithinGroup3);
indexSearcher.search(new TermQuery(new Term("content", "random")), allGroupHeadsCollector);
// 7 b/c higher doc id wins, even if order of field is in not in reverse.
assertTrue(arrayContains(new int[]{0, 3, 4, 6}, allGroupHeadsCollector.retrieveGroupHeads()));
assertTrue(openBitSetContains(new int[]{0, 3, 4, 6}, allGroupHeadsCollector.retrieveGroupHeads(maxDoc), maxDoc));
assertTrue(arrayContains(new int[] {0, 3, 4, 6}, allGroupHeadsCollector.retrieveGroupHeads()));
assertTrue(
openBitSetContains(
new int[] {0, 3, 4, 6}, allGroupHeadsCollector.retrieveGroupHeads(maxDoc), maxDoc));
indexSearcher.getIndexReader().close();
dir.close();
@ -174,7 +185,8 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
int numberOfRuns = atLeast(1);
for (int iter = 0; iter < numberOfRuns; iter++) {
if (VERBOSE) {
System.out.println(String.format(Locale.ROOT, "TEST: iter=%d total=%d", iter, numberOfRuns));
System.out.println(
String.format(Locale.ROOT, "TEST: iter=%d total=%d", iter, numberOfRuns));
}
final int numDocs = TestUtil.nextInt(random(), 100, 1000) * RANDOM_MULTIPLIER;
@ -188,10 +200,11 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
for (int i = 0; i < numGroups; i++) {
String randomValue;
do {
// B/c of DV based impl we can't see the difference between an empty string and a null value.
// B/c of DV based impl we can't see the difference between an empty string and a null
// value.
// For that reason we don't generate empty string groups.
randomValue = TestUtil.randomRealisticUnicodeString(random());
//randomValue = TestUtil.randomSimpleString(random());
// randomValue = TestUtil.randomSimpleString(random());
} while ("".equals(randomValue));
groups.add(new BytesRef(randomValue));
}
@ -213,10 +226,8 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
}
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(
random(),
dir,
newIndexWriterConfig(new MockAnalyzer(random())));
RandomIndexWriter w =
new RandomIndexWriter(random(), dir, newIndexWriterConfig(new MockAnalyzer(random())));
DocValuesType valueType = DocValuesType.SORTED;
Document doc = new Document();
@ -250,17 +261,29 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
groupValue = groups.get(random().nextInt(groups.size()));
}
final GroupDoc groupDoc = new GroupDoc(
i,
groupValue,
groups.get(random().nextInt(groups.size())),
groups.get(random().nextInt(groups.size())),
new BytesRef(String.format(Locale.ROOT, "%05d", i)),
contentStrings[random().nextInt(contentStrings.length)]
);
final GroupDoc groupDoc =
new GroupDoc(
i,
groupValue,
groups.get(random().nextInt(groups.size())),
groups.get(random().nextInt(groups.size())),
new BytesRef(String.format(Locale.ROOT, "%05d", i)),
contentStrings[random().nextInt(contentStrings.length)]);
if (VERBOSE) {
System.out.println(" doc content=" + groupDoc.content + " id=" + i + " group=" + (groupDoc.group == null ? "null" : groupDoc.group.utf8ToString()) + " sort1=" + groupDoc.sort1.utf8ToString() + " sort2=" + groupDoc.sort2.utf8ToString() + " sort3=" + groupDoc.sort3.utf8ToString());
System.out.println(
" doc content="
+ groupDoc.content
+ " id="
+ i
+ " group="
+ (groupDoc.group == null ? "null" : groupDoc.group.utf8ToString())
+ " sort1="
+ groupDoc.sort1.utf8ToString()
+ " sort2="
+ groupDoc.sort2.utf8ToString()
+ " sort3="
+ groupDoc.sort3.utf8ToString());
}
groupDocs[i] = groupDoc;
@ -296,7 +319,8 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
Set<Integer> seenIDs = new HashSet<>();
for (int contentID = 0; contentID < 3; contentID++) {
final ScoreDoc[] hits = s.search(new TermQuery(new Term("content", "real" + contentID)), numDocs).scoreDocs;
final ScoreDoc[] hits =
s.search(new TermQuery(new Term("content", "real" + contentID)), numDocs).scoreDocs;
for (ScoreDoc hit : hits) {
int idValue = docIDToFieldId[hit.doc];
final GroupDoc gd = groupDocs[idValue];
@ -315,19 +339,22 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
assertTrue(Float.isFinite(gd.score));
assertTrue(gd.score >= 0.0);
}
for (int searchIter = 0; searchIter < 100; searchIter++) {
if (VERBOSE) {
System.out.println("TEST: searchIter=" + searchIter);
}
final String searchTerm = "real" + random().nextInt(3);
boolean sortByScoreOnly = random().nextBoolean();
Sort sortWithinGroup = getRandomSort(sortByScoreOnly);
AllGroupHeadsCollector<?> allGroupHeadsCollector = createRandomCollector("group", sortWithinGroup);
AllGroupHeadsCollector<?> allGroupHeadsCollector =
createRandomCollector("group", sortWithinGroup);
s.search(new TermQuery(new Term("content", searchTerm)), allGroupHeadsCollector);
int[] expectedGroupHeads = createExpectedGroupHeads(searchTerm, groupDocs, sortWithinGroup, sortByScoreOnly, fieldIdToDocID);
int[] expectedGroupHeads =
createExpectedGroupHeads(
searchTerm, groupDocs, sortWithinGroup, sortByScoreOnly, fieldIdToDocID);
int[] actualGroupHeads = allGroupHeadsCollector.retrieveGroupHeads();
// The actual group heads contains Lucene ids. Need to change them into our id value.
for (int i = 0; i < actualGroupHeads.length; i++) {
@ -336,7 +363,7 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
// Allows us the easily iterate and assert the actual and expected results.
Arrays.sort(expectedGroupHeads);
Arrays.sort(actualGroupHeads);
if (VERBOSE) {
System.out.println("Collector: " + allGroupHeadsCollector.getClass().getSimpleName());
System.out.println("Sort within group: " + sortWithinGroup);
@ -345,41 +372,50 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
System.out.println("\n=== Expected: \n");
for (int expectedDocId : expectedGroupHeads) {
GroupDoc expectedGroupDoc = groupDocs[expectedDocId];
String expectedGroup = expectedGroupDoc.group == null ? null : expectedGroupDoc.group.utf8ToString();
String expectedGroup =
expectedGroupDoc.group == null ? null : expectedGroupDoc.group.utf8ToString();
System.out.println(
String.format(Locale.ROOT,
String.format(
Locale.ROOT,
"Group:%10s score%5f Sort1:%10s Sort2:%10s Sort3:%10s doc:%5d",
expectedGroup, expectedGroupDoc.score, expectedGroupDoc.sort1.utf8ToString(),
expectedGroupDoc.sort2.utf8ToString(), expectedGroupDoc.sort3.utf8ToString(), expectedDocId
)
);
expectedGroup,
expectedGroupDoc.score,
expectedGroupDoc.sort1.utf8ToString(),
expectedGroupDoc.sort2.utf8ToString(),
expectedGroupDoc.sort3.utf8ToString(),
expectedDocId));
}
System.out.println("\n=== Actual: \n");
for (int actualDocId : actualGroupHeads) {
GroupDoc actualGroupDoc = groupDocs[actualDocId];
String actualGroup = actualGroupDoc.group == null ? null : actualGroupDoc.group.utf8ToString();
String actualGroup =
actualGroupDoc.group == null ? null : actualGroupDoc.group.utf8ToString();
System.out.println(
String.format(Locale.ROOT,
String.format(
Locale.ROOT,
"Group:%10s score%5f Sort1:%10s Sort2:%10s Sort3:%10s doc:%5d",
actualGroup, actualGroupDoc.score, actualGroupDoc.sort1.utf8ToString(),
actualGroupDoc.sort2.utf8ToString(), actualGroupDoc.sort3.utf8ToString(), actualDocId
)
);
actualGroup,
actualGroupDoc.score,
actualGroupDoc.sort1.utf8ToString(),
actualGroupDoc.sort2.utf8ToString(),
actualGroupDoc.sort3.utf8ToString(),
actualDocId));
}
System.out.println("\n===================================================================================");
System.out.println(
"\n===================================================================================");
}
assertArrayEquals(expectedGroupHeads, actualGroupHeads);
}
r.close();
dir.close();
}
}
private boolean arrayContains(int[] expected, int[] actual) {
Arrays.sort(actual); // in some cases the actual docs aren't sorted by docid. This method expects that.
// in some cases the actual docs aren't sorted by docid. This method expects that.
Arrays.sort(actual);
if (expected.length != actual.length) {
return false;
}
@ -401,9 +437,10 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
return true;
}
private boolean openBitSetContains(int[] expectedDocs, Bits actual, int maxDoc) throws IOException {
private boolean openBitSetContains(int[] expectedDocs, Bits actual, int maxDoc)
throws IOException {
assert actual instanceof FixedBitSet;
if (expectedDocs.length != ((FixedBitSet)actual).cardinality()) {
if (expectedDocs.length != ((FixedBitSet) actual).cardinality()) {
return false;
}
@ -412,7 +449,12 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
expected.set(expectedDoc);
}
for (int docId = expected.nextSetBit(0); docId != DocIdSetIterator.NO_MORE_DOCS; docId = docId + 1 >= expected.length() ? DocIdSetIterator.NO_MORE_DOCS : expected.nextSetBit(docId + 1)) {
for (int docId = expected.nextSetBit(0);
docId != DocIdSetIterator.NO_MORE_DOCS;
docId =
docId + 1 >= expected.length()
? DocIdSetIterator.NO_MORE_DOCS
: expected.nextSetBit(docId + 1)) {
if (!actual.get(docId)) {
return false;
}
@ -421,7 +463,12 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
return true;
}
private int[] createExpectedGroupHeads(String searchTerm, GroupDoc[] groupDocs, Sort docSort, boolean sortByScoreOnly, int[] fieldIdToDocID) {
private int[] createExpectedGroupHeads(
String searchTerm,
GroupDoc[] groupDocs,
Sort docSort,
boolean sortByScoreOnly,
int[] fieldIdToDocID) {
Map<BytesRef, List<GroupDoc>> groupHeads = new HashMap<>();
for (GroupDoc groupDoc : groupDocs) {
if (!groupDoc.content.startsWith(searchTerm)) {
@ -473,7 +520,8 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
return new Sort(sortFields.toArray(new SortField[sortFields.size()]));
}
private Comparator<GroupDoc> getComparator(Sort sort, final boolean sortByScoreOnly, final int[] fieldIdToDocID) {
private Comparator<GroupDoc> getComparator(
Sort sort, final boolean sortByScoreOnly, final int[] fieldIdToDocID) {
final SortField[] sortFields = sort.getSort();
return new Comparator<GroupDoc>() {
@Override
@ -509,19 +557,22 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
};
}
@SuppressWarnings({"unchecked","rawtypes"})
@SuppressWarnings({"unchecked", "rawtypes"})
private AllGroupHeadsCollector<?> createRandomCollector(String groupField, Sort sortWithinGroup) {
if (random().nextBoolean()) {
ValueSource vs = new BytesRefFieldSource(groupField);
return AllGroupHeadsCollector.newCollector(new ValueSourceGroupSelector(vs, new HashMap<>()), sortWithinGroup);
return AllGroupHeadsCollector.newCollector(
new ValueSourceGroupSelector(vs, new HashMap<>()), sortWithinGroup);
} else {
return AllGroupHeadsCollector.newCollector(new TermGroupSelector(groupField), sortWithinGroup);
return AllGroupHeadsCollector.newCollector(
new TermGroupSelector(groupField), sortWithinGroup);
}
}
private void addGroupField(Document doc, String groupField, String value, DocValuesType valueType) {
private void addGroupField(
Document doc, String groupField, String value, DocValuesType valueType) {
Field valuesField = null;
switch(valueType) {
switch (valueType) {
case BINARY:
valuesField = new BinaryDocValuesField(groupField, new BytesRef(value));
break;
@ -544,7 +595,8 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
final String content;
float score;
public GroupDoc(int id, BytesRef group, BytesRef sort1, BytesRef sort2, BytesRef sort3, String content) {
public GroupDoc(
int id, BytesRef group, BytesRef sort1, BytesRef sort2, BytesRef sort3, String content) {
this.id = id;
this.group = group;
this.sort1 = sort1;
@ -552,7 +604,5 @@ public class TestAllGroupHeadsCollector extends LuceneTestCase {
this.sort3 = sort3;
this.content = content;
}
}
}

View File

@ -17,7 +17,6 @@
package org.apache.lucene.search.grouping;
import java.util.HashMap;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
@ -43,10 +42,11 @@ public class TestAllGroupsCollector extends LuceneTestCase {
customType.setStored(true);
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(
random(),
dir,
newIndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(newLogMergePolicy()));
RandomIndexWriter w =
new RandomIndexWriter(
random(),
dir,
newIndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(newLogMergePolicy()));
// 0
Document doc = new Document();
@ -124,11 +124,9 @@ public class TestAllGroupsCollector extends LuceneTestCase {
private AllGroupsCollector<?> createRandomCollector(String groupField) {
if (random().nextBoolean()) {
return new AllGroupsCollector<>(new TermGroupSelector(groupField));
}
else {
} else {
ValueSource vs = new BytesRefFieldSource(groupField);
return new AllGroupsCollector<>(new ValueSourceGroupSelector(vs, new HashMap<>()));
}
}
}

View File

@ -20,7 +20,6 @@ package org.apache.lucene.search.grouping;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.NumericDocValuesField;
@ -65,16 +64,16 @@ public class TestBlockGrouping extends AbstractGroupingTestCase {
String bookName = searcher.doc(tg.groups[i].scoreDocs[0].doc).get("book");
// The contents of each group should be equal to the results of a search for
// that group alone
Query filtered = new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(new TermQuery(new Term("book", bookName)), BooleanClause.Occur.FILTER)
.build();
Query filtered =
new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(new TermQuery(new Term("book", bookName)), BooleanClause.Occur.FILTER)
.build();
TopDocs td = searcher.search(filtered, 10);
assertScoreDocsEquals(td.scoreDocs, tg.groups[i].scoreDocs);
}
shard.close();
}
public void testTopLevelSort() throws IOException {
@ -88,7 +87,8 @@ public class TestBlockGrouping extends AbstractGroupingTestCase {
Query blockEndQuery = new TermQuery(new Term("blockEnd", "true"));
GroupingSearch grouper = new GroupingSearch(blockEndQuery);
grouper.setGroupDocsLimit(10);
grouper.setGroupSort(sort); // groups returned sorted by length, chapters within group sorted by relevancy
// groups returned sorted by length, chapters within group sorted by relevancy
grouper.setGroupSort(sort);
Query topLevel = new TermQuery(new Term("text", "grandmother"));
TopGroups<?> tg = grouper.search(searcher, topLevel, 0, 5);
@ -96,16 +96,17 @@ public class TestBlockGrouping extends AbstractGroupingTestCase {
// The sort value of the top doc in the top group should be the same as the sort value
// of the top result from the same search done with no grouping
TopDocs topDoc = searcher.search(topLevel, 1, sort);
assertEquals(((FieldDoc)topDoc.scoreDocs[0]).fields[0], tg.groups[0].groupSortValues[0]);
assertEquals(((FieldDoc) topDoc.scoreDocs[0]).fields[0], tg.groups[0].groupSortValues[0]);
for (int i = 0; i < tg.groups.length; i++) {
String bookName = searcher.doc(tg.groups[i].scoreDocs[0].doc).get("book");
// The contents of each group should be equal to the results of a search for
// that group alone, sorted by score
Query filtered = new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(new TermQuery(new Term("book", bookName)), BooleanClause.Occur.FILTER)
.build();
Query filtered =
new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(new TermQuery(new Term("book", bookName)), BooleanClause.Occur.FILTER)
.build();
TopDocs td = searcher.search(filtered, 10);
assertScoreDocsEquals(td.scoreDocs, tg.groups[i].scoreDocs);
if (i > 1) {
@ -114,7 +115,6 @@ public class TestBlockGrouping extends AbstractGroupingTestCase {
}
shard.close();
}
public void testWithinGroupSort() throws IOException {
@ -128,7 +128,8 @@ public class TestBlockGrouping extends AbstractGroupingTestCase {
Query blockEndQuery = new TermQuery(new Term("blockEnd", "true"));
GroupingSearch grouper = new GroupingSearch(blockEndQuery);
grouper.setGroupDocsLimit(10);
grouper.setSortWithinGroup(sort); // groups returned sorted by relevancy, chapters within group sorted by length
// groups returned sorted by relevancy, chapters within group sorted by length
grouper.setSortWithinGroup(sort);
Query topLevel = new TermQuery(new Term("text", "grandmother"));
TopGroups<?> tg = grouper.search(searcher, topLevel, 0, 5);
@ -136,16 +137,17 @@ public class TestBlockGrouping extends AbstractGroupingTestCase {
// We're sorting by score, so the score of the top group should be the same as the
// score of the top document from the same query with no grouping
TopDocs topDoc = searcher.search(topLevel, 1);
assertEquals(topDoc.scoreDocs[0].score, (float)tg.groups[0].groupSortValues[0], 0);
assertEquals(topDoc.scoreDocs[0].score, (float) tg.groups[0].groupSortValues[0], 0);
for (int i = 0; i < tg.groups.length; i++) {
String bookName = searcher.doc(tg.groups[i].scoreDocs[0].doc).get("book");
// The contents of each group should be equal to the results of a search for
// that group alone, sorted by length
Query filtered = new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(new TermQuery(new Term("book", bookName)), BooleanClause.Occur.FILTER)
.build();
Query filtered =
new BooleanQuery.Builder()
.add(topLevel, BooleanClause.Occur.MUST)
.add(new TermQuery(new Term("book", bookName)), BooleanClause.Occur.FILTER)
.build();
TopDocs td = searcher.search(filtered, 10, sort);
assertFieldDocsEquals(td.scoreDocs, tg.groups[i].scoreDocs);
// We're sorting by score, so the group sort value for each group should be a float,
@ -188,14 +190,15 @@ public class TestBlockGrouping extends AbstractGroupingTestCase {
return block;
}
private static final String[] TEXT = new String[]{
"It was the day my grandmother exploded",
"It was the best of times, it was the worst of times",
"It was a bright cold morning in April",
"It is a truth universally acknowledged",
"I have just returned from a visit to my landlord",
"I've been here and I've been there"
};
private static final String[] TEXT =
new String[] {
"It was the day my grandmother exploded",
"It was the best of times, it was the worst of times",
"It was a bright cold morning in April",
"It is a truth universally acknowledged",
"I have just returned from a visit to my landlord",
"I've been here and I've been there"
};
private static String randomText() {
StringBuilder sb = new StringBuilder(TEXT[random().nextInt(TEXT.length)]);
@ -209,7 +212,7 @@ public class TestBlockGrouping extends AbstractGroupingTestCase {
private void assertSortsBefore(GroupDocs<?> first, GroupDocs<?> second) {
Object[] groupSortValues = second.groupSortValues;
Object[] prevSortValues = first.groupSortValues;
assertTrue(((Long)prevSortValues[0]).compareTo((Long)groupSortValues[0]) <= 0);
assertTrue(((Long) prevSortValues[0]).compareTo((Long) groupSortValues[0]) <= 0);
}
protected static void assertFieldDocsEquals(ScoreDoc[] expected, ScoreDoc[] actual) {
@ -221,5 +224,4 @@ public class TestBlockGrouping extends AbstractGroupingTestCase {
assertArrayEquals(e.fields, a.fields);
}
}
}

View File

@ -29,7 +29,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
@ -51,18 +50,19 @@ import org.apache.lucene.util.mutable.MutableValueStr;
public class TestDistinctValuesCollector extends AbstractGroupingTestCase {
private final static NullComparator nullComparator = new NullComparator();
private static final NullComparator nullComparator = new NullComparator();
private static final String GROUP_FIELD = "author";
private static final String COUNT_FIELD = "publisher";
public void testSimple() throws Exception {
Random random = random();
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(
random,
dir,
newIndexWriterConfig(new MockAnalyzer(random)).setMergePolicy(newLogMergePolicy()));
RandomIndexWriter w =
new RandomIndexWriter(
random,
dir,
newIndexWriterConfig(new MockAnalyzer(random)).setMergePolicy(newLogMergePolicy()));
Document doc = new Document();
addField(doc, GROUP_FIELD, "1");
addField(doc, COUNT_FIELD, "1");
@ -120,27 +120,30 @@ public class TestDistinctValuesCollector extends AbstractGroupingTestCase {
IndexSearcher indexSearcher = newSearcher(w.getReader());
w.close();
Comparator<DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>>> cmp = (groupCount1, groupCount2) -> {
if (groupCount1.groupValue == null) {
if (groupCount2.groupValue == null) {
return 0;
}
return -1;
} else if (groupCount2.groupValue == null) {
return 1;
} else {
return groupCount1.groupValue.compareTo(groupCount2.groupValue);
}
};
Comparator<DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>>> cmp =
(groupCount1, groupCount2) -> {
if (groupCount1.groupValue == null) {
if (groupCount2.groupValue == null) {
return 0;
}
return -1;
} else if (groupCount2.groupValue == null) {
return 1;
} else {
return groupCount1.groupValue.compareTo(groupCount2.groupValue);
}
};
// === Search for content:random
FirstPassGroupingCollector<Comparable<Object>> firstCollector = createRandomFirstPassCollector(new Sort(), GROUP_FIELD, 10);
FirstPassGroupingCollector<Comparable<Object>> firstCollector =
createRandomFirstPassCollector(new Sort(), GROUP_FIELD, 10);
indexSearcher.search(new TermQuery(new Term("content", "random")), firstCollector);
DistinctValuesCollector<Comparable<Object>, Comparable<Object>> distinctValuesCollector
= createDistinctCountCollector(firstCollector, COUNT_FIELD);
DistinctValuesCollector<Comparable<Object>, Comparable<Object>> distinctValuesCollector =
createDistinctCountCollector(firstCollector, COUNT_FIELD);
indexSearcher.search(new TermQuery(new Term("content", "random")), distinctValuesCollector);
List<DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>>> gcs = distinctValuesCollector.getGroups();
List<DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>>> gcs =
distinctValuesCollector.getGroups();
Collections.sort(gcs, cmp);
assertEquals(4, gcs.size());
@ -193,7 +196,7 @@ public class TestDistinctValuesCollector extends AbstractGroupingTestCase {
assertEquals(1, countValues.size());
compare("1", countValues.get(0));
// === Search for content:blob
// === Search for content:blob
firstCollector = createRandomFirstPassCollector(new Sort(), GROUP_FIELD, 10);
indexSearcher.search(new TermQuery(new Term("content", "blob")), firstCollector);
distinctValuesCollector = createDistinctCountCollector(firstCollector, COUNT_FIELD);
@ -229,33 +232,40 @@ public class TestDistinctValuesCollector extends AbstractGroupingTestCase {
Sort groupSort = new Sort(new SortField("id", SortField.Type.STRING));
int topN = 1 + random.nextInt(10);
List<DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>>> expectedResult = createExpectedResult(context, term, groupSort, topN);
List<DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>>>
expectedResult = createExpectedResult(context, term, groupSort, topN);
FirstPassGroupingCollector<Comparable<Object>> firstCollector = createRandomFirstPassCollector(groupSort, GROUP_FIELD, topN);
FirstPassGroupingCollector<Comparable<Object>> firstCollector =
createRandomFirstPassCollector(groupSort, GROUP_FIELD, topN);
searcher.search(new TermQuery(new Term("content", term)), firstCollector);
DistinctValuesCollector<Comparable<Object>, Comparable<Object>> distinctValuesCollector
= createDistinctCountCollector(firstCollector, COUNT_FIELD);
DistinctValuesCollector<Comparable<Object>, Comparable<Object>> distinctValuesCollector =
createDistinctCountCollector(firstCollector, COUNT_FIELD);
searcher.search(new TermQuery(new Term("content", term)), distinctValuesCollector);
@SuppressWarnings("unchecked")
List<DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>>> actualResult = distinctValuesCollector.getGroups();
List<DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>>>
actualResult = distinctValuesCollector.getGroups();
if (VERBOSE) {
System.out.println("Index iter=" + indexIter);
System.out.println("Search iter=" + searchIter);
System.out.println("1st pass collector class name=" + firstCollector.getClass().getName());
System.out.println("2nd pass collector class name=" + distinctValuesCollector.getClass().getName());
System.out.println(
"1st pass collector class name=" + firstCollector.getClass().getName());
System.out.println(
"2nd pass collector class name=" + distinctValuesCollector.getClass().getName());
System.out.println("Search term=" + term);
System.out.println("1st pass groups=" + firstCollector.getTopGroups(0));
System.out.println("Expected:");
System.out.println("Expected:");
printGroups(expectedResult);
System.out.println("Actual:");
System.out.println("Actual:");
printGroups(actualResult);
}
assertEquals(expectedResult.size(), actualResult.size());
for (int i = 0; i < expectedResult.size(); i++) {
DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>> expected = expectedResult.get(i);
DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>> actual = actualResult.get(i);
DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>> expected =
expectedResult.get(i);
DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>> actual =
actualResult.get(i);
assertValues(expected.groupValue, actual.groupValue);
assertEquals(expected.uniqueValues.size(), actual.uniqueValues.size());
List<Comparable<?>> expectedUniqueValues = new ArrayList<>(expected.uniqueValues);
@ -272,16 +282,18 @@ public class TestDistinctValuesCollector extends AbstractGroupingTestCase {
}
}
private void printGroups(List<DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>>> results) {
for(int i=0;i<results.size();i++) {
DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>> group = results.get(i);
private void printGroups(
List<DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>>> results) {
for (int i = 0; i < results.size(); i++) {
DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>> group =
results.get(i);
Object gv = group.groupValue;
if (gv instanceof BytesRef) {
System.out.println(i + ": groupValue=" + ((BytesRef) gv).utf8ToString());
} else {
System.out.println(i + ": groupValue=" + gv);
}
for(Object o : group.uniqueValues) {
for (Object o : group.uniqueValues) {
if (o instanceof BytesRef) {
System.out.println(" " + ((BytesRef) o).utf8ToString());
} else {
@ -298,7 +310,7 @@ public class TestDistinctValuesCollector extends AbstractGroupingTestCase {
compare(((BytesRef) expected).utf8ToString(), actual);
}
}
private void compare(String expected, Object groupValue) {
if (BytesRef.class.isAssignableFrom(groupValue.getClass())) {
assertEquals(expected, ((BytesRef) groupValue).utf8ToString());
@ -338,13 +350,16 @@ public class TestDistinctValuesCollector extends AbstractGroupingTestCase {
doc.add(new SortedDocValuesField(field, new BytesRef(value)));
}
@SuppressWarnings({"unchecked","rawtypes"})
private <T extends Comparable<Object>, R extends Comparable<Object>> DistinctValuesCollector<T, R> createDistinctCountCollector(FirstPassGroupingCollector<T> firstPassGroupingCollector,
String countField) throws IOException {
@SuppressWarnings({"unchecked", "rawtypes"})
private <T extends Comparable<Object>, R extends Comparable<Object>>
DistinctValuesCollector<T, R> createDistinctCountCollector(
FirstPassGroupingCollector<T> firstPassGroupingCollector, String countField)
throws IOException {
Collection<SearchGroup<T>> searchGroups = firstPassGroupingCollector.getTopGroups(0);
GroupSelector<T> selector = firstPassGroupingCollector.getGroupSelector();
if (ValueSourceGroupSelector.class.isAssignableFrom(selector.getClass())) {
GroupSelector gs = new ValueSourceGroupSelector(new BytesRefFieldSource(countField), new HashMap<>());
GroupSelector gs =
new ValueSourceGroupSelector(new BytesRefFieldSource(countField), new HashMap<>());
return new DistinctValuesCollector<>(selector, searchGroups, gs);
} else {
GroupSelector ts = new TermGroupSelector(countField);
@ -352,18 +367,26 @@ public class TestDistinctValuesCollector extends AbstractGroupingTestCase {
}
}
@SuppressWarnings({"unchecked","rawtypes"})
private <T> FirstPassGroupingCollector<T> createRandomFirstPassCollector(Sort groupSort, String groupField, int topNGroups) throws IOException {
@SuppressWarnings({"unchecked", "rawtypes"})
private <T> FirstPassGroupingCollector<T> createRandomFirstPassCollector(
Sort groupSort, String groupField, int topNGroups) throws IOException {
Random random = random();
if (random.nextBoolean()) {
return (FirstPassGroupingCollector<T>) new FirstPassGroupingCollector<>(new ValueSourceGroupSelector(new BytesRefFieldSource(groupField), new HashMap<>()), groupSort, topNGroups);
return (FirstPassGroupingCollector<T>)
new FirstPassGroupingCollector<>(
new ValueSourceGroupSelector(new BytesRefFieldSource(groupField), new HashMap<>()),
groupSort,
topNGroups);
} else {
return (FirstPassGroupingCollector<T>) new FirstPassGroupingCollector<>(new TermGroupSelector(groupField), groupSort, topNGroups);
return (FirstPassGroupingCollector<T>)
new FirstPassGroupingCollector<>(
new TermGroupSelector(groupField), groupSort, topNGroups);
}
}
@SuppressWarnings({"unchecked","rawtypes"})
private List<DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>>> createExpectedResult(IndexContext context, String term, Sort groupSort, int topN) {
@SuppressWarnings({"unchecked", "rawtypes"})
private List<DistinctValuesCollector.GroupCount<Comparable<Object>, Comparable<Object>>>
createExpectedResult(IndexContext context, String term, Sort groupSort, int topN) {
List result = new ArrayList();
Map<String, Set<String>> groupCounts = context.searchTermToGroupCounts.get(term);
int i = 0;
@ -375,7 +398,9 @@ public class TestDistinctValuesCollector extends AbstractGroupingTestCase {
for (String val : groupCounts.get(group)) {
uniqueValues.add(val != null ? new BytesRef(val) : null);
}
result.add(new DistinctValuesCollector.GroupCount(group != null ? new BytesRef(group) : null, uniqueValues));
result.add(
new DistinctValuesCollector.GroupCount(
group != null ? new BytesRef(group) : null, uniqueValues));
}
return result;
}
@ -384,11 +409,11 @@ public class TestDistinctValuesCollector extends AbstractGroupingTestCase {
Random random = random();
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(
random,
dir,
newIndexWriterConfig(new MockAnalyzer(random)).setMergePolicy(newLogMergePolicy())
);
RandomIndexWriter w =
new RandomIndexWriter(
random,
dir,
newIndexWriterConfig(new MockAnalyzer(random)).setMergePolicy(newLogMergePolicy()));
int numDocs = 86 + random.nextInt(1087) * RANDOM_MULTIPLIER;
String[] groupValues = new String[numDocs / 5];
@ -399,12 +424,14 @@ public class TestDistinctValuesCollector extends AbstractGroupingTestCase {
for (int i = 0; i < countValues.length; i++) {
countValues[i] = generateRandomNonEmptyString();
}
List<String> contentStrings = new ArrayList<>();
Map<String, Map<String, Set<String>>> searchTermToGroupCounts = new HashMap<>();
for (int i = 1; i <= numDocs; i++) {
String groupValue = random.nextInt(23) == 14 ? null : groupValues[random.nextInt(groupValues.length)];
String countValue = random.nextInt(21) == 13 ? null : countValues[random.nextInt(countValues.length)];
String groupValue =
random.nextInt(23) == 14 ? null : groupValues[random.nextInt(groupValues.length)];
String countValue =
random.nextInt(21) == 13 ? null : countValues[random.nextInt(countValues.length)];
String content = "random" + random.nextInt(numDocs / 20);
Map<String, Set<String>> groupToCounts = searchTermToGroupCounts.get(content);
if (groupToCounts == null) {
@ -434,14 +461,28 @@ public class TestDistinctValuesCollector extends AbstractGroupingTestCase {
DirectoryReader reader = w.getReader();
if (VERBOSE) {
for(int docID=0;docID<reader.maxDoc();docID++) {
for (int docID = 0; docID < reader.maxDoc(); docID++) {
Document doc = reader.document(docID);
System.out.println("docID=" + docID + " id=" + doc.get("id") + " content=" + doc.get("content") + " author=" + doc.get("author") + " publisher=" + doc.get("publisher"));
System.out.println(
"docID="
+ docID
+ " id="
+ doc.get("id")
+ " content="
+ doc.get("content")
+ " author="
+ doc.get("author")
+ " publisher="
+ doc.get("publisher"));
}
}
w.close();
return new IndexContext(dir, reader, searchTermToGroupCounts, contentStrings.toArray(new String[contentStrings.size()]));
return new IndexContext(
dir,
reader,
searchTermToGroupCounts,
contentStrings.toArray(new String[contentStrings.size()]));
}
private static class IndexContext {
@ -451,8 +492,11 @@ public class TestDistinctValuesCollector extends AbstractGroupingTestCase {
final Map<String, Map<String, Set<String>>> searchTermToGroupCounts;
final String[] contentStrings;
IndexContext(Directory directory, DirectoryReader indexReader,
Map<String, Map<String, Set<String>>> searchTermToGroupCounts, String[] contentStrings) {
IndexContext(
Directory directory,
DirectoryReader indexReader,
Map<String, Map<String, Set<String>>> searchTermToGroupCounts,
String[] contentStrings) {
this.directory = directory;
this.indexReader = indexReader;
this.searchTermToGroupCounts = searchTermToGroupCounts;
@ -463,7 +507,7 @@ public class TestDistinctValuesCollector extends AbstractGroupingTestCase {
private static class NullComparator implements Comparator<Comparable<?>> {
@Override
@SuppressWarnings({"unchecked","rawtypes"})
@SuppressWarnings({"unchecked", "rawtypes"})
public int compare(Comparable a, Comparable b) {
if (a == b) {
return 0;
@ -475,7 +519,5 @@ public class TestDistinctValuesCollector extends AbstractGroupingTestCase {
return a.compareTo(b);
}
}
}
}

View File

@ -33,7 +33,5 @@ public class TestDoubleRangeFactory extends LuceneTestCase {
assertEquals(new DoubleRange(30, 40), factory.getRange(35, scratch));
assertEquals(new DoubleRange(50, Double.MAX_VALUE), factory.getRange(50, scratch));
assertEquals(new DoubleRange(50, Double.MAX_VALUE), factory.getRange(500, scratch));
}
}

View File

@ -32,7 +32,7 @@ public class TestDoubleRangeGroupSelector extends BaseGroupSelectorTestCase<Doub
@Override
protected void addGroupField(Document document, int id) {
if (rarely()) {
return; // missing value
return; // missing value
}
// numbers between 0 and 1000, groups are 100 wide from 100 to 900
double value = random().nextDouble() * 1000;
@ -42,8 +42,8 @@ public class TestDoubleRangeGroupSelector extends BaseGroupSelectorTestCase<Doub
@Override
protected GroupSelector<DoubleRange> getGroupSelector() {
return new DoubleRangeGroupSelector(DoubleValuesSource.fromDoubleField("double"),
new DoubleRangeFactory(100, 100, 900));
return new DoubleRangeGroupSelector(
DoubleValuesSource.fromDoubleField("double"), new DoubleRangeFactory(100, 100, 900));
}
@Override

View File

@ -29,7 +29,6 @@ import java.util.NavigableSet;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
@ -48,7 +47,6 @@ import org.apache.lucene.store.Directory;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.TestUtil;
public class TestGroupFacetCollector extends AbstractGroupingTestCase {
public void testSimple() throws Exception {
@ -57,10 +55,11 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
customType.setStored(true);
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(
random(),
dir,
newIndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(newLogMergePolicy()));
RandomIndexWriter w =
new RandomIndexWriter(
random(),
dir,
newIndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(newLogMergePolicy()));
boolean useDv = true;
// 0
@ -104,18 +103,19 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
List<TermGroupFacetCollector.FacetEntry> entries;
GroupFacetCollector groupedAirportFacetCollector;
TermGroupFacetCollector.GroupedFacetResult airportResult;
for (int limit : new int[] { 2, 10, 100, Integer.MAX_VALUE }) {
for (int limit : new int[] {2, 10, 100, Integer.MAX_VALUE}) {
// any of these limits is plenty for the data we have
groupedAirportFacetCollector = createRandomCollector
(useDv ? "hotel_dv" : "hotel",
useDv ? "airport_dv" : "airport", null, false);
groupedAirportFacetCollector =
createRandomCollector(
useDv ? "hotel_dv" : "hotel", useDv ? "airport_dv" : "airport", null, false);
indexSearcher.search(new MatchAllDocsQuery(), groupedAirportFacetCollector);
int maxOffset = 5;
airportResult = groupedAirportFacetCollector.mergeSegmentResults
(Integer.MAX_VALUE == limit ? limit : maxOffset + limit, 0, false);
airportResult =
groupedAirportFacetCollector.mergeSegmentResults(
Integer.MAX_VALUE == limit ? limit : maxOffset + limit, 0, false);
assertEquals(3, airportResult.getTotalCount());
assertEquals(0, airportResult.getTotalMissingCount());
@ -135,9 +135,12 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
assertEquals(1, entries.get(0).getCount());
}
GroupFacetCollector groupedDurationFacetCollector = createRandomCollector(useDv ? "hotel_dv" : "hotel", useDv ? "duration_dv" : "duration", null, false);
GroupFacetCollector groupedDurationFacetCollector =
createRandomCollector(
useDv ? "hotel_dv" : "hotel", useDv ? "duration_dv" : "duration", null, false);
indexSearcher.search(new MatchAllDocsQuery(), groupedDurationFacetCollector);
TermGroupFacetCollector.GroupedFacetResult durationResult = groupedDurationFacetCollector.mergeSegmentResults(10, 0, false);
TermGroupFacetCollector.GroupedFacetResult durationResult =
groupedDurationFacetCollector.mergeSegmentResults(10, 0, false);
assertEquals(4, durationResult.getTotalCount());
assertEquals(0, durationResult.getTotalMissingCount());
@ -181,7 +184,9 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
indexSearcher.getIndexReader().close();
indexSearcher = newSearcher(w.getReader());
groupedAirportFacetCollector = createRandomCollector(useDv ? "hotel_dv" : "hotel", useDv ? "airport_dv" : "airport", null, !useDv);
groupedAirportFacetCollector =
createRandomCollector(
useDv ? "hotel_dv" : "hotel", useDv ? "airport_dv" : "airport", null, !useDv);
indexSearcher.search(new MatchAllDocsQuery(), groupedAirportFacetCollector);
airportResult = groupedAirportFacetCollector.mergeSegmentResults(3, 0, true);
entries = airportResult.getFacetEntries(1, 2);
@ -202,7 +207,9 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
assertEquals(1, entries.get(1).getCount());
}
groupedDurationFacetCollector = createRandomCollector(useDv ? "hotel_dv" : "hotel", useDv ? "duration_dv" : "duration", null, false);
groupedDurationFacetCollector =
createRandomCollector(
useDv ? "hotel_dv" : "hotel", useDv ? "duration_dv" : "duration", null, false);
indexSearcher.search(new MatchAllDocsQuery(), groupedDurationFacetCollector);
durationResult = groupedDurationFacetCollector.mergeSegmentResults(10, 2, true);
assertEquals(5, durationResult.getTotalCount());
@ -229,7 +236,9 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
indexSearcher.getIndexReader().close();
indexSearcher = newSearcher(w.getReader());
groupedAirportFacetCollector = createRandomCollector(useDv ? "hotel_dv" : "hotel", useDv ? "airport_dv" : "airport", null, false);
groupedAirportFacetCollector =
createRandomCollector(
useDv ? "hotel_dv" : "hotel", useDv ? "airport_dv" : "airport", null, false);
indexSearcher.search(new MatchAllDocsQuery(), groupedAirportFacetCollector);
airportResult = groupedAirportFacetCollector.mergeSegmentResults(10, 0, false);
entries = airportResult.getFacetEntries(0, 10);
@ -257,7 +266,9 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
assertEquals(2, entries.get(2).getCount());
}
groupedDurationFacetCollector = createRandomCollector(useDv ? "hotel_dv" : "hotel", useDv ? "duration_dv" : "duration", "1", false);
groupedDurationFacetCollector =
createRandomCollector(
useDv ? "hotel_dv" : "hotel", useDv ? "duration_dv" : "duration", "1", false);
indexSearcher.search(new MatchAllDocsQuery(), groupedDurationFacetCollector);
durationResult = groupedDurationFacetCollector.mergeSegmentResults(10, 0, true);
assertEquals(5, durationResult.getTotalCount());
@ -281,10 +292,12 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
customType.setStored(true);
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(
random(),
dir,
newIndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(NoMergePolicy.INSTANCE));
RandomIndexWriter w =
new RandomIndexWriter(
random(),
dir,
newIndexWriterConfig(new MockAnalyzer(random()))
.setMergePolicy(NoMergePolicy.INSTANCE));
boolean useDv = true;
// Cannot assert this since we use NoMergePolicy:
@ -343,9 +356,11 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
w.close();
IndexSearcher indexSearcher = newSearcher(DirectoryReader.open(dir));
GroupFacetCollector groupedAirportFacetCollector = createRandomCollector(groupField + "_dv", "airport", null, true);
GroupFacetCollector groupedAirportFacetCollector =
createRandomCollector(groupField + "_dv", "airport", null, true);
indexSearcher.search(new MatchAllDocsQuery(), groupedAirportFacetCollector);
TermGroupFacetCollector.GroupedFacetResult airportResult = groupedAirportFacetCollector.mergeSegmentResults(10, 0, false);
TermGroupFacetCollector.GroupedFacetResult airportResult =
groupedAirportFacetCollector.mergeSegmentResults(10, 0, false);
assertEquals(3, airportResult.getTotalCount());
assertEquals(1, airportResult.getTotalMissingCount());
@ -385,9 +400,11 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
int limit = random.nextInt(context.facetValues.size());
int offset = random.nextInt(context.facetValues.size() - limit);
int size = offset + limit;
int minCount = random.nextBoolean() ? 0 : random.nextInt(1 + context.facetWithMostGroups / 10);
int minCount =
random.nextBoolean() ? 0 : random.nextInt(1 + context.facetWithMostGroups / 10);
boolean orderByCount = random.nextBoolean();
String randomStr = getFromSet(context.facetValues, random.nextInt(context.facetValues.size()));
String randomStr =
getFromSet(context.facetValues, random.nextInt(context.facetValues.size()));
final String facetPrefix;
if (randomStr == null) {
facetPrefix = null;
@ -402,13 +419,19 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
}
}
GroupedFacetResult expectedFacetResult = createExpectedFacetResult(searchTerm, context, offset, limit, minCount, orderByCount, facetPrefix);
GroupFacetCollector groupFacetCollector = createRandomCollector("group", "facet", facetPrefix, multipleFacetsPerDocument);
GroupedFacetResult expectedFacetResult =
createExpectedFacetResult(
searchTerm, context, offset, limit, minCount, orderByCount, facetPrefix);
GroupFacetCollector groupFacetCollector =
createRandomCollector("group", "facet", facetPrefix, multipleFacetsPerDocument);
searcher.search(new TermQuery(new Term("content", searchTerm)), groupFacetCollector);
TermGroupFacetCollector.GroupedFacetResult actualFacetResult = groupFacetCollector.mergeSegmentResults(size, minCount, orderByCount);
TermGroupFacetCollector.GroupedFacetResult actualFacetResult =
groupFacetCollector.mergeSegmentResults(size, minCount, orderByCount);
List<TermGroupFacetCollector.FacetEntry> expectedFacetEntries = expectedFacetResult.getFacetEntries();
List<TermGroupFacetCollector.FacetEntry> actualFacetEntries = actualFacetResult.getFacetEntries(offset, limit);
List<TermGroupFacetCollector.FacetEntry> expectedFacetEntries =
expectedFacetResult.getFacetEntries();
List<TermGroupFacetCollector.FacetEntry> actualFacetEntries =
actualFacetResult.getFacetEntries(offset, limit);
if (VERBOSE) {
System.out.println("Collector: " + groupFacetCollector.getClass().getSimpleName());
@ -431,11 +454,12 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
int counter = 0;
for (TermGroupFacetCollector.FacetEntry expectedFacetEntry : expectedFacetEntries) {
System.out.println(
String.format(Locale.ROOT,
String.format(
Locale.ROOT,
"%d. Expected facet value %s with count %d",
counter++, expectedFacetEntry.getValue().utf8ToString(), expectedFacetEntry.getCount()
)
);
counter++,
expectedFacetEntry.getValue().utf8ToString(),
expectedFacetEntry.getCount()));
}
System.out.println("\n=== Actual: \n");
@ -444,23 +468,42 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
counter = 0;
for (TermGroupFacetCollector.FacetEntry actualFacetEntry : actualFacetEntries) {
System.out.println(
String.format(Locale.ROOT,
String.format(
Locale.ROOT,
"%d. Actual facet value %s with count %d",
counter++, actualFacetEntry.getValue().utf8ToString(), actualFacetEntry.getCount()
)
);
counter++,
actualFacetEntry.getValue().utf8ToString(),
actualFacetEntry.getCount()));
}
System.out.println("\n===================================================================================");
System.out.println(
"\n===================================================================================");
}
assertEquals(expectedFacetResult.getTotalCount(), actualFacetResult.getTotalCount());
assertEquals(expectedFacetResult.getTotalMissingCount(), actualFacetResult.getTotalMissingCount());
assertEquals(
expectedFacetResult.getTotalMissingCount(), actualFacetResult.getTotalMissingCount());
assertEquals(expectedFacetEntries.size(), actualFacetEntries.size());
for (int i = 0; i < expectedFacetEntries.size(); i++) {
TermGroupFacetCollector.FacetEntry expectedFacetEntry = expectedFacetEntries.get(i);
TermGroupFacetCollector.FacetEntry actualFacetEntry = actualFacetEntries.get(i);
assertEquals("i=" + i + ": " + expectedFacetEntry.getValue().utf8ToString() + " != " + actualFacetEntry.getValue().utf8ToString(), expectedFacetEntry.getValue(), actualFacetEntry.getValue());
assertEquals("i=" + i + ": " + expectedFacetEntry.getCount() + " != " + actualFacetEntry.getCount(), expectedFacetEntry.getCount(), actualFacetEntry.getCount());
assertEquals(
"i="
+ i
+ ": "
+ expectedFacetEntry.getValue().utf8ToString()
+ " != "
+ actualFacetEntry.getValue().utf8ToString(),
expectedFacetEntry.getValue(),
actualFacetEntry.getValue());
assertEquals(
"i="
+ i
+ ": "
+ expectedFacetEntry.getCount()
+ " != "
+ actualFacetEntry.getCount(),
expectedFacetEntry.getCount(),
actualFacetEntry.getCount());
}
}
@ -469,7 +512,8 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
}
}
private IndexContext createIndexContext(boolean multipleFacetValuesPerDocument) throws IOException {
private IndexContext createIndexContext(boolean multipleFacetValuesPerDocument)
throws IOException {
final Random random = random();
final int numDocs = TestUtil.nextInt(random, 138, 1145) * RANDOM_MULTIPLIER;
final int numGroups = TestUtil.nextInt(random, 1, numDocs / 4);
@ -499,11 +543,8 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
}
Directory dir = newDirectory();
RandomIndexWriter writer = new RandomIndexWriter(
random,
dir,
newIndexWriterConfig(new MockAnalyzer(random))
);
RandomIndexWriter writer =
new RandomIndexWriter(random, dir, newIndexWriterConfig(new MockAnalyzer(random)));
Document doc = new Document();
Document docNoGroup = new Document();
Document docNoFacet = new Document();
@ -524,7 +565,8 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
doc.add(facetFields[1]);
docNoGroup.add(facetFields[1]);
} else {
facetFields = multipleFacetValuesPerDocument ? new Field[2 + random.nextInt(6)] : new Field[1];
facetFields =
multipleFacetValuesPerDocument ? new Field[2 + random.nextInt(6)] : new Field[1];
for (int i = 0; i < facetFields.length; i++) {
facetFields[i] = new SortedSetDocValuesField("facet", new BytesRef());
doc.add(facetFields[i]);
@ -537,22 +579,23 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
docNoFacet.add(content);
docNoGroupNoFacet.add(content);
NavigableSet<String> uniqueFacetValues = new TreeSet<>(new Comparator<String>() {
NavigableSet<String> uniqueFacetValues =
new TreeSet<>(
new Comparator<String>() {
@Override
public int compare(String a, String b) {
if (a == b) {
return 0;
} else if (a == null) {
return -1;
} else if (b == null) {
return 1;
} else {
return a.compareTo(b);
}
}
});
@Override
public int compare(String a, String b) {
if (a == b) {
return 0;
} else if (a == null) {
return -1;
} else if (b == null) {
return 1;
} else {
return a.compareTo(b);
}
}
});
Map<String, Map<String, Set<String>>> searchTermToFacetToGroups = new HashMap<>();
int facetWithMostGroups = 0;
for (int i = 0; i < numDocs; i++) {
@ -604,7 +647,13 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
}
if (VERBOSE) {
System.out.println(" doc content=" + contentStr + " group=" + (groupValue == null ? "null" : groupValue) + " facetVals=" + facetVals);
System.out.println(
" doc content="
+ contentStr
+ " group="
+ (groupValue == null ? "null" : groupValue)
+ " facetVals="
+ facetVals);
}
if (groupValue != null) {
@ -630,10 +679,25 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
DirectoryReader reader = writer.getReader();
writer.close();
return new IndexContext(searchTermToFacetToGroups, reader, numDocs, dir, facetWithMostGroups, numGroups, contentBrs, uniqueFacetValues);
return new IndexContext(
searchTermToFacetToGroups,
reader,
numDocs,
dir,
facetWithMostGroups,
numGroups,
contentBrs,
uniqueFacetValues);
}
private GroupedFacetResult createExpectedFacetResult(String searchTerm, IndexContext context, int offset, int limit, int minCount, final boolean orderByCount, String facetPrefix) {
private GroupedFacetResult createExpectedFacetResult(
String searchTerm,
IndexContext context,
int offset,
int limit,
int minCount,
final boolean orderByCount,
String facetPrefix) {
Map<String, Set<String>> facetGroups = context.searchTermToFacetGroups.get(searchTerm);
if (facetGroups == null) {
facetGroups = new HashMap<>();
@ -676,20 +740,22 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
}
}
Collections.sort(entries, new Comparator<TermGroupFacetCollector.FacetEntry>() {
Collections.sort(
entries,
new Comparator<TermGroupFacetCollector.FacetEntry>() {
@Override
public int compare(TermGroupFacetCollector.FacetEntry a, TermGroupFacetCollector.FacetEntry b) {
if (orderByCount) {
int cmp = b.getCount() - a.getCount();
if (cmp != 0) {
return cmp;
@Override
public int compare(
TermGroupFacetCollector.FacetEntry a, TermGroupFacetCollector.FacetEntry b) {
if (orderByCount) {
int cmp = b.getCount() - a.getCount();
if (cmp != 0) {
return cmp;
}
}
return a.getValue().compareTo(b.getValue());
}
}
return a.getValue().compareTo(b.getValue());
}
});
});
int endOffset = offset + limit;
List<TermGroupFacetCollector.FacetEntry> entriesResult;
@ -703,9 +769,11 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
return new GroupedFacetResult(totalCount, totalMissCount, entriesResult);
}
private GroupFacetCollector createRandomCollector(String groupField, String facetField, String facetPrefix, boolean multipleFacetsPerDocument) {
private GroupFacetCollector createRandomCollector(
String groupField, String facetField, String facetPrefix, boolean multipleFacetsPerDocument) {
BytesRef facetPrefixBR = facetPrefix == null ? null : new BytesRef(facetPrefix);
return TermGroupFacetCollector.createTermGroupFacetCollector(groupField, facetField, multipleFacetsPerDocument, facetPrefixBR, random().nextInt(1024));
return TermGroupFacetCollector.createTermGroupFacetCollector(
groupField, facetField, multipleFacetsPerDocument, facetPrefixBR, random().nextInt(1024));
}
private String getFromSet(Set<String> set, int index) {
@ -730,8 +798,15 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
final int numGroups;
final String[] contentStrings;
public IndexContext(Map<String, Map<String, Set<String>>> searchTermToFacetGroups, DirectoryReader r,
int numDocs, Directory dir, int facetWithMostGroups, int numGroups, String[] contentStrings, NavigableSet<String> facetValues) {
public IndexContext(
Map<String, Map<String, Set<String>>> searchTermToFacetGroups,
DirectoryReader r,
int numDocs,
Directory dir,
int facetWithMostGroups,
int numGroups,
String[] contentStrings,
NavigableSet<String> facetValues) {
this.searchTermToFacetGroups = searchTermToFacetGroups;
this.indexReader = r;
this.numDocs = numDocs;
@ -749,7 +824,10 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
final int totalMissingCount;
final List<TermGroupFacetCollector.FacetEntry> facetEntries;
private GroupedFacetResult(int totalCount, int totalMissingCount, List<TermGroupFacetCollector.FacetEntry> facetEntries) {
private GroupedFacetResult(
int totalCount,
int totalMissingCount,
List<TermGroupFacetCollector.FacetEntry> facetEntries) {
this.totalCount = totalCount;
this.totalMissingCount = totalMissingCount;
this.facetEntries = facetEntries;
@ -767,5 +845,4 @@ public class TestGroupFacetCollector extends AbstractGroupingTestCase {
return facetEntries;
}
}
}

View File

@ -16,6 +16,9 @@
*/
package org.apache.lucene.search.grouping;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
@ -37,10 +40,6 @@ import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.mutable.MutableValueStr;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class TestGroupingSearch extends LuceneTestCase {
// Tests some very basic usages...
@ -52,10 +51,11 @@ public class TestGroupingSearch extends LuceneTestCase {
customType.setStored(true);
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(
random(),
dir,
newIndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(newLogMergePolicy()));
RandomIndexWriter w =
new RandomIndexWriter(
random(),
dir,
newIndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(newLogMergePolicy()));
boolean canUseIDV = true;
List<Document> documents = new ArrayList<>();
// 0
@ -122,7 +122,8 @@ public class TestGroupingSearch extends LuceneTestCase {
Sort groupSort = Sort.RELEVANCE;
GroupingSearch groupingSearch = createRandomGroupingSearch(groupField, groupSort, 5, canUseIDV);
TopGroups<?> groups = groupingSearch.search(indexSearcher, new TermQuery(new Term("content", "random")), 0, 10);
TopGroups<?> groups =
groupingSearch.search(indexSearcher, new TermQuery(new Term("content", "random")), 0, 10);
assertEquals(7, groups.totalHitCount);
assertEquals(7, groups.totalGroupedHitCount);
@ -160,13 +161,14 @@ public class TestGroupingSearch extends LuceneTestCase {
Query lastDocInBlock = new TermQuery(new Term("groupend", "x"));
groupingSearch = new GroupingSearch(lastDocInBlock);
groups = groupingSearch.search(indexSearcher, new TermQuery(new Term("content", "random")), 0, 10);
groups =
groupingSearch.search(indexSearcher, new TermQuery(new Term("content", "random")), 0, 10);
assertEquals(7, groups.totalHitCount);
assertEquals(7, groups.totalGroupedHitCount);
assertEquals(4, groups.totalGroupCount.longValue());
assertEquals(4, groups.groups.length);
indexSearcher.getIndexReader().close();
dir.close();
}
@ -201,7 +203,8 @@ public class TestGroupingSearch extends LuceneTestCase {
}
}
private GroupingSearch createRandomGroupingSearch(String groupField, Sort groupSort, int docsInGroup, boolean canUseIDV) {
private GroupingSearch createRandomGroupingSearch(
String groupField, Sort groupSort, int docsInGroup, boolean canUseIDV) {
GroupingSearch groupingSearch;
if (random().nextBoolean()) {
ValueSource vs = new BytesRefFieldSource(groupField);
@ -222,10 +225,11 @@ public class TestGroupingSearch extends LuceneTestCase {
public void testSetAllGroups() throws Exception {
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(
random(),
dir,
newIndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(newLogMergePolicy()));
RandomIndexWriter w =
new RandomIndexWriter(
random(),
dir,
newIndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(newLogMergePolicy()));
Document doc = new Document();
doc.add(newField("group", "foo", StringField.TYPE_NOT_STORED));
doc.add(new SortedDocValuesField("group", new BytesRef("foo")));
@ -238,7 +242,7 @@ public class TestGroupingSearch extends LuceneTestCase {
gs.setAllGroups(true);
TopGroups<?> groups = gs.search(indexSearcher, new TermQuery(new Term("group", "foo")), 0, 10);
assertEquals(1, groups.totalHitCount);
//assertEquals(1, groups.totalGroupCount.intValue());
// assertEquals(1, groups.totalGroupCount.intValue());
assertEquals(1, groups.totalGroupedHitCount);
assertEquals(1, gs.getAllMatchingGroups().size());
indexSearcher.getIndexReader().close();

View File

@ -33,7 +33,5 @@ public class TestLongRangeFactory extends LuceneTestCase {
assertEquals(new LongRange(30, 40), factory.getRange(35, scratch));
assertEquals(new LongRange(50, Long.MAX_VALUE), factory.getRange(50, scratch));
assertEquals(new LongRange(50, Long.MAX_VALUE), factory.getRange(500, scratch));
}
}

View File

@ -42,8 +42,8 @@ public class TestLongRangeGroupSelector extends BaseGroupSelectorTestCase<LongRa
@Override
protected GroupSelector<LongRange> getGroupSelector() {
return new LongRangeGroupSelector(LongValuesSource.fromLongField("long"),
new LongRangeFactory(100, 100, 900));
return new LongRangeGroupSelector(
LongValuesSource.fromLongField("long"), new LongRangeFactory(100, 100, 900));
}
@Override

View File

@ -35,7 +35,7 @@ public class TestTermGroupSelector extends BaseGroupSelectorTestCase<BytesRef> {
@Override
protected void addGroupField(Document document, int id) {
if (rarely()) {
return; // missing value
return; // missing value
}
String groupValue = "group" + random().nextInt(10);
document.add(new SortedDocValuesField("groupField", new BytesRef(groupValue)));

View File

@ -104,56 +104,87 @@ public class TestTopGroups extends LuceneTestCase {
final TopGroups<String> shard1TopGroups;
{
final GroupDocs<String> group1 = haveBlueWhale
? createSingletonGroupDocs(blueGroupValue, new Object[] { blueWhaleSize }, 1 /* docId */, blueWhaleScore, 0 /* shardIndex */)
: createEmptyGroupDocs(blueGroupValue, new Object[] { blueWhaleSize });
final GroupDocs<String> group1 =
haveBlueWhale
? createSingletonGroupDocs(
blueGroupValue,
new Object[] {blueWhaleSize},
1 /* docId */,
blueWhaleScore,
0 /* shardIndex */)
: createEmptyGroupDocs(blueGroupValue, new Object[] {blueWhaleSize});
final GroupDocs<String> group2 = haveRedAnt
? createSingletonGroupDocs(redGroupValue, new Object[] { redAntSize }, 2 /* docId */, redAntScore, 0 /* shardIndex */)
: createEmptyGroupDocs(redGroupValue, new Object[] { redAntSize });
final GroupDocs<String> group2 =
haveRedAnt
? createSingletonGroupDocs(
redGroupValue,
new Object[] {redAntSize},
2 /* docId */,
redAntScore,
0 /* shardIndex */)
: createEmptyGroupDocs(redGroupValue, new Object[] {redAntSize});
shard1TopGroups = new TopGroups<String>(
sort.getSort() /* groupSort */,
sort.getSort() /* withinGroupSort */,
group1.scoreDocs.length + group2.scoreDocs.length /* totalHitCount */,
group1.scoreDocs.length + group2.scoreDocs.length /* totalGroupedHitCount */,
combineGroupDocs(group1, group2) /* groups */,
(haveBlueWhale ? blueWhaleScore : (haveRedAnt ? redAntScore : Float.NaN)) /* maxScore */);
shard1TopGroups =
new TopGroups<String>(
sort.getSort() /* groupSort */,
sort.getSort() /* withinGroupSort */,
group1.scoreDocs.length + group2.scoreDocs.length /* totalHitCount */,
group1.scoreDocs.length + group2.scoreDocs.length /* totalGroupedHitCount */,
combineGroupDocs(group1, group2) /* groups */,
(haveBlueWhale
? blueWhaleScore
: (haveRedAnt ? redAntScore : Float.NaN)) /* maxScore */);
}
final TopGroups<String> shard2TopGroups;
{
final GroupDocs<String> group1 = haveBlueDragonfly
? createSingletonGroupDocs(blueGroupValue, new Object[] { blueDragonflySize }, 3 /* docId */, blueDragonflyScore, 1 /* shardIndex */)
: createEmptyGroupDocs(blueGroupValue, new Object[] { blueDragonflySize });
final GroupDocs<String> group1 =
haveBlueDragonfly
? createSingletonGroupDocs(
blueGroupValue,
new Object[] {blueDragonflySize},
3 /* docId */,
blueDragonflyScore,
1 /* shardIndex */)
: createEmptyGroupDocs(blueGroupValue, new Object[] {blueDragonflySize});
final GroupDocs<String> group2 = haveRedSquirrel
? createSingletonGroupDocs(redGroupValue, new Object[] { redSquirrelSize }, 4 /* docId */, redSquirrelScore, 1 /* shardIndex */)
: createEmptyGroupDocs(redGroupValue, new Object[] { redSquirrelSize });
final GroupDocs<String> group2 =
haveRedSquirrel
? createSingletonGroupDocs(
redGroupValue,
new Object[] {redSquirrelSize},
4 /* docId */,
redSquirrelScore,
1 /* shardIndex */)
: createEmptyGroupDocs(redGroupValue, new Object[] {redSquirrelSize});
shard2TopGroups = new TopGroups<String>(
sort.getSort() /* groupSort */,
sort.getSort() /* withinGroupSort */,
group1.scoreDocs.length + group2.scoreDocs.length /* totalHitCount */,
group1.scoreDocs.length + group2.scoreDocs.length /* totalGroupedHitCount */,
combineGroupDocs(group1, group2) /* groups */,
(haveRedSquirrel ? redSquirrelScore : (haveBlueDragonfly ? blueDragonflyScore : Float.NaN)) /* maxScore */);
shard2TopGroups =
new TopGroups<String>(
sort.getSort() /* groupSort */,
sort.getSort() /* withinGroupSort */,
group1.scoreDocs.length + group2.scoreDocs.length /* totalHitCount */,
group1.scoreDocs.length + group2.scoreDocs.length /* totalGroupedHitCount */,
combineGroupDocs(group1, group2) /* groups */,
(haveRedSquirrel
? redSquirrelScore
: (haveBlueDragonfly ? blueDragonflyScore : Float.NaN)) /* maxScore */);
}
final TopGroups<String> mergedTopGroups = TopGroups.<String>merge(
combineTopGroups(shard1TopGroups, shard2TopGroups),
sort /* groupSort */,
sort /* docSort */,
0 /* docOffset */,
2 /* docTopN */,
TopGroups.ScoreMergeMode.None);
final TopGroups<String> mergedTopGroups =
TopGroups.<String>merge(
combineTopGroups(shard1TopGroups, shard2TopGroups),
sort /* groupSort */,
sort /* docSort */,
0 /* docOffset */,
2 /* docTopN */,
TopGroups.ScoreMergeMode.None);
assertNotNull(mergedTopGroups);
final int expectedCount =
(haveBlueWhale ? 1 : 0) +
(haveRedAnt ? 1 : 0) +
(haveBlueDragonfly ? 1 : 0) +
(haveRedSquirrel ? 1 : 0);
(haveBlueWhale ? 1 : 0)
+ (haveRedAnt ? 1 : 0)
+ (haveBlueDragonfly ? 1 : 0)
+ (haveRedSquirrel ? 1 : 0);
assertEquals(expectedCount, mergedTopGroups.totalHitCount);
assertEquals(expectedCount, mergedTopGroups.totalGroupedHitCount);
@ -173,11 +204,13 @@ public class TestTopGroups extends LuceneTestCase {
}
final float expectedMaxScore =
(haveBlueWhale ? blueWhaleScore
: (haveRedSquirrel ? redSquirrelScore
: (haveBlueDragonfly ? blueDragonflyScore
: (haveRedAnt ? redAntScore
: Float.NaN))));
(haveBlueWhale
? blueWhaleScore
: (haveRedSquirrel
? redSquirrelScore
: (haveBlueDragonfly
? blueDragonflyScore
: (haveRedAnt ? redAntScore : Float.NaN))));
checkMaxScore(expectedMaxScore, mergedTopGroups.maxScore);
}
@ -191,41 +224,43 @@ public class TestTopGroups extends LuceneTestCase {
// helper methods
private static GroupDocs<String> createEmptyGroupDocs(String groupValue, Object[] groupSortValues) {
return new GroupDocs<String>(
private static GroupDocs<String> createEmptyGroupDocs(
String groupValue, Object[] groupSortValues) {
return new GroupDocs<String>(
Float.NaN /* score */,
Float.NaN /* maxScore */,
new TotalHits(0, TotalHits.Relation.EQUAL_TO),
new ScoreDoc[0],
groupValue,
groupSortValues);
}
}
private static GroupDocs<String> createSingletonGroupDocs(String groupValue, Object[] groupSortValues,
int docId, float docScore, int shardIndex) {
return new GroupDocs<String>(
private static GroupDocs<String> createSingletonGroupDocs(
String groupValue, Object[] groupSortValues, int docId, float docScore, int shardIndex) {
return new GroupDocs<String>(
Float.NaN /* score */,
docScore /* maxScore */,
new TotalHits(1, TotalHits.Relation.EQUAL_TO),
new ScoreDoc[] { new ScoreDoc(docId, docScore, shardIndex) },
new ScoreDoc[] {new ScoreDoc(docId, docScore, shardIndex)},
groupValue,
groupSortValues);
}
}
private static GroupDocs<String>[] combineGroupDocs(GroupDocs<String> group0, GroupDocs<String> group1) {
@SuppressWarnings({"unchecked","rawtypes"})
private static GroupDocs<String>[] combineGroupDocs(
GroupDocs<String> group0, GroupDocs<String> group1) {
@SuppressWarnings({"unchecked", "rawtypes"})
final GroupDocs<String>[] groups = new GroupDocs[2];
groups[0] = group0;
groups[1] = group1;
return groups;
}
private static TopGroups<String>[] combineTopGroups(TopGroups<String> group0, TopGroups<String> group1) {
@SuppressWarnings({"unchecked","rawtypes"})
private static TopGroups<String>[] combineTopGroups(
TopGroups<String> group0, TopGroups<String> group1) {
@SuppressWarnings({"unchecked", "rawtypes"})
final TopGroups<String>[] groups = new TopGroups[2];
groups[0] = group0;
groups[1] = group1;
return groups;
}
}

View File

@ -18,7 +18,6 @@
package org.apache.lucene.search.grouping;
import java.util.HashMap;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.SortedDocValuesField;

View File

@ -20,7 +20,6 @@ package org.apache.lucene.luke.app;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.luke.util.LoggerFactory;
@ -45,5 +44,4 @@ public abstract class AbstractHandler<T extends Observer> {
}
protected abstract void notifyOne(T observer);
}

View File

@ -19,7 +19,6 @@ package org.apache.lucene.luke.app;
import java.io.IOException;
import java.util.Objects;
import org.apache.lucene.luke.app.desktop.util.MessageUtils;
import org.apache.lucene.luke.models.LukeException;
import org.apache.lucene.luke.models.util.IndexUtils;
@ -60,7 +59,8 @@ public final class DirectoryHandler extends AbstractHandler<DirectoryObserver> {
try {
dir = IndexUtils.openDirectory(indexPath, dirImpl);
} catch (IOException e) {
throw new LukeException(MessageUtils.getLocalizedMessage("openindex.message.index_path_invalid", indexPath), e);
throw new LukeException(
MessageUtils.getLocalizedMessage("openindex.message.index_path_invalid", indexPath), e);
}
state = new LukeStateImpl();
@ -108,5 +108,4 @@ public final class DirectoryHandler extends AbstractHandler<DirectoryObserver> {
return dir;
}
}
}

View File

@ -23,5 +23,4 @@ public interface DirectoryObserver extends Observer {
void openDirectory(LukeState state);
void closeDirectory();
}

View File

@ -19,7 +19,6 @@ package org.apache.lucene.luke.app;
import java.lang.invoke.MethodHandles;
import java.util.Objects;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.luke.app.desktop.util.MessageUtils;
@ -57,7 +56,12 @@ public final class IndexHandler extends AbstractHandler<IndexObserver> {
open(indexPath, dirImpl, false, false, false);
}
public void open(String indexPath, String dirImpl, boolean readOnly, boolean useCompound, boolean keepAllCommits) {
public void open(
String indexPath,
String dirImpl,
boolean readOnly,
boolean useCompound,
boolean keepAllCommits) {
Objects.requireNonNull(indexPath);
if (indexOpened()) {
@ -69,7 +73,8 @@ public final class IndexHandler extends AbstractHandler<IndexObserver> {
reader = IndexUtils.openIndex(indexPath, dirImpl);
} catch (Exception e) {
log.error("Error opening index", e);
throw new LukeException(MessageUtils.getLocalizedMessage("openindex.message.index_path_invalid", indexPath), e);
throw new LukeException(
MessageUtils.getLocalizedMessage("openindex.message.index_path_invalid", indexPath), e);
}
state = new LukeStateImpl();
@ -96,7 +101,12 @@ public final class IndexHandler extends AbstractHandler<IndexObserver> {
public void reOpen() {
close();
open(state.getIndexPath(), state.getDirImpl(), state.readOnly(), state.useCompound(), state.keepAllCommits());
open(
state.getIndexPath(),
state.getDirImpl(),
state.readOnly(),
state.useCompound(),
state.keepAllCommits());
}
public LukeState getState() {

View File

@ -23,5 +23,4 @@ public interface IndexObserver extends Observer {
void openIndex(LukeState state);
void closeIndex();
}

View File

@ -21,9 +21,7 @@ import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.store.Directory;
/**
* Holder for current index/directory.
*/
/** Holder for current index/directory. */
public interface LukeState {
String getIndexPath();
@ -53,5 +51,4 @@ public interface LukeState {
default boolean hasDirectoryReader() {
return getIndexReader() instanceof DirectoryReader;
}
}

View File

@ -18,5 +18,4 @@
package org.apache.lucene.luke.app;
/** Marker interface for observers */
public interface Observer {
}
public interface Observer {}

View File

@ -17,13 +17,14 @@
package org.apache.lucene.luke.app.desktop;
import javax.swing.JFrame;
import javax.swing.UIManager;
import static org.apache.lucene.luke.app.desktop.util.ExceptionHandler.handle;
import java.awt.GraphicsEnvironment;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.nio.file.FileSystems;
import javax.swing.JFrame;
import javax.swing.UIManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.luke.app.desktop.components.LukeWindowProvider;
import org.apache.lucene.luke.app.desktop.components.dialog.menubar.OpenIndexDialogFactory;
@ -32,20 +33,22 @@ import org.apache.lucene.luke.app.desktop.util.FontUtils;
import org.apache.lucene.luke.app.desktop.util.MessageUtils;
import org.apache.lucene.luke.util.LoggerFactory;
import static org.apache.lucene.luke.app.desktop.util.ExceptionHandler.handle;
/** Entry class for desktop Luke */
public class LukeMain {
public static final String LOG_FILE = System.getProperty("user.home") +
FileSystems.getDefault().getSeparator() + ".luke.d" +
FileSystems.getDefault().getSeparator() + "luke.log";
public static final String LOG_FILE =
System.getProperty("user.home")
+ FileSystems.getDefault().getSeparator()
+ ".luke.d"
+ FileSystems.getDefault().getSeparator()
+ "luke.log";
static {
LoggerFactory.initGuiLogging(LOG_FILE);
}
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static JFrame frame;
public static JFrame getOwnerFrame() {
@ -55,9 +58,7 @@ public class LukeMain {
private static void createAndShowGUI() {
// uncaught error handler
MessageBroker messageBroker = MessageBroker.getInstance();
Thread.setDefaultUncaughtExceptionHandler((thread, cause) ->
handle(cause, messageBroker)
);
Thread.setDefaultUncaughtExceptionHandler((thread, cause) -> handle(cause, messageBroker));
try {
frame = new LukeWindowProvider().get();
@ -68,9 +69,12 @@ public class LukeMain {
// show open index dialog
OpenIndexDialogFactory openIndexDialogFactory = OpenIndexDialogFactory.getInstance();
new DialogOpener<>(openIndexDialogFactory).open(MessageUtils.getLocalizedMessage("openindex.dialog.title"), 600, 420,
(factory) -> {
});
new DialogOpener<>(openIndexDialogFactory)
.open(
MessageUtils.getLocalizedMessage("openindex.dialog.title"),
600,
420,
(factory) -> {});
} catch (IOException e) {
messageBroker.showUnknownErrorMessage();
log.error("Cannot initialize components.", e);
@ -79,7 +83,8 @@ public class LukeMain {
public static void main(String[] args) throws Exception {
String lookAndFeelClassName = UIManager.getSystemLookAndFeelClassName();
if (!lookAndFeelClassName.contains("AquaLookAndFeel") && !lookAndFeelClassName.contains("PlasticXPLookAndFeel")) {
if (!lookAndFeelClassName.contains("AquaLookAndFeel")
&& !lookAndFeelClassName.contains("PlasticXPLookAndFeel")) {
// may be running on linux platform
lookAndFeelClassName = "javax.swing.plaf.metal.MetalLookAndFeel";
}
@ -89,6 +94,5 @@ public class LukeMain {
genv.registerFont(FontUtils.createElegantIconFont());
javax.swing.SwingUtilities.invokeLater(LukeMain::createAndShowGUI);
}
}

View File

@ -61,5 +61,4 @@ public class MessageBroker {
void clearStatusMessage();
}
}

View File

@ -38,7 +38,13 @@ public interface Preferences {
boolean isKeepAllCommits();
void setIndexOpenerPrefs(boolean readOnly, String dirImpl, boolean noReader, boolean useCompound, boolean keepAllCommits) throws IOException;
void setIndexOpenerPrefs(
boolean readOnly,
String dirImpl,
boolean noReader,
boolean useCompound,
boolean keepAllCommits)
throws IOException;
ColorTheme getColorTheme();

View File

@ -24,11 +24,10 @@ public class PreferencesFactory {
private static Preferences prefs;
public synchronized static Preferences getInstance() throws IOException {
public static synchronized Preferences getInstance() throws IOException {
if (prefs == null) {
prefs = new PreferencesImpl();
}
return prefs;
}
}

View File

@ -23,7 +23,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.luke.app.desktop.util.inifile.IniFile;
import org.apache.lucene.luke.app.desktop.util.inifile.SimpleIniFile;
import org.apache.lucene.store.FSDirectory;
@ -31,14 +30,14 @@ import org.apache.lucene.store.FSDirectory;
/** Default implementation of {@link Preferences} */
public final class PreferencesImpl implements Preferences {
private static final String CONFIG_DIR = System.getProperty("user.home") + FileSystems.getDefault().getSeparator() + ".luke.d";
private static final String CONFIG_DIR =
System.getProperty("user.home") + FileSystems.getDefault().getSeparator() + ".luke.d";
private static final String INIT_FILE = "luke.ini";
private static final String HISTORY_FILE = "history";
private static final int MAX_HISTORY = 10;
private final IniFile ini = new SimpleIniFile();
private final List<String> history = new ArrayList<>();
public PreferencesImpl() throws IOException {
@ -61,7 +60,6 @@ public final class PreferencesImpl implements Preferences {
List<String> allHistory = Files.readAllLines(histFile);
history.addAll(allHistory.subList(0, Math.min(MAX_HISTORY, allHistory.size())));
}
}
public List<String> getHistory() {
@ -128,7 +126,13 @@ public final class PreferencesImpl implements Preferences {
}
@Override
public void setIndexOpenerPrefs(boolean readOnly, String dirImpl, boolean noReader, boolean useCompound, boolean keepAllCommits) throws IOException {
public void setIndexOpenerPrefs(
boolean readOnly,
String dirImpl,
boolean noReader,
boolean useCompound,
boolean keepAllCommits)
throws IOException {
ini.put("opener", "readOnly", readOnly);
ini.put("opener", "dirImpl", dirImpl);
ini.put("opener", "noReader", noReader);

View File

@ -17,16 +17,6 @@
package org.apache.lucene.luke.app.desktop.components;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
@ -39,7 +29,16 @@ import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.custom.CustomAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
@ -126,16 +125,21 @@ public final class AnalysisPanelProvider implements AnalysisTabOperator {
operatorRegistry.register(AnalysisTabOperator.class, this);
operatorRegistry.get(PresetAnalyzerPanelOperator.class).ifPresent(operator -> {
// Scanning all Analyzer types will take time...
ExecutorService executorService =
Executors.newFixedThreadPool(1, new NamedThreadFactory("load-preset-analyzer-types"));
executorService.execute(() -> {
operator.setPresetAnalyzers(analysisModel.getPresetAnalyzerTypes());
operator.setSelectedAnalyzer(analysisModel.currentAnalyzer().getClass());
});
executorService.shutdown();
});
operatorRegistry
.get(PresetAnalyzerPanelOperator.class)
.ifPresent(
operator -> {
// Scanning all Analyzer types will take time...
ExecutorService executorService =
Executors.newFixedThreadPool(
1, new NamedThreadFactory("load-preset-analyzer-types"));
executorService.execute(
() -> {
operator.setPresetAnalyzers(analysisModel.getPresetAnalyzerTypes());
operator.setSelectedAnalyzer(analysisModel.currentAnalyzer().getClass());
});
executorService.shutdown();
});
}
public JPanel get() {
@ -143,7 +147,8 @@ public final class AnalysisPanelProvider implements AnalysisTabOperator {
panel.setOpaque(false);
panel.setBorder(BorderFactory.createLineBorder(Color.gray));
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel());
JSplitPane splitPane =
new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel());
splitPane.setOpaque(false);
splitPane.setDividerLocation(320);
panel.add(splitPane);
@ -194,16 +199,18 @@ public final class AnalysisPanelProvider implements AnalysisTabOperator {
JPanel analyzerName = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 2));
analyzerName.setOpaque(false);
analyzerName.add(new JLabel(MessageUtils.getLocalizedMessage("analysis.label.selected_analyzer")));
analyzerName.add(
new JLabel(MessageUtils.getLocalizedMessage("analysis.label.selected_analyzer")));
analyzerNameLbl.setText(analysisModel.currentAnalyzer().getClass().getName());
analyzerName.add(analyzerNameLbl);
showChainLbl.setText(MessageUtils.getLocalizedMessage("analysis.label.show_chain"));
showChainLbl.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
listeners.showAnalysisChain(e);
}
});
showChainLbl.addMouseListener(
new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
listeners.showAnalysisChain(e);
}
});
showChainLbl.setVisible(analysisModel.currentAnalyzer() instanceof CustomAnalyzer);
analyzerName.add(FontUtils.toLinkText(showChainLbl));
inner1.add(analyzerName, BorderLayout.PAGE_START);
@ -217,8 +224,10 @@ public final class AnalysisPanelProvider implements AnalysisTabOperator {
inputArea.setText(MessageUtils.getLocalizedMessage("analysis.textarea.prompt"));
input.add(new JScrollPane(inputArea));
JButton executeBtn = new JButton(FontUtils.elegantIconHtml("&#xe007;",
MessageUtils.getLocalizedMessage("analysis.button.test")));
JButton executeBtn =
new JButton(
FontUtils.elegantIconHtml(
"&#xe007;", MessageUtils.getLocalizedMessage("analysis.button.test")));
executeBtn.setFont(StyleConstants.FONT_BUTTON_LARGE);
executeBtn.setMargin(new Insets(3, 3, 3, 3));
executeBtn.addActionListener(listeners::executeAnalysis);
@ -233,13 +242,16 @@ public final class AnalysisPanelProvider implements AnalysisTabOperator {
JButton clearBtn = new JButton(MessageUtils.getLocalizedMessage("button.clear"));
clearBtn.setFont(StyleConstants.FONT_BUTTON_LARGE);
clearBtn.setMargin(new Insets(5, 5, 5, 5));
clearBtn.addActionListener(e -> {
inputArea.setText("");
operatorRegistry.get(SimpleAnalyzeResultPanelOperator.class).ifPresent(
SimpleAnalyzeResultPanelOperator::clearTable);
operatorRegistry.get(StepByStepAnalyzeResultPanelOperator.class).ifPresent(
StepByStepAnalyzeResultPanelOperator::clearTable);
});
clearBtn.addActionListener(
e -> {
inputArea.setText("");
operatorRegistry
.get(SimpleAnalyzeResultPanelOperator.class)
.ifPresent(SimpleAnalyzeResultPanelOperator::clearTable);
operatorRegistry
.get(StepByStepAnalyzeResultPanelOperator.class)
.ifPresent(StepByStepAnalyzeResultPanelOperator::clearTable);
});
input.add(clearBtn);
inner1.add(input, BorderLayout.CENTER);
@ -259,20 +271,26 @@ public final class AnalysisPanelProvider implements AnalysisTabOperator {
mainPanel.remove(custom);
mainPanel.add(preset, BorderLayout.CENTER);
operatorRegistry.get(PresetAnalyzerPanelOperator.class).ifPresent(operator -> {
operator.setPresetAnalyzers(analysisModel.getPresetAnalyzerTypes());
operator.setSelectedAnalyzer(analysisModel.currentAnalyzer().getClass());
});
operatorRegistry
.get(PresetAnalyzerPanelOperator.class)
.ifPresent(
operator -> {
operator.setPresetAnalyzers(analysisModel.getPresetAnalyzerTypes());
operator.setSelectedAnalyzer(analysisModel.currentAnalyzer().getClass());
});
stepByStepCB.setSelected(false);
stepByStepCB.setVisible(false);
} else if (command.equalsIgnoreCase(TYPE_CUSTOM)) {
mainPanel.remove(preset);
mainPanel.add(custom, BorderLayout.CENTER);
operatorRegistry.get(CustomAnalyzerPanelOperator.class).ifPresent(operator -> {
operator.setAnalysisModel(analysisModel);
operator.resetAnalysisComponents();
});
operatorRegistry
.get(CustomAnalyzerPanelOperator.class)
.ifPresent(
operator -> {
operator.setAnalysisModel(analysisModel);
operator.resetAnalysisComponents();
});
stepByStepCB.setVisible(true);
}
mainPanel.setVisible(false);
@ -282,16 +300,20 @@ public final class AnalysisPanelProvider implements AnalysisTabOperator {
void executeAnalysis() {
String text = inputArea.getText();
if (Objects.isNull(text) || text.isEmpty()) {
messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("analysis.message.empry_input"));
messageBroker.showStatusMessage(
MessageUtils.getLocalizedMessage("analysis.message.empry_input"));
}
lowerPanel.remove(stepByStepResult);
lowerPanel.add(simpleResult, BorderLayout.CENTER);
operatorRegistry.get(SimpleAnalyzeResultPanelOperator.class).ifPresent(operator -> {
operator.setAnalysisModel(analysisModel);
operator.executeAnalysis(text);
});
operatorRegistry
.get(SimpleAnalyzeResultPanelOperator.class)
.ifPresent(
operator -> {
operator.setAnalysisModel(analysisModel);
operator.executeAnalysis(text);
});
lowerPanel.setVisible(false);
lowerPanel.setVisible(true);
@ -300,14 +322,18 @@ public final class AnalysisPanelProvider implements AnalysisTabOperator {
void executeAnalysisStepByStep() {
String text = inputArea.getText();
if (Objects.isNull(text) || text.isEmpty()) {
messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("analysis.message.empry_input"));
messageBroker.showStatusMessage(
MessageUtils.getLocalizedMessage("analysis.message.empry_input"));
}
lowerPanel.remove(simpleResult);
lowerPanel.add(stepByStepResult, BorderLayout.CENTER);
operatorRegistry.get(StepByStepAnalyzeResultPanelOperator.class).ifPresent(operator -> {
operator.setAnalysisModel(analysisModel);
operator.executeAnalysisStepByStep(text);
});
operatorRegistry
.get(StepByStepAnalyzeResultPanelOperator.class)
.ifPresent(
operator -> {
operator.setAnalysisModel(analysisModel);
operator.executeAnalysisStepByStep(text);
});
lowerPanel.setVisible(false);
lowerPanel.setVisible(true);
@ -316,10 +342,14 @@ public final class AnalysisPanelProvider implements AnalysisTabOperator {
void showAnalysisChainDialog() {
if (getCurrentAnalyzer() instanceof CustomAnalyzer) {
CustomAnalyzer analyzer = (CustomAnalyzer) getCurrentAnalyzer();
new DialogOpener<>(analysisChainDialogFactory).open("Analysis chain", 600, 320,
(factory) -> {
factory.setAnalyzer(analyzer);
});
new DialogOpener<>(analysisChainDialogFactory)
.open(
"Analysis chain",
600,
320,
(factory) -> {
factory.setAnalyzer(analyzer);
});
}
}
@ -328,12 +358,15 @@ public final class AnalysisPanelProvider implements AnalysisTabOperator {
analysisModel.createAnalyzerFromClassName(analyzerType);
analyzerNameLbl.setText(analysisModel.currentAnalyzer().getClass().getName());
showChainLbl.setVisible(false);
operatorRegistry.get(AnalyzerTabOperator.class).ifPresent(operator ->
operator.setAnalyzer(analysisModel.currentAnalyzer()));
operatorRegistry.get(MLTTabOperator.class).ifPresent(operator ->
operator.setAnalyzer(analysisModel.currentAnalyzer()));
operatorRegistry.get(AddDocumentDialogOperator.class).ifPresent(operator ->
operator.setAnalyzer(analysisModel.currentAnalyzer()));
operatorRegistry
.get(AnalyzerTabOperator.class)
.ifPresent(operator -> operator.setAnalyzer(analysisModel.currentAnalyzer()));
operatorRegistry
.get(MLTTabOperator.class)
.ifPresent(operator -> operator.setAnalyzer(analysisModel.currentAnalyzer()));
operatorRegistry
.get(AddDocumentDialogOperator.class)
.ifPresent(operator -> operator.setAnalyzer(analysisModel.currentAnalyzer()));
}
@Override
@ -341,12 +374,15 @@ public final class AnalysisPanelProvider implements AnalysisTabOperator {
analysisModel.buildCustomAnalyzer(config);
analyzerNameLbl.setText(analysisModel.currentAnalyzer().getClass().getName());
showChainLbl.setVisible(true);
operatorRegistry.get(AnalyzerTabOperator.class).ifPresent(operator ->
operator.setAnalyzer(analysisModel.currentAnalyzer()));
operatorRegistry.get(MLTTabOperator.class).ifPresent(operator ->
operator.setAnalyzer(analysisModel.currentAnalyzer()));
operatorRegistry.get(AddDocumentDialogOperator.class).ifPresent(operator ->
operator.setAnalyzer(analysisModel.currentAnalyzer()));
operatorRegistry
.get(AnalyzerTabOperator.class)
.ifPresent(operator -> operator.setAnalyzer(analysisModel.currentAnalyzer()));
operatorRegistry
.get(MLTTabOperator.class)
.ifPresent(operator -> operator.setAnalyzer(analysisModel.currentAnalyzer()));
operatorRegistry
.get(AddDocumentDialogOperator.class)
.ifPresent(operator -> operator.setAnalyzer(analysisModel.currentAnalyzer()));
}
@Override
@ -372,9 +408,6 @@ public final class AnalysisPanelProvider implements AnalysisTabOperator {
}
}
void executeAnalysisStepByStep(ActionEvent e) {
}
void executeAnalysisStepByStep(ActionEvent e) {}
}
}

View File

@ -28,6 +28,4 @@ public interface AnalysisTabOperator extends ComponentOperatorRegistry.Component
void setAnalyzerByCustomConfiguration(CustomAnalyzerConfig config);
Analyzer getCurrentAnalyzer();
}

View File

@ -17,6 +17,18 @@
package org.apache.lucene.luke.app.desktop.components;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
@ -32,19 +44,6 @@ import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.luke.app.DirectoryHandler;
import org.apache.lucene.luke.app.DirectoryObserver;
@ -100,7 +99,8 @@ public final class CommitsPanelProvider {
panel.setOpaque(false);
panel.setBorder(BorderFactory.createLineBorder(Color.gray));
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel());
JSplitPane splitPane =
new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel());
splitPane.setOpaque(false);
splitPane.setBorder(BorderFactory.createEmptyBorder());
splitPane.setDividerLocation(120);
@ -162,7 +162,11 @@ public final class CommitsPanelProvider {
userDataTA.setLineWrap(true);
userDataTA.setWrapStyleWord(true);
userDataTA.setEditable(false);
JScrollPane userDataScroll = new JScrollPane(userDataTA, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
JScrollPane userDataScroll =
new JScrollPane(
userDataTA,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
c1.gridx = 1;
c1.gridy = 2;
c1.weightx = 0.5;
@ -179,7 +183,8 @@ public final class CommitsPanelProvider {
panel.setOpaque(false);
panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, initFilesPanel(), initSegmentsPanel());
JSplitPane splitPane =
new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, initFilesPanel(), initSegmentsPanel());
splitPane.setOpaque(false);
splitPane.setBorder(BorderFactory.createEmptyBorder());
splitPane.setDividerLocation(300);
@ -197,7 +202,12 @@ public final class CommitsPanelProvider {
header.add(new JLabel(MessageUtils.getLocalizedMessage("commits.label.files")));
panel.add(header, BorderLayout.PAGE_START);
TableUtils.setupTable(filesTable, ListSelectionModel.SINGLE_SELECTION, new FilesTableModel(), null, FilesTableModel.Column.FILENAME.getColumnWidth());
TableUtils.setupTable(
filesTable,
ListSelectionModel.SINGLE_SELECTION,
new FilesTableModel(),
null,
FilesTableModel.Column.FILENAME.getColumnWidth());
panel.add(new JScrollPane(filesTable), BorderLayout.CENTER);
return panel;
@ -213,7 +223,10 @@ public final class CommitsPanelProvider {
segments.add(new JLabel(MessageUtils.getLocalizedMessage("commits.label.segments")));
panel.add(segments);
TableUtils.setupTable(segmentsTable, ListSelectionModel.SINGLE_SELECTION, new SegmentsTableModel(),
TableUtils.setupTable(
segmentsTable,
ListSelectionModel.SINGLE_SELECTION,
new SegmentsTableModel(),
new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
@ -241,12 +254,13 @@ public final class CommitsPanelProvider {
diagRB.setSelected(true);
diagRB.setEnabled(false);
diagRB.setOpaque(false);
diagRB.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
listeners.showSegmentDetails(e);
}
});
diagRB.addMouseListener(
new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
listeners.showSegmentDetails(e);
}
});
buttons.add(diagRB);
attrRB.setText("Attributes");
@ -254,12 +268,13 @@ public final class CommitsPanelProvider {
attrRB.setSelected(false);
attrRB.setEnabled(false);
attrRB.setOpaque(false);
attrRB.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
listeners.showSegmentDetails(e);
}
});
attrRB.addMouseListener(
new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
listeners.showSegmentDetails(e);
}
});
buttons.add(attrRB);
codecRB.setText("Codec");
@ -267,12 +282,13 @@ public final class CommitsPanelProvider {
codecRB.setSelected(false);
codecRB.setEnabled(false);
codecRB.setOpaque(false);
codecRB.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
listeners.showSegmentDetails(e);
}
});
codecRB.addMouseListener(
new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
listeners.showSegmentDetails(e);
}
});
buttons.add(codecRB);
rbGroup.add(diagRB);
@ -296,30 +312,55 @@ public final class CommitsPanelProvider {
segDetailList.setModel(new DefaultListModel<>());
long commitGen = (long) commitGenCombo.getSelectedItem();
commitsModel.getCommit(commitGen).ifPresent(commit -> {
deletedLbl.setText(String.valueOf(commit.isDeleted()));
segCntLbl.setText(String.valueOf(commit.getSegCount()));
userDataTA.setText(commit.getUserData());
});
commitsModel
.getCommit(commitGen)
.ifPresent(
commit -> {
deletedLbl.setText(String.valueOf(commit.isDeleted()));
segCntLbl.setText(String.valueOf(commit.getSegCount()));
userDataTA.setText(commit.getUserData());
});
filesTable.setModel(new FilesTableModel(commitsModel.getFiles(commitGen)));
filesTable.setShowGrid(true);
filesTable.getColumnModel().getColumn(FilesTableModel.Column.FILENAME.getIndex()).setPreferredWidth(FilesTableModel.Column.FILENAME.getColumnWidth());
filesTable
.getColumnModel()
.getColumn(FilesTableModel.Column.FILENAME.getIndex())
.setPreferredWidth(FilesTableModel.Column.FILENAME.getColumnWidth());
segmentsTable.setModel(new SegmentsTableModel(commitsModel.getSegments(commitGen)));
segmentsTable.setShowGrid(true);
segmentsTable.getColumnModel().getColumn(SegmentsTableModel.Column.NAME.getIndex()).setPreferredWidth(SegmentsTableModel.Column.NAME.getColumnWidth());
segmentsTable.getColumnModel().getColumn(SegmentsTableModel.Column.MAXDOCS.getIndex()).setPreferredWidth(SegmentsTableModel.Column.MAXDOCS.getColumnWidth());
segmentsTable.getColumnModel().getColumn(SegmentsTableModel.Column.DELS.getIndex()).setPreferredWidth(SegmentsTableModel.Column.DELS.getColumnWidth());
segmentsTable.getColumnModel().getColumn(SegmentsTableModel.Column.DELGEN.getIndex()).setPreferredWidth(SegmentsTableModel.Column.DELGEN.getColumnWidth());
segmentsTable.getColumnModel().getColumn(SegmentsTableModel.Column.VERSION.getIndex()).setPreferredWidth(SegmentsTableModel.Column.VERSION.getColumnWidth());
segmentsTable.getColumnModel().getColumn(SegmentsTableModel.Column.CODEC.getIndex()).setPreferredWidth(SegmentsTableModel.Column.CODEC.getColumnWidth());
segmentsTable
.getColumnModel()
.getColumn(SegmentsTableModel.Column.NAME.getIndex())
.setPreferredWidth(SegmentsTableModel.Column.NAME.getColumnWidth());
segmentsTable
.getColumnModel()
.getColumn(SegmentsTableModel.Column.MAXDOCS.getIndex())
.setPreferredWidth(SegmentsTableModel.Column.MAXDOCS.getColumnWidth());
segmentsTable
.getColumnModel()
.getColumn(SegmentsTableModel.Column.DELS.getIndex())
.setPreferredWidth(SegmentsTableModel.Column.DELS.getColumnWidth());
segmentsTable
.getColumnModel()
.getColumn(SegmentsTableModel.Column.DELGEN.getIndex())
.setPreferredWidth(SegmentsTableModel.Column.DELGEN.getColumnWidth());
segmentsTable
.getColumnModel()
.getColumn(SegmentsTableModel.Column.VERSION.getIndex())
.setPreferredWidth(SegmentsTableModel.Column.VERSION.getColumnWidth());
segmentsTable
.getColumnModel()
.getColumn(SegmentsTableModel.Column.CODEC.getIndex())
.setPreferredWidth(SegmentsTableModel.Column.CODEC.getColumnWidth());
}
private void showSegmentDetails() {
int selectedRow = segmentsTable.getSelectedRow();
if (commitGenCombo.getSelectedItem() == null ||
selectedRow < 0 || selectedRow >= segmentsTable.getRowCount()) {
if (commitGenCombo.getSelectedItem() == null
|| selectedRow < 0
|| selectedRow >= segmentsTable.getRowCount()) {
return;
}
@ -328,7 +369,8 @@ public final class CommitsPanelProvider {
codecRB.setEnabled(true);
long commitGen = (long) commitGenCombo.getSelectedItem();
String segName = (String) segmentsTable.getValueAt(selectedRow, SegmentsTableModel.Column.NAME.getIndex());
String segName =
(String) segmentsTable.getValueAt(selectedRow, SegmentsTableModel.Column.NAME.getIndex());
ActionCommand command = ActionCommand.valueOf(rbGroup.getSelection().getActionCommand());
final DefaultListModel<String> detailsModel = new DefaultListModel<>();
@ -344,27 +386,30 @@ public final class CommitsPanelProvider {
.forEach(detailsModel::addElement);
break;
case CODEC:
commitsModel.getSegmentCodec(commitGen, segName).ifPresent(codec -> {
Map<String, String> map = new HashMap<>();
map.put("Codec name", codec.getName());
map.put("Codec class name", codec.getClass().getName());
map.put("Compound format", codec.compoundFormat().getClass().getName());
map.put("DocValues format", codec.docValuesFormat().getClass().getName());
map.put("FieldInfos format", codec.fieldInfosFormat().getClass().getName());
map.put("LiveDocs format", codec.liveDocsFormat().getClass().getName());
map.put("Norms format", codec.normsFormat().getClass().getName());
map.put("Points format", codec.pointsFormat().getClass().getName());
map.put("Postings format", codec.postingsFormat().getClass().getName());
map.put("SegmentInfo format", codec.segmentInfoFormat().getClass().getName());
map.put("StoredFields format", codec.storedFieldsFormat().getClass().getName());
map.put("TermVectors format", codec.termVectorsFormat().getClass().getName());
map.entrySet().stream()
.map(entry -> entry.getKey() + " = " + entry.getValue()).forEach(detailsModel::addElement);
});
commitsModel
.getSegmentCodec(commitGen, segName)
.ifPresent(
codec -> {
Map<String, String> map = new HashMap<>();
map.put("Codec name", codec.getName());
map.put("Codec class name", codec.getClass().getName());
map.put("Compound format", codec.compoundFormat().getClass().getName());
map.put("DocValues format", codec.docValuesFormat().getClass().getName());
map.put("FieldInfos format", codec.fieldInfosFormat().getClass().getName());
map.put("LiveDocs format", codec.liveDocsFormat().getClass().getName());
map.put("Norms format", codec.normsFormat().getClass().getName());
map.put("Points format", codec.pointsFormat().getClass().getName());
map.put("Postings format", codec.postingsFormat().getClass().getName());
map.put("SegmentInfo format", codec.segmentInfoFormat().getClass().getName());
map.put("StoredFields format", codec.storedFieldsFormat().getClass().getName());
map.put("TermVectors format", codec.termVectorsFormat().getClass().getName());
map.entrySet().stream()
.map(entry -> entry.getKey() + " = " + entry.getValue())
.forEach(detailsModel::addElement);
});
break;
}
segDetailList.setModel(detailsModel);
}
private class ListenerFunctions {
@ -376,7 +421,6 @@ public final class CommitsPanelProvider {
void showSegmentDetails(MouseEvent e) {
CommitsPanelProvider.this.showSegmentDetails();
}
}
private class Observer implements IndexObserver, DirectoryObserver {
@ -425,8 +469,17 @@ public final class CommitsPanelProvider {
deletedLbl.setText("");
segCntLbl.setText("");
userDataTA.setText("");
TableUtils.setupTable(filesTable, ListSelectionModel.SINGLE_SELECTION, new FilesTableModel(), null, FilesTableModel.Column.FILENAME.getColumnWidth());
TableUtils.setupTable(segmentsTable, ListSelectionModel.SINGLE_SELECTION, new SegmentsTableModel(), null,
TableUtils.setupTable(
filesTable,
ListSelectionModel.SINGLE_SELECTION,
new FilesTableModel(),
null,
FilesTableModel.Column.FILENAME.getColumnWidth());
TableUtils.setupTable(
segmentsTable,
ListSelectionModel.SINGLE_SELECTION,
new SegmentsTableModel(),
null,
SegmentsTableModel.Column.NAME.getColumnWidth(),
SegmentsTableModel.Column.MAXDOCS.getColumnWidth(),
SegmentsTableModel.Column.DELS.getColumnWidth(),
@ -441,13 +494,14 @@ public final class CommitsPanelProvider {
}
enum ActionCommand {
DIAGNOSTICS, ATTRIBUTES, CODEC;
DIAGNOSTICS,
ATTRIBUTES,
CODEC;
}
static final class FilesTableModel extends TableModelBase<FilesTableModel.Column> {
enum Column implements TableColumnInfo {
FILENAME("Filename", 0, String.class, 200),
SIZE("Size", 1, String.class, Integer.MAX_VALUE);
@ -506,7 +560,6 @@ public final class CommitsPanelProvider {
static final class SegmentsTableModel extends TableModelBase<SegmentsTableModel.Column> {
enum Column implements TableColumnInfo {
NAME("Name", 0, String.class, 60),
MAXDOCS("Max docs", 1, Integer.class, 60),
DELS("Dels", 2, Integer.class, 60),
@ -572,4 +625,3 @@ public final class CommitsPanelProvider {
}
}
}

View File

@ -44,7 +44,5 @@ public class ComponentOperatorRegistry {
}
/** marker interface for operators */
public interface ComponentOperator {
}
public interface ComponentOperator {}
}

View File

@ -17,26 +17,6 @@
package org.apache.lucene.luke.app.desktop.components;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.table.TableCellRenderer;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
@ -57,7 +37,26 @@ import java.math.BigInteger;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.table.TableCellRenderer;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.Term;
@ -152,26 +151,30 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
this.dvDialogFactory = DocValuesDialogFactory.getInstance();
this.valueDialogFactory = StoredValueDialogFactory.getInstance();
HelpDialogFactory helpDialogFactory = HelpDialogFactory.getInstance();
this.tableHeaderRenderer = new HelpHeaderRenderer(
"About Flags", "Format: IdfpoNPSB#txxVDtxxxxTx/x",
createFlagsHelpDialog(), helpDialogFactory);
this.tableHeaderRenderer =
new HelpHeaderRenderer(
"About Flags",
"Format: IdfpoNPSB#txxVDtxxxxTx/x",
createFlagsHelpDialog(),
helpDialogFactory);
IndexHandler.getInstance().addObserver(new Observer());
operatorRegistry.register(DocumentsTabOperator.class, this);
}
private JComponent createFlagsHelpDialog() {
String[] values = new String[]{
"I - index options(docs, frequencies, positions, offsets)",
"N - norms",
"P - payloads",
"S - stored",
"B - binary stored values",
"#txx - numeric stored values(type, precision)",
"V - term vectors",
"Dtxxxxx - doc values(type)",
"Tx/x - point values(num bytes/dimension)"
};
String[] values =
new String[] {
"I - index options(docs, frequencies, positions, offsets)",
"N - norms",
"P - payloads",
"S - stored",
"B - binary stored values",
"#txx - numeric stored values(type, precision)",
"V - term vectors",
"Dtxxxxx - doc values(type)",
"Tx/x - point values(num bytes/dimension)"
};
JList<String> list = new JList<>(values);
return new JScrollPane(list);
}
@ -181,7 +184,8 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
panel.setOpaque(false);
panel.setBorder(BorderFactory.createLineBorder(Color.gray));
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel());
JSplitPane splitPane =
new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel());
splitPane.setOpaque(false);
splitPane.setDividerLocation(0.4);
panel.add(splitPane);
@ -238,7 +242,9 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
c.gridwidth = 2;
center.add(fieldsCombo, c);
firstTermBtn.setText(FontUtils.elegantIconHtml("&#x38;", MessageUtils.getLocalizedMessage("documents.button.first_term")));
firstTermBtn.setText(
FontUtils.elegantIconHtml(
"&#x38;", MessageUtils.getLocalizedMessage("documents.button.first_term")));
firstTermBtn.setMaximumSize(new Dimension(80, 30));
firstTermBtn.addActionListener(listeners::showFirstTerm);
c.gridx = 0;
@ -272,7 +278,8 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
JPanel footer = new JPanel(new FlowLayout(FlowLayout.LEADING, 20, 5));
footer.setOpaque(false);
JLabel hintLbl = new JLabel(MessageUtils.getLocalizedMessage("documents.label.browse_terms_hint"));
JLabel hintLbl =
new JLabel(MessageUtils.getLocalizedMessage("documents.label.browse_terms_hint"));
footer.add(hintLbl);
panel.add(footer, BorderLayout.PAGE_END);
@ -289,7 +296,8 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
JLabel label = new JLabel(MessageUtils.getLocalizedMessage("documents.label.browse_doc_by_term"));
JLabel label =
new JLabel(MessageUtils.getLocalizedMessage("documents.label.browse_doc_by_term"));
c.gridx = 0;
c.gridy = 0;
c.weightx = 0.0;
@ -308,7 +316,9 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
c.insets = new Insets(5, 5, 5, 5);
center.add(selectedTermTF, c);
firstTermDocBtn.setText(FontUtils.elegantIconHtml("&#x38;", MessageUtils.getLocalizedMessage("documents.button.first_termdoc")));
firstTermDocBtn.setText(
FontUtils.elegantIconHtml(
"&#x38;", MessageUtils.getLocalizedMessage("documents.button.first_termdoc")));
firstTermDocBtn.addActionListener(listeners::showFirstTermDoc);
c.gridx = 0;
c.gridy = 2;
@ -343,8 +353,14 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
c.insets = new Insets(5, 5, 5, 5);
center.add(termDocsNumLbl, c);
TableUtils.setupTable(posTable, ListSelectionModel.SINGLE_SELECTION, new PosTableModel(), null,
PosTableModel.Column.POSITION.getColumnWidth(), PosTableModel.Column.OFFSETS.getColumnWidth(), PosTableModel.Column.PAYLOAD.getColumnWidth());
TableUtils.setupTable(
posTable,
ListSelectionModel.SINGLE_SELECTION,
new PosTableModel(),
null,
PosTableModel.Column.POSITION.getColumnWidth(),
PosTableModel.Column.OFFSETS.getColumnWidth(),
PosTableModel.Column.PAYLOAD.getColumnWidth());
JScrollPane scrollPane = new JScrollPane(posTable);
scrollPane.setMinimumSize(new Dimension(100, 100));
c.gridx = 0;
@ -370,17 +386,23 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
JPanel browseDocsNote1 = new JPanel(new FlowLayout(FlowLayout.LEADING));
browseDocsNote1.setOpaque(false);
browseDocsNote1.add(new JLabel(MessageUtils.getLocalizedMessage("documents.label.doc_table_note1")));
browseDocsNote1.add(
new JLabel(MessageUtils.getLocalizedMessage("documents.label.doc_table_note1")));
browseDocsPanel.add(browseDocsNote1);
JPanel browseDocsNote2 = new JPanel(new FlowLayout(FlowLayout.LEADING));
browseDocsNote2.setOpaque(false);
browseDocsNote2.add(new JLabel(MessageUtils.getLocalizedMessage("documents.label.doc_table_note2")));
browseDocsNote2.add(
new JLabel(MessageUtils.getLocalizedMessage("documents.label.doc_table_note2")));
browseDocsPanel.add(browseDocsNote2);
panel.add(browseDocsPanel, BorderLayout.PAGE_START);
TableUtils.setupTable(documentTable, ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, new DocumentsTableModel(), new MouseAdapter() {
TableUtils.setupTable(
documentTable,
ListSelectionModel.MULTIPLE_INTERVAL_SELECTION,
new DocumentsTableModel(),
new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
listeners.showDocumentContextMenu(e);
@ -394,7 +416,10 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
flagsHeader.setOpaque(false);
flagsHeader.add(new JLabel("Flags"));
flagsHeader.add(new JLabel("Help"));
documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.FLAGS.getIndex()).setHeaderValue(flagsHeader);
documentTable
.getColumnModel()
.getColumn(DocumentsTableModel.Column.FLAGS.getIndex())
.setHeaderValue(flagsHeader);
JScrollPane scrollPane = new JScrollPane(documentTable);
scrollPane.getHorizontalScrollBar().setAutoscrolls(false);
@ -410,7 +435,10 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
JPanel left = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 2));
left.setOpaque(false);
JLabel label = new JLabel(FontUtils.elegantIconHtml("&#x68;", MessageUtils.getLocalizedMessage("documents.label.browse_doc_by_idx")));
JLabel label =
new JLabel(
FontUtils.elegantIconHtml(
"&#x68;", MessageUtils.getLocalizedMessage("documents.label.browse_doc_by_idx")));
label.setHorizontalTextPosition(JLabel.LEFT);
left.add(label);
docNumSpnr.setPreferredSize(new Dimension(100, 25));
@ -422,15 +450,21 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
JPanel right = new JPanel(new FlowLayout(FlowLayout.TRAILING));
right.setOpaque(false);
copyDocValuesBtn.setText(FontUtils.elegantIconHtml("&#xe0e6;", MessageUtils.getLocalizedMessage("documents.buttont.copy_values")));
copyDocValuesBtn.setText(
FontUtils.elegantIconHtml(
"&#xe0e6;", MessageUtils.getLocalizedMessage("documents.buttont.copy_values")));
copyDocValuesBtn.setMargin(new Insets(5, 0, 5, 0));
copyDocValuesBtn.addActionListener(listeners::copySelectedOrAllStoredValues);
right.add(copyDocValuesBtn);
mltBtn.setText(FontUtils.elegantIconHtml("&#xe030;", MessageUtils.getLocalizedMessage("documents.button.mlt")));
mltBtn.setText(
FontUtils.elegantIconHtml(
"&#xe030;", MessageUtils.getLocalizedMessage("documents.button.mlt")));
mltBtn.setMargin(new Insets(5, 0, 5, 0));
mltBtn.addActionListener(listeners::mltSearch);
right.add(mltBtn);
addDocBtn.setText(FontUtils.elegantIconHtml("&#x59;", MessageUtils.getLocalizedMessage("documents.button.add")));
addDocBtn.setText(
FontUtils.elegantIconHtml(
"&#x59;", MessageUtils.getLocalizedMessage("documents.button.add")));
addDocBtn.setMargin(new Insets(5, 0, 5, 0));
addDocBtn.addActionListener(listeners::showAddDocumentDialog);
right.add(addDocBtn);
@ -441,22 +475,26 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
private void setUpDocumentContextMenu() {
// show term vector
JMenuItem item1 = new JMenuItem(MessageUtils.getLocalizedMessage("documents.doctable.menu.item1"));
JMenuItem item1 =
new JMenuItem(MessageUtils.getLocalizedMessage("documents.doctable.menu.item1"));
item1.addActionListener(listeners::showTermVectorDialog);
documentContextMenu.add(item1);
// show doc values
JMenuItem item2 = new JMenuItem(MessageUtils.getLocalizedMessage("documents.doctable.menu.item2"));
JMenuItem item2 =
new JMenuItem(MessageUtils.getLocalizedMessage("documents.doctable.menu.item2"));
item2.addActionListener(listeners::showDocValuesDialog);
documentContextMenu.add(item2);
// show stored value
JMenuItem item3 = new JMenuItem(MessageUtils.getLocalizedMessage("documents.doctable.menu.item3"));
JMenuItem item3 =
new JMenuItem(MessageUtils.getLocalizedMessage("documents.doctable.menu.item3"));
item3.addActionListener(listeners::showStoredValueDialog);
documentContextMenu.add(item3);
// copy stored value to clipboard
JMenuItem item4 = new JMenuItem(MessageUtils.getLocalizedMessage("documents.doctable.menu.item4"));
JMenuItem item4 =
new JMenuItem(MessageUtils.getLocalizedMessage("documents.doctable.menu.item4"));
item4.addActionListener(listeners::copyStoredValue);
documentContextMenu.add(item4);
}
@ -466,7 +504,8 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
private void showFirstTerm() {
String fieldName = (String) fieldsCombo.getSelectedItem();
if (fieldName == null || fieldName.length() == 0) {
messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.field.message.not_selected"));
messageBroker.showStatusMessage(
MessageUtils.getLocalizedMessage("documents.field.message.not_selected"));
return;
}
@ -542,9 +581,12 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
messageBroker.clearStatusMessage();
}
private void clearPosTable() {
TableUtils.setupTable(posTable, ListSelectionModel.SINGLE_SELECTION, new PosTableModel(), null,
TableUtils.setupTable(
posTable,
ListSelectionModel.SINGLE_SELECTION,
new PosTableModel(),
null,
PosTableModel.Column.POSITION.getColumnWidth(),
PosTableModel.Column.OFFSETS.getColumnWidth(),
PosTableModel.Column.PAYLOAD.getColumnWidth());
@ -555,7 +597,8 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
int docid = documentsModel.firstTermDoc().orElse(-1);
if (docid < 0) {
nextTermDocBtn.setEnabled(false);
messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.termdocs.message.not_available"));
messageBroker.showStatusMessage(
MessageUtils.getLocalizedMessage("documents.termdocs.message.not_available"));
return;
}
termDocIdxTF.setText(String.valueOf(1));
@ -563,9 +606,18 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
List<TermPosting> postings = documentsModel.getTermPositions();
posTable.setModel(new PosTableModel(postings));
posTable.getColumnModel().getColumn(PosTableModel.Column.POSITION.getIndex()).setPreferredWidth(PosTableModel.Column.POSITION.getColumnWidth());
posTable.getColumnModel().getColumn(PosTableModel.Column.OFFSETS.getIndex()).setPreferredWidth(PosTableModel.Column.OFFSETS.getColumnWidth());
posTable.getColumnModel().getColumn(PosTableModel.Column.PAYLOAD.getIndex()).setPreferredWidth(PosTableModel.Column.PAYLOAD.getColumnWidth());
posTable
.getColumnModel()
.getColumn(PosTableModel.Column.POSITION.getIndex())
.setPreferredWidth(PosTableModel.Column.POSITION.getColumnWidth());
posTable
.getColumnModel()
.getColumn(PosTableModel.Column.OFFSETS.getIndex())
.setPreferredWidth(PosTableModel.Column.OFFSETS.getColumnWidth());
posTable
.getColumnModel()
.getColumn(PosTableModel.Column.PAYLOAD.getIndex())
.setPreferredWidth(PosTableModel.Column.PAYLOAD.getColumnWidth());
nextTermDocBtn.setEnabled(true);
messageBroker.clearStatusMessage();
@ -575,7 +627,8 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
int docid = documentsModel.nextTermDoc().orElse(-1);
if (docid < 0) {
nextTermDocBtn.setEnabled(false);
messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.termdocs.message.not_available"));
messageBroker.showStatusMessage(
MessageUtils.getLocalizedMessage("documents.termdocs.message.not_available"));
return;
}
int curIdx = Integer.parseInt(termDocIdxTF.getText());
@ -596,75 +649,121 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
private void mltSearch() {
int docNum = (int) docNumSpnr.getValue();
operatorRegistry.get(SearchTabOperator.class).ifPresent(operator -> {
operator.mltSearch(docNum);
tabSwitcher.switchTab(TabbedPaneProvider.Tab.SEARCH);
});
operatorRegistry
.get(SearchTabOperator.class)
.ifPresent(
operator -> {
operator.mltSearch(docNum);
tabSwitcher.switchTab(TabbedPaneProvider.Tab.SEARCH);
});
}
private void showAddDocumentDialog() {
new DialogOpener<>(addDocDialogFactory).open("Add document", 600, 500,
(factory) -> {
});
new DialogOpener<>(addDocDialogFactory).open("Add document", 600, 500, (factory) -> {});
}
private void showTermVectorDialog() {
int docid = (Integer) docNumSpnr.getValue();
String field = (String) documentTable.getModel().getValueAt(documentTable.getSelectedRow(), DocumentsTableModel.Column.FIELD.getIndex());
String field =
(String)
documentTable
.getModel()
.getValueAt(
documentTable.getSelectedRow(), DocumentsTableModel.Column.FIELD.getIndex());
List<TermVectorEntry> tvEntries = documentsModel.getTermVectors(docid, field);
if (tvEntries.isEmpty()) {
messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.termvector.message.not_available", field, docid));
messageBroker.showStatusMessage(
MessageUtils.getLocalizedMessage(
"documents.termvector.message.not_available", field, docid));
return;
}
new DialogOpener<>(tvDialogFactory).open(
"Term Vector", 600, 400,
(factory) -> {
factory.setField(field);
factory.setTvEntries(tvEntries);
});
new DialogOpener<>(tvDialogFactory)
.open(
"Term Vector",
600,
400,
(factory) -> {
factory.setField(field);
factory.setTvEntries(tvEntries);
});
messageBroker.clearStatusMessage();
}
private void showDocValuesDialog() {
int docid = (Integer) docNumSpnr.getValue();
String field = (String) documentTable.getModel().getValueAt(documentTable.getSelectedRow(), DocumentsTableModel.Column.FIELD.getIndex());
String field =
(String)
documentTable
.getModel()
.getValueAt(
documentTable.getSelectedRow(), DocumentsTableModel.Column.FIELD.getIndex());
Optional<DocValues> docValues = documentsModel.getDocValues(docid, field);
if (docValues.isPresent()) {
new DialogOpener<>(dvDialogFactory).open(
"Doc Values", 400, 300,
(factory) -> {
factory.setValue(field, docValues.get());
});
new DialogOpener<>(dvDialogFactory)
.open(
"Doc Values",
400,
300,
(factory) -> {
factory.setValue(field, docValues.get());
});
messageBroker.clearStatusMessage();
} else {
messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.docvalues.message.not_available", field, docid));
messageBroker.showStatusMessage(
MessageUtils.getLocalizedMessage(
"documents.docvalues.message.not_available", field, docid));
}
}
private void showStoredValueDialog() {
int docid = (Integer) docNumSpnr.getValue();
String field = (String) documentTable.getModel().getValueAt(documentTable.getSelectedRow(), DocumentsTableModel.Column.FIELD.getIndex());
String value = (String) documentTable.getModel().getValueAt(documentTable.getSelectedRow(), DocumentsTableModel.Column.VALUE.getIndex());
String field =
(String)
documentTable
.getModel()
.getValueAt(
documentTable.getSelectedRow(), DocumentsTableModel.Column.FIELD.getIndex());
String value =
(String)
documentTable
.getModel()
.getValueAt(
documentTable.getSelectedRow(), DocumentsTableModel.Column.VALUE.getIndex());
if (Objects.isNull(value)) {
messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.stored.message.not_availabe", field, docid));
messageBroker.showStatusMessage(
MessageUtils.getLocalizedMessage("documents.stored.message.not_availabe", field, docid));
return;
}
new DialogOpener<>(valueDialogFactory).open(
"Stored Value", 400, 300,
(factory) -> {
factory.setField(field);
factory.setValue(value);
});
new DialogOpener<>(valueDialogFactory)
.open(
"Stored Value",
400,
300,
(factory) -> {
factory.setField(field);
factory.setValue(value);
});
messageBroker.clearStatusMessage();
}
private void copyStoredValue() {
int docid = (Integer) docNumSpnr.getValue();
String field = (String) documentTable.getModel().getValueAt(documentTable.getSelectedRow(), DocumentsTableModel.Column.FIELD.getIndex());
String value = (String) documentTable.getModel().getValueAt(documentTable.getSelectedRow(), DocumentsTableModel.Column.VALUE.getIndex());
String field =
(String)
documentTable
.getModel()
.getValueAt(
documentTable.getSelectedRow(), DocumentsTableModel.Column.FIELD.getIndex());
String value =
(String)
documentTable
.getModel()
.getValueAt(
documentTable.getSelectedRow(), DocumentsTableModel.Column.VALUE.getIndex());
if (Objects.isNull(value)) {
messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.stored.message.not_availabe", field, docid));
messageBroker.showStatusMessage(
MessageUtils.getLocalizedMessage("documents.stored.message.not_availabe", field, docid));
return;
}
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
@ -688,7 +787,9 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
private StringSelection copyAllValues() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < documentTable.getRowCount(); i++) {
String value = (String) documentTable.getModel().getValueAt(i, DocumentsTableModel.Column.VALUE.getIndex());
String value =
(String)
documentTable.getModel().getValueAt(i, DocumentsTableModel.Column.VALUE.getIndex());
if (Objects.nonNull(value)) {
sb.append((i == 0) ? value : System.lineSeparator() + value);
}
@ -700,7 +801,11 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
StringBuilder sb = new StringBuilder();
boolean isFirst = true;
for (int rowIndex : documentTable.getSelectedRows()) {
String value = (String) documentTable.getModel().getValueAt(rowIndex, DocumentsTableModel.Column.VALUE.getIndex());
String value =
(String)
documentTable
.getModel()
.getValueAt(rowIndex, DocumentsTableModel.Column.VALUE.getIndex());
if (Objects.nonNull(value)) {
sb.append(isFirst ? value : System.lineSeparator() + value);
isFirst = false;
@ -727,7 +832,6 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
public void displayDoc(int docid) {
showDoc(docid);
}
;
private void showDoc(int docid) {
@ -736,13 +840,34 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
List<DocumentField> doc = documentsModel.getDocumentFields(docid);
documentTable.setModel(new DocumentsTableModel(doc));
documentTable.setFont(StyleConstants.FONT_MONOSPACE_LARGE);
documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.FIELD.getIndex()).setPreferredWidth(DocumentsTableModel.Column.FIELD.getColumnWidth());
documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.FLAGS.getIndex()).setMinWidth(DocumentsTableModel.Column.FLAGS.getColumnWidth());
documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.FLAGS.getIndex()).setMaxWidth(DocumentsTableModel.Column.FIELD.getColumnWidth());
documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.NORM.getIndex()).setMinWidth(DocumentsTableModel.Column.NORM.getColumnWidth());
documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.NORM.getIndex()).setMaxWidth(DocumentsTableModel.Column.NORM.getColumnWidth());
documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.VALUE.getIndex()).setPreferredWidth(DocumentsTableModel.Column.VALUE.getColumnWidth());
documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.FLAGS.getIndex()).setHeaderRenderer(tableHeaderRenderer);
documentTable
.getColumnModel()
.getColumn(DocumentsTableModel.Column.FIELD.getIndex())
.setPreferredWidth(DocumentsTableModel.Column.FIELD.getColumnWidth());
documentTable
.getColumnModel()
.getColumn(DocumentsTableModel.Column.FLAGS.getIndex())
.setMinWidth(DocumentsTableModel.Column.FLAGS.getColumnWidth());
documentTable
.getColumnModel()
.getColumn(DocumentsTableModel.Column.FLAGS.getIndex())
.setMaxWidth(DocumentsTableModel.Column.FIELD.getColumnWidth());
documentTable
.getColumnModel()
.getColumn(DocumentsTableModel.Column.NORM.getIndex())
.setMinWidth(DocumentsTableModel.Column.NORM.getColumnWidth());
documentTable
.getColumnModel()
.getColumn(DocumentsTableModel.Column.NORM.getIndex())
.setMaxWidth(DocumentsTableModel.Column.NORM.getColumnWidth());
documentTable
.getColumnModel()
.getColumn(DocumentsTableModel.Column.VALUE.getIndex())
.setPreferredWidth(DocumentsTableModel.Column.VALUE.getColumnWidth());
documentTable
.getColumnModel()
.getColumn(DocumentsTableModel.Column.FLAGS.getIndex())
.setHeaderRenderer(tableHeaderRenderer);
messageBroker.clearStatusMessage();
}
@ -810,7 +935,6 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
void copySelectedOrAllStoredValues(ActionEvent e) {
DocumentsPanelProvider.this.copySelectedOrAllStoredValues();
}
}
private class Observer implements IndexObserver {
@ -854,7 +978,6 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
static final class PosTableModel extends TableModelBase<PosTableModel.Column> {
enum Column implements TableColumnInfo {
POSITION("Position", 0, Integer.class, 80),
OFFSETS("Offsets", 1, String.class, 120),
PAYLOAD("Payload", 2, String.class, 300);
@ -912,7 +1035,7 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
payload = BytesRefUtils.decode(p.getPayload());
}
data[i] = new Object[]{position, offset, payload};
data[i] = new Object[] {position, offset, payload};
}
}
@ -983,7 +1106,7 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
} else if (docField.getBinaryValue() != null) {
value = String.valueOf(docField.getBinaryValue());
}
data[i] = new Object[]{field, flags, norm, value};
data[i] = new Object[] {field, flags, norm, value};
}
}
@ -1110,6 +1233,4 @@ public final class DocumentsPanelProvider implements DocumentsTabOperator {
return Column.values();
}
}
}

View File

@ -28,4 +28,4 @@ public interface DocumentsTabOperator extends ComponentOperatorRegistry.Componen
void seekNextTerm();
void showFirstTermDoc();
}
}

View File

@ -17,14 +17,13 @@
package org.apache.lucene.luke.app.desktop.components;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import org.apache.lucene.luke.app.desktop.LukeMain;
import org.apache.lucene.luke.app.desktop.util.MessageUtils;
@ -54,5 +53,4 @@ public final class LogsPanelProvider {
panel.add(new JScrollPane(logTextArea), BorderLayout.CENTER);
return panel;
}
}

View File

@ -17,14 +17,6 @@
package org.apache.lucene.luke.app.desktop.components;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.WindowConstants;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
@ -33,7 +25,14 @@ import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.io.IOException;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.WindowConstants;
import org.apache.lucene.luke.app.DirectoryHandler;
import org.apache.lucene.luke.app.DirectoryObserver;
import org.apache.lucene.luke.app.IndexHandler;
@ -51,7 +50,8 @@ import org.apache.lucene.util.Version;
/** Provider of the root window */
public final class LukeWindowProvider implements LukeWindowOperator {
private static final String WINDOW_TITLE = MessageUtils.getLocalizedMessage("window.title") + " - v" + Version.LATEST.toString();
private static final String WINDOW_TITLE =
MessageUtils.getLocalizedMessage("window.title") + " - v" + Version.LATEST.toString();
private final Preferences prefs;
@ -149,7 +149,6 @@ public final class LukeWindowProvider implements LukeWindowOperator {
multiIcon.setVisible(false);
iconPanel.add(multiIcon);
readOnlyIcon.setText(FontUtils.elegantIconHtml("&#xe06c;"));
readOnlyIcon.setToolTipText(MessageUtils.getLocalizedMessage("tooltip.read_only"));
readOnlyIcon.setVisible(false);
@ -206,9 +205,11 @@ public final class LukeWindowProvider implements LukeWindowOperator {
noReaderIcon.setVisible(false);
if (state.readOnly()) {
messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("message.index_opened_ro"));
messageBroker.showStatusMessage(
MessageUtils.getLocalizedMessage("message.index_opened_ro"));
} else if (!state.hasDirectoryReader()) {
messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("message.index_opened_multi"));
messageBroker.showStatusMessage(
MessageUtils.getLocalizedMessage("message.index_opened_multi"));
} else {
messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("message.index_opened"));
}
@ -222,7 +223,6 @@ public final class LukeWindowProvider implements LukeWindowOperator {
messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("message.index_closed"));
}
}
private class MessageReceiverImpl implements MessageBroker.MessageReceiver {
@ -242,9 +242,6 @@ public final class LukeWindowProvider implements LukeWindowOperator {
messageLbl.setText("");
}
private MessageReceiverImpl() {
}
private MessageReceiverImpl() {}
}
}

View File

@ -17,12 +17,11 @@
package org.apache.lucene.luke.app.desktop.components;
import java.awt.event.ActionEvent;
import java.io.IOException;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import java.awt.event.ActionEvent;
import java.io.IOException;
import org.apache.lucene.luke.app.DirectoryHandler;
import org.apache.lucene.luke.app.DirectoryObserver;
import org.apache.lucene.luke.app.IndexHandler;
@ -135,7 +134,6 @@ public final class MenuBarProvider {
createIndexMItem.addActionListener(listeners::showCreateIndexDialog);
fileMenu.add(createIndexMItem);
closeIndexMItem.setText(MessageUtils.getLocalizedMessage("menu.item.close_index"));
closeIndexMItem.setEnabled(false);
closeIndexMItem.addActionListener(listeners::closeIndex);
@ -197,13 +195,21 @@ public final class MenuBarProvider {
private class ListenerFunctions {
void showOpenIndexDialog(ActionEvent e) {
new DialogOpener<>(openIndexDialogFactory).open(MessageUtils.getLocalizedMessage("openindex.dialog.title"), 600, 420,
(factory) -> {});
new DialogOpener<>(openIndexDialogFactory)
.open(
MessageUtils.getLocalizedMessage("openindex.dialog.title"),
600,
420,
(factory) -> {});
}
void showCreateIndexDialog(ActionEvent e) {
new DialogOpener<>(createIndexDialogFactory).open(MessageUtils.getLocalizedMessage("createindex.dialog.title"), 600, 360,
(factory) -> {});
new DialogOpener<>(createIndexDialogFactory)
.open(
MessageUtils.getLocalizedMessage("createindex.dialog.title"),
600,
360,
(factory) -> {});
}
void reopenIndex(ActionEvent e) {
@ -233,7 +239,9 @@ public final class MenuBarProvider {
private void changeTheme(Preferences.ColorTheme theme) {
try {
prefs.setColorTheme(theme);
operatorRegistry.get(LukeWindowOperator.class).ifPresent(operator -> operator.setColorTheme(theme));
operatorRegistry
.get(LukeWindowOperator.class)
.ifPresent(operator -> operator.setColorTheme(theme));
} catch (IOException e) {
throw new LukeException("Failed to set color theme : " + theme.name(), e);
}
@ -250,30 +258,22 @@ public final class MenuBarProvider {
}
void showOptimizeIndexDialog(ActionEvent e) {
new DialogOpener<>(optimizeIndexDialogFactory).open("Optimize index", 600, 600,
factory -> {
});
new DialogOpener<>(optimizeIndexDialogFactory)
.open("Optimize index", 600, 600, factory -> {});
}
void showCheckIndexDialog(ActionEvent e) {
new DialogOpener<>(checkIndexDialogFactory).open("Check index", 600, 600,
factory -> {
});
new DialogOpener<>(checkIndexDialogFactory).open("Check index", 600, 600, factory -> {});
}
void showAboutDialog(ActionEvent e) {
final String title = "About Luke v" + Version.LATEST.toString();
new DialogOpener<>(aboutDialogFactory).open(title, 800, 480,
factory -> {
});
new DialogOpener<>(aboutDialogFactory).open(title, 800, 480, factory -> {});
}
void showExportTermsDialog(ActionEvent e) {
new DialogOpener<>(exportTermsDialogFactory).open("Export terms", 600, 450,
factory -> {
});
new DialogOpener<>(exportTermsDialogFactory).open("Export terms", 600, 450, factory -> {});
}
}
private class Observer implements IndexObserver, DirectoryObserver {
@ -317,6 +317,5 @@ public final class MenuBarProvider {
checkIndexMItem.setEnabled(false);
exportTermsMItem.setEnabled(false);
}
}
}

View File

@ -17,6 +17,20 @@
package org.apache.lucene.luke.app.desktop.components;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
@ -33,21 +47,6 @@ import javax.swing.ListSelectionModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableRowSorter;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.lucene.luke.app.IndexHandler;
import org.apache.lucene.luke.app.IndexObserver;
import org.apache.lucene.luke.app.LukeState;
@ -127,7 +126,8 @@ public final class OverviewPanelProvider {
panel.setLayout(new GridLayout(1, 1));
panel.setBorder(BorderFactory.createLineBorder(Color.gray));
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel());
JSplitPane splitPane =
new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel());
splitPane.setDividerLocation(0.4);
splitPane.setOpaque(false);
panel.add(splitPane);
@ -149,7 +149,8 @@ public final class OverviewPanelProvider {
c.gridx = GRIDX_DESC;
c.weightx = WEIGHTX_DESC;
panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.index_path"), JLabel.RIGHT), c);
panel.add(
new JLabel(MessageUtils.getLocalizedMessage("overview.label.index_path"), JLabel.RIGHT), c);
c.gridx = GRIDX_VAL;
c.weightx = WEIGHTX_VAL;
@ -159,7 +160,8 @@ public final class OverviewPanelProvider {
c.gridx = GRIDX_DESC;
c.gridy += 1;
c.weightx = WEIGHTX_DESC;
panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.num_fields"), JLabel.RIGHT), c);
panel.add(
new JLabel(MessageUtils.getLocalizedMessage("overview.label.num_fields"), JLabel.RIGHT), c);
c.gridx = GRIDX_VAL;
c.weightx = WEIGHTX_VAL;
@ -169,7 +171,8 @@ public final class OverviewPanelProvider {
c.gridx = GRIDX_DESC;
c.gridy += 1;
c.weightx = WEIGHTX_DESC;
panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.num_docs"), JLabel.RIGHT), c);
panel.add(
new JLabel(MessageUtils.getLocalizedMessage("overview.label.num_docs"), JLabel.RIGHT), c);
c.gridx = GRIDX_VAL;
c.weightx = WEIGHTX_VAL;
@ -179,7 +182,8 @@ public final class OverviewPanelProvider {
c.gridx = GRIDX_DESC;
c.gridy += 1;
c.weightx = WEIGHTX_DESC;
panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.num_terms"), JLabel.RIGHT), c);
panel.add(
new JLabel(MessageUtils.getLocalizedMessage("overview.label.num_terms"), JLabel.RIGHT), c);
c.gridx = GRIDX_VAL;
c.weightx = WEIGHTX_VAL;
@ -189,7 +193,8 @@ public final class OverviewPanelProvider {
c.gridx = GRIDX_DESC;
c.gridy += 1;
c.weightx = WEIGHTX_DESC;
panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.del_opt"), JLabel.RIGHT), c);
panel.add(
new JLabel(MessageUtils.getLocalizedMessage("overview.label.del_opt"), JLabel.RIGHT), c);
c.gridx = GRIDX_VAL;
c.weightx = WEIGHTX_VAL;
@ -199,7 +204,9 @@ public final class OverviewPanelProvider {
c.gridx = GRIDX_DESC;
c.gridy += 1;
c.weightx = WEIGHTX_DESC;
panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.index_version"), JLabel.RIGHT), c);
panel.add(
new JLabel(MessageUtils.getLocalizedMessage("overview.label.index_version"), JLabel.RIGHT),
c);
c.gridx = GRIDX_VAL;
c.weightx = WEIGHTX_VAL;
@ -209,7 +216,9 @@ public final class OverviewPanelProvider {
c.gridx = GRIDX_DESC;
c.gridy += 1;
c.weightx = WEIGHTX_DESC;
panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.index_format"), JLabel.RIGHT), c);
panel.add(
new JLabel(MessageUtils.getLocalizedMessage("overview.label.index_format"), JLabel.RIGHT),
c);
c.gridx = GRIDX_VAL;
c.weightx = WEIGHTX_VAL;
@ -219,7 +228,8 @@ public final class OverviewPanelProvider {
c.gridx = GRIDX_DESC;
c.gridy += 1;
c.weightx = WEIGHTX_DESC;
panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.dir_impl"), JLabel.RIGHT), c);
panel.add(
new JLabel(MessageUtils.getLocalizedMessage("overview.label.dir_impl"), JLabel.RIGHT), c);
c.gridx = GRIDX_VAL;
c.weightx = WEIGHTX_VAL;
@ -229,7 +239,9 @@ public final class OverviewPanelProvider {
c.gridx = GRIDX_DESC;
c.gridy += 1;
c.weightx = WEIGHTX_DESC;
panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.commit_point"), JLabel.RIGHT), c);
panel.add(
new JLabel(MessageUtils.getLocalizedMessage("overview.label.commit_point"), JLabel.RIGHT),
c);
c.gridx = GRIDX_VAL;
c.weightx = WEIGHTX_VAL;
@ -239,7 +251,10 @@ public final class OverviewPanelProvider {
c.gridx = GRIDX_DESC;
c.gridy += 1;
c.weightx = WEIGHTX_DESC;
panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.commit_userdata"), JLabel.RIGHT), c);
panel.add(
new JLabel(
MessageUtils.getLocalizedMessage("overview.label.commit_userdata"), JLabel.RIGHT),
c);
c.gridx = GRIDX_VAL;
c.weightx = WEIGHTX_VAL;
@ -257,7 +272,8 @@ public final class OverviewPanelProvider {
label.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
panel.add(label, BorderLayout.PAGE_START);
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, initTermCountsPanel(), initTopTermsPanel());
JSplitPane splitPane =
new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, initTermCountsPanel(), initTopTermsPanel());
splitPane.setOpaque(false);
splitPane.setDividerLocation(320);
splitPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
@ -274,13 +290,18 @@ public final class OverviewPanelProvider {
label.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0));
panel.add(label, BorderLayout.PAGE_START);
TableUtils.setupTable(termCountsTable, ListSelectionModel.SINGLE_SELECTION, new TermCountsTableModel(),
TableUtils.setupTable(
termCountsTable,
ListSelectionModel.SINGLE_SELECTION,
new TermCountsTableModel(),
new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
listeners.selectField(e);
}
}, TermCountsTableModel.Column.NAME.getColumnWidth(), TermCountsTableModel.Column.TERM_COUNT.getColumnWidth());
},
TermCountsTableModel.Column.NAME.getColumnWidth(),
TermCountsTableModel.Column.TERM_COUNT.getColumnWidth());
JScrollPane scrollPane = new JScrollPane(termCountsTable);
panel.add(scrollPane, BorderLayout.CENTER);
@ -344,13 +365,18 @@ public final class OverviewPanelProvider {
label.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0));
termsPanel.add(label, BorderLayout.PAGE_START);
TableUtils.setupTable(topTermsTable, ListSelectionModel.SINGLE_SELECTION, new TopTermsTableModel(),
TableUtils.setupTable(
topTermsTable,
ListSelectionModel.SINGLE_SELECTION,
new TopTermsTableModel(),
new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
listeners.showTopTermsContextMenu(e);
}
}, TopTermsTableModel.Column.RANK.getColumnWidth(), TopTermsTableModel.Column.FREQ.getColumnWidth());
},
TopTermsTableModel.Column.RANK.getColumnWidth(),
TopTermsTableModel.Column.FREQ.getColumnWidth());
JScrollPane scrollPane = new JScrollPane(topTermsTable);
termsPanel.add(scrollPane, BorderLayout.CENTER);
@ -364,11 +390,13 @@ public final class OverviewPanelProvider {
}
private void setUpTopTermsContextMenu() {
JMenuItem item1 = new JMenuItem(MessageUtils.getLocalizedMessage("overview.toptermtable.menu.item1"));
JMenuItem item1 =
new JMenuItem(MessageUtils.getLocalizedMessage("overview.toptermtable.menu.item1"));
item1.addActionListener(listeners::browseByTerm);
topTermsContextMenu.add(item1);
JMenuItem item2 = new JMenuItem(MessageUtils.getLocalizedMessage("overview.toptermtable.menu.item2"));
JMenuItem item2 =
new JMenuItem(MessageUtils.getLocalizedMessage("overview.toptermtable.menu.item2"));
item2.addActionListener(listeners::searchByTerm);
topTermsContextMenu.add(item2);
}
@ -388,27 +416,39 @@ public final class OverviewPanelProvider {
// update top terms table
topTermsTable.setModel(new TopTermsTableModel(termStats, numTerms));
topTermsTable.getColumnModel().getColumn(TopTermsTableModel.Column.RANK.getIndex()).setMaxWidth(TopTermsTableModel.Column.RANK.getColumnWidth());
topTermsTable.getColumnModel().getColumn(TopTermsTableModel.Column.FREQ.getIndex()).setMaxWidth(TopTermsTableModel.Column.FREQ.getColumnWidth());
topTermsTable
.getColumnModel()
.getColumn(TopTermsTableModel.Column.RANK.getIndex())
.setMaxWidth(TopTermsTableModel.Column.RANK.getColumnWidth());
topTermsTable
.getColumnModel()
.getColumn(TopTermsTableModel.Column.FREQ.getIndex())
.setMaxWidth(TopTermsTableModel.Column.FREQ.getColumnWidth());
messageBroker.clearStatusMessage();
}
private void browseByTerm() {
String field = getSelectedField();
String term = getSelectedTerm();
operatorRegistry.get(DocumentsTabOperator.class).ifPresent(operator -> {
operator.browseTerm(field, term);
tabSwitcher.switchTab(TabbedPaneProvider.Tab.DOCUMENTS);
});
operatorRegistry
.get(DocumentsTabOperator.class)
.ifPresent(
operator -> {
operator.browseTerm(field, term);
tabSwitcher.switchTab(TabbedPaneProvider.Tab.DOCUMENTS);
});
}
private void searchByTerm() {
String field = getSelectedField();
String term = getSelectedTerm();
operatorRegistry.get(SearchTabOperator.class).ifPresent(operator -> {
operator.searchByTerm(field, term);
tabSwitcher.switchTab(TabbedPaneProvider.Tab.SEARCH);
});
operatorRegistry
.get(SearchTabOperator.class)
.ifPresent(
operator -> {
operator.searchByTerm(field, term);
tabSwitcher.switchTab(TabbedPaneProvider.Tab.SEARCH);
});
}
private String getSelectedField() {
@ -419,7 +459,8 @@ public final class OverviewPanelProvider {
if (row < 0 || row >= termCountsTable.getRowCount()) {
throw new IllegalStateException("Field is not selected.");
}
return (String) termCountsTable.getModel().getValueAt(row, TermCountsTableModel.Column.NAME.getIndex());
return (String)
termCountsTable.getModel().getValueAt(row, TermCountsTableModel.Column.NAME.getIndex());
}
private String getSelectedTerm() {
@ -427,7 +468,8 @@ public final class OverviewPanelProvider {
if (rowTerm < 0 || rowTerm >= topTermsTable.getRowCount()) {
throw new IllegalStateException("Term is not selected.");
}
return (String) topTermsTable.getModel().getValueAt(rowTerm, TopTermsTableModel.Column.TEXT.getIndex());
return (String)
topTermsTable.getModel().getValueAt(rowTerm, TopTermsTableModel.Column.TEXT.getIndex());
}
private class ListenerFunctions {
@ -457,7 +499,6 @@ public final class OverviewPanelProvider {
void searchByTerm(ActionEvent e) {
OverviewPanelProvider.this.searchByTerm();
}
}
private class Observer implements IndexObserver {
@ -471,7 +512,10 @@ public final class OverviewPanelProvider {
numFieldsLbl.setText(Integer.toString(overviewModel.getNumFields()));
numDocsLbl.setText(Integer.toString(overviewModel.getNumDocuments()));
numTermsLbl.setText(Long.toString(overviewModel.getNumTerms()));
String del = overviewModel.hasDeletions() ? String.format(Locale.ENGLISH, "Yes (%d)", overviewModel.getNumDeletedDocs()) : "No";
String del =
overviewModel.hasDeletions()
? String.format(Locale.ENGLISH, "Yes (%d)", overviewModel.getNumDeletedDocs())
: "No";
String opt = overviewModel.isOptimized().map(b -> b ? "Yes" : "No").orElse("?");
delOptLbl.setText(del + " / " + opt);
indexVerLbl.setText(overviewModel.getIndexVersion().map(v -> Long.toString(v)).orElse("?"));
@ -485,16 +529,31 @@ public final class OverviewPanelProvider {
long numTerms = overviewModel.getNumTerms();
termCountsTable.setModel(new TermCountsTableModel(numTerms, termCounts));
termCountsTable.setRowSorter(new TableRowSorter<>(termCountsTable.getModel()));
termCountsTable.getColumnModel().getColumn(TermCountsTableModel.Column.NAME.getIndex()).setMaxWidth(TermCountsTableModel.Column.NAME.getColumnWidth());
termCountsTable.getColumnModel().getColumn(TermCountsTableModel.Column.TERM_COUNT.getIndex()).setMaxWidth(TermCountsTableModel.Column.TERM_COUNT.getColumnWidth());
termCountsTable
.getColumnModel()
.getColumn(TermCountsTableModel.Column.NAME.getIndex())
.setMaxWidth(TermCountsTableModel.Column.NAME.getColumnWidth());
termCountsTable
.getColumnModel()
.getColumn(TermCountsTableModel.Column.TERM_COUNT.getIndex())
.setMaxWidth(TermCountsTableModel.Column.TERM_COUNT.getColumnWidth());
DefaultTableCellRenderer rightRenderer = new DefaultTableCellRenderer();
rightRenderer.setHorizontalAlignment(JLabel.RIGHT);
termCountsTable.getColumnModel().getColumn(TermCountsTableModel.Column.RATIO.getIndex()).setCellRenderer(rightRenderer);
termCountsTable
.getColumnModel()
.getColumn(TermCountsTableModel.Column.RATIO.getIndex())
.setCellRenderer(rightRenderer);
// top terms table
topTermsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
topTermsTable.getColumnModel().getColumn(TopTermsTableModel.Column.RANK.getIndex()).setMaxWidth(TopTermsTableModel.Column.RANK.getColumnWidth());
topTermsTable.getColumnModel().getColumn(TopTermsTableModel.Column.FREQ.getIndex()).setMaxWidth(TopTermsTableModel.Column.FREQ.getColumnWidth());
topTermsTable
.getColumnModel()
.getColumn(TopTermsTableModel.Column.RANK.getIndex())
.setMaxWidth(TopTermsTableModel.Column.RANK.getColumnWidth());
topTermsTable
.getColumnModel()
.getColumn(TopTermsTableModel.Column.FREQ.getIndex())
.setMaxWidth(TopTermsTableModel.Column.FREQ.getColumnWidth());
topTermsTable.getColumnModel().setColumnMargin(StyleConstants.TABLE_COLUMN_MARGIN_DEFAULT);
}
@ -518,13 +577,11 @@ public final class OverviewPanelProvider {
termCountsTable.setModel(new TermCountsTableModel());
topTermsTable.setModel(new TopTermsTableModel());
}
}
static final class TermCountsTableModel extends TableModelBase<TermCountsTableModel.Column> {
enum Column implements TableColumnInfo {
NAME("Name", 0, String.class, 150),
TERM_COUNT("Term count", 1, Long.class, 100),
RATIO("%", 2, String.class, Integer.MAX_VALUE);
@ -572,7 +629,10 @@ public final class OverviewPanelProvider {
for (Map.Entry<String, Long> e : termCounts.entrySet()) {
String term = e.getKey();
Long count = e.getValue();
data[i++] = new Object[]{term, count, String.format(Locale.ENGLISH, "%.2f %%", count / numTerms * 100)};
data[i++] =
new Object[] {
term, count, String.format(Locale.ENGLISH, "%.2f %%", count / numTerms * 100)
};
}
}
@ -632,7 +692,7 @@ public final class OverviewPanelProvider {
int rank = i + 1;
int freq = termStats.get(i).getDocFreq();
String termText = termStats.get(i).getDecodedTermText();
data[i] = new Object[]{rank, freq, termText};
data[i] = new Object[] {rank, freq, termText};
}
}

View File

@ -17,21 +17,6 @@
package org.apache.lucene.luke.app.desktop.components;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
@ -51,7 +36,21 @@ import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.index.Term;
@ -192,7 +191,8 @@ public final class SearchPanelProvider implements SearchTabOperator {
panel.setOpaque(false);
panel.setBorder(BorderFactory.createLineBorder(Color.gray));
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel());
JSplitPane splitPane =
new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel());
splitPane.setOpaque(false);
splitPane.setDividerLocation(350);
panel.add(splitPane);
@ -201,7 +201,8 @@ public final class SearchPanelProvider implements SearchTabOperator {
}
private JSplitPane initUpperPanel() {
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, initQuerySettingsPane(), initQueryPane());
JSplitPane splitPane =
new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, initQuerySettingsPane(), initQueryPane());
splitPane.setOpaque(false);
splitPane.setDividerLocation(570);
return splitPane;
@ -263,7 +264,12 @@ public final class SearchPanelProvider implements SearchTabOperator {
c.gridwidth = 3;
c.weightx = 0.0;
c.insets = new Insets(2, 0, 2, 2);
panel.add(new JScrollPane(queryStringTA, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), c);
panel.add(
new JScrollPane(
queryStringTA,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER),
c);
JLabel labelPQ = new JLabel(MessageUtils.getLocalizedMessage("search.label.parsed"));
c.gridx = 0;
@ -283,7 +289,9 @@ public final class SearchPanelProvider implements SearchTabOperator {
c.insets = new Insets(2, 0, 2, 2);
panel.add(new JScrollPane(parsedQueryTA), c);
parseBtn.setText(FontUtils.elegantIconHtml("&#xe0df;", MessageUtils.getLocalizedMessage("search.button.parse")));
parseBtn.setText(
FontUtils.elegantIconHtml(
"&#xe0df;", MessageUtils.getLocalizedMessage("search.button.parse")));
parseBtn.setFont(StyleConstants.FONT_BUTTON_LARGE);
parseBtn.setMargin(new Insets(3, 0, 3, 0));
parseBtn.addActionListener(listeners::execParse);
@ -303,7 +311,9 @@ public final class SearchPanelProvider implements SearchTabOperator {
c.insets = new Insets(5, 0, 0, 2);
panel.add(rewriteCB, c);
searchBtn.setText(FontUtils.elegantIconHtml("&#x55;", MessageUtils.getLocalizedMessage("search.button.search")));
searchBtn.setText(
FontUtils.elegantIconHtml(
"&#x55;", MessageUtils.getLocalizedMessage("search.button.search")));
searchBtn.setFont(StyleConstants.FONT_BUTTON_LARGE);
searchBtn.setMargin(new Insets(3, 0, 3, 0));
searchBtn.addActionListener(listeners::execSearch);
@ -323,7 +333,9 @@ public final class SearchPanelProvider implements SearchTabOperator {
c.insets = new Insets(5, 0, 0, 2);
panel.add(exactHitsCntCB, c);
mltBtn.setText(FontUtils.elegantIconHtml("&#xe030;", MessageUtils.getLocalizedMessage("search.button.mlt")));
mltBtn.setText(
FontUtils.elegantIconHtml(
"&#xe030;", MessageUtils.getLocalizedMessage("search.button.mlt")));
mltBtn.setFont(StyleConstants.FONT_BUTTON_LARGE);
mltBtn.setMargin(new Insets(3, 0, 3, 0));
mltBtn.addActionListener(listeners::execMLTSearch);
@ -366,7 +378,10 @@ public final class SearchPanelProvider implements SearchTabOperator {
JPanel panel = new JPanel(new GridLayout(1, 2));
panel.setOpaque(false);
JLabel label = new JLabel(FontUtils.elegantIconHtml("&#xe025;", MessageUtils.getLocalizedMessage("search.label.results")));
JLabel label =
new JLabel(
FontUtils.elegantIconHtml(
"&#xe025;", MessageUtils.getLocalizedMessage("search.label.results")));
label.setHorizontalTextPosition(JLabel.LEFT);
label.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0));
panel.add(label);
@ -407,7 +422,9 @@ public final class SearchPanelProvider implements SearchTabOperator {
sep.setPreferredSize(new Dimension(5, 1));
resultsInfo.add(sep);
delBtn.setText(FontUtils.elegantIconHtml("&#xe07d;", MessageUtils.getLocalizedMessage("search.button.del_all")));
delBtn.setText(
FontUtils.elegantIconHtml(
"&#xe07d;", MessageUtils.getLocalizedMessage("search.button.del_all")));
delBtn.setMargin(new Insets(5, 0, 5, 0));
delBtn.setEnabled(false);
delBtn.addActionListener(listeners::confirmDeletion);
@ -427,7 +444,10 @@ public final class SearchPanelProvider implements SearchTabOperator {
note.add(new JLabel(MessageUtils.getLocalizedMessage("search.label.results.note")));
panel.add(note, BorderLayout.PAGE_START);
TableUtils.setupTable(resultsTable, ListSelectionModel.SINGLE_SELECTION, new SearchResultsTableModel(),
TableUtils.setupTable(
resultsTable,
ListSelectionModel.SINGLE_SELECTION,
new SearchResultsTableModel(),
new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
@ -456,10 +476,10 @@ public final class SearchPanelProvider implements SearchTabOperator {
tabbedPane.setEnabledAt(Tab.QPARSER.index(), false);
tabbedPane.setEnabledAt(Tab.ANALYZER.index(), false);
tabbedPane.setEnabledAt(Tab.SIMILARITY.index(), false);
if (tabbedPane.getSelectedIndex() == Tab.QPARSER.index() ||
tabbedPane.getSelectedIndex() == Tab.ANALYZER.index() ||
tabbedPane.getSelectedIndex() == Tab.SIMILARITY.index() ||
tabbedPane.getSelectedIndex() == Tab.MLT.index()) {
if (tabbedPane.getSelectedIndex() == Tab.QPARSER.index()
|| tabbedPane.getSelectedIndex() == Tab.ANALYZER.index()
|| tabbedPane.getSelectedIndex() == Tab.SIMILARITY.index()
|| tabbedPane.getSelectedIndex() == Tab.MLT.index()) {
tabbedPane.setSelectedIndex(Tab.SORT.index());
}
parseBtn.setEnabled(false);
@ -489,24 +509,34 @@ public final class SearchPanelProvider implements SearchTabOperator {
}
String[] tmp = queryStringTA.getText().split(":");
if (tmp.length < 2) {
throw new LukeException(String.format(Locale.ENGLISH, "Invalid query [ %s ]", queryStringTA.getText()));
throw new LukeException(
String.format(Locale.ENGLISH, "Invalid query [ %s ]", queryStringTA.getText()));
}
query = new TermQuery(new Term(tmp[0].trim(), tmp[1].trim()));
} else {
query = parse(false);
}
SimilarityConfig simConfig = operatorRegistry.get(SimilarityTabOperator.class)
.map(SimilarityTabOperator::getConfig)
.orElse(new SimilarityConfig.Builder().build());
Sort sort = operatorRegistry.get(SortTabOperator.class)
.map(SortTabOperator::getSort)
.orElse(null);
Set<String> fieldsToLoad = operatorRegistry.get(FieldValuesTabOperator.class)
.map(FieldValuesTabOperator::getFieldsToLoad)
.orElse(Collections.emptySet());
SearchResults results = searchModel.search(query, simConfig, sort, fieldsToLoad, DEFAULT_PAGE_SIZE, exactHitsCntCB.isSelected());
SimilarityConfig simConfig =
operatorRegistry
.get(SimilarityTabOperator.class)
.map(SimilarityTabOperator::getConfig)
.orElse(new SimilarityConfig.Builder().build());
Sort sort =
operatorRegistry.get(SortTabOperator.class).map(SortTabOperator::getSort).orElse(null);
Set<String> fieldsToLoad =
operatorRegistry
.get(FieldValuesTabOperator.class)
.map(FieldValuesTabOperator::getFieldsToLoad)
.orElse(Collections.emptySet());
SearchResults results =
searchModel.search(
query, simConfig, sort, fieldsToLoad, DEFAULT_PAGE_SIZE, exactHitsCntCB.isSelected());
TableUtils.setupTable(resultsTable, ListSelectionModel.SINGLE_SELECTION, new SearchResultsTableModel(), null,
TableUtils.setupTable(
resultsTable,
ListSelectionModel.SINGLE_SELECTION,
new SearchResultsTableModel(),
null,
SearchResultsTableModel.Column.DOCID.getColumnWidth(),
SearchResultsTableModel.Column.SCORE.getColumnWidth());
populateResults(results);
@ -529,19 +559,31 @@ public final class SearchPanelProvider implements SearchTabOperator {
throw new LukeException("Doc num is not set.");
}
int docNum = (int) mltDocFTF.getValue();
MLTConfig mltConfig = operatorRegistry.get(MLTTabOperator.class)
.map(MLTTabOperator::getConfig)
.orElse(new MLTConfig.Builder().build());
Analyzer analyzer = operatorRegistry.get(AnalysisTabOperator.class)
.map(AnalysisTabOperator::getCurrentAnalyzer)
.orElse(new StandardAnalyzer());
MLTConfig mltConfig =
operatorRegistry
.get(MLTTabOperator.class)
.map(MLTTabOperator::getConfig)
.orElse(new MLTConfig.Builder().build());
Analyzer analyzer =
operatorRegistry
.get(AnalysisTabOperator.class)
.map(AnalysisTabOperator::getCurrentAnalyzer)
.orElse(new StandardAnalyzer());
Query query = searchModel.mltQuery(docNum, mltConfig, analyzer);
Set<String> fieldsToLoad = operatorRegistry.get(FieldValuesTabOperator.class)
.map(FieldValuesTabOperator::getFieldsToLoad)
.orElse(Collections.emptySet());
SearchResults results = searchModel.search(query, new SimilarityConfig.Builder().build(), fieldsToLoad, DEFAULT_PAGE_SIZE, false);
Set<String> fieldsToLoad =
operatorRegistry
.get(FieldValuesTabOperator.class)
.map(FieldValuesTabOperator::getFieldsToLoad)
.orElse(Collections.emptySet());
SearchResults results =
searchModel.search(
query, new SimilarityConfig.Builder().build(), fieldsToLoad, DEFAULT_PAGE_SIZE, false);
TableUtils.setupTable(resultsTable, ListSelectionModel.SINGLE_SELECTION, new SearchResultsTableModel(), null,
TableUtils.setupTable(
resultsTable,
ListSelectionModel.SINGLE_SELECTION,
new SearchResultsTableModel(),
null,
SearchResultsTableModel.Column.DOCID.getColumnWidth(),
SearchResultsTableModel.Column.SCORE.getColumnWidth());
populateResults(results);
@ -550,16 +592,23 @@ public final class SearchPanelProvider implements SearchTabOperator {
}
private Query parse(boolean rewrite) {
String expr = StringUtils.isNullOrEmpty(queryStringTA.getText()) ? "*:*" : queryStringTA.getText();
String df = operatorRegistry.get(QueryParserTabOperator.class)
.map(QueryParserTabOperator::getDefaultField)
.orElse("");
QueryParserConfig config = operatorRegistry.get(QueryParserTabOperator.class)
.map(QueryParserTabOperator::getConfig)
.orElse(new QueryParserConfig.Builder().build());
Analyzer analyzer = operatorRegistry.get(AnalysisTabOperator.class)
.map(AnalysisTabOperator::getCurrentAnalyzer)
.orElse(new StandardAnalyzer());
String expr =
StringUtils.isNullOrEmpty(queryStringTA.getText()) ? "*:*" : queryStringTA.getText();
String df =
operatorRegistry
.get(QueryParserTabOperator.class)
.map(QueryParserTabOperator::getDefaultField)
.orElse("");
QueryParserConfig config =
operatorRegistry
.get(QueryParserTabOperator.class)
.map(QueryParserTabOperator::getConfig)
.orElse(new QueryParserConfig.Builder().build());
Analyzer analyzer =
operatorRegistry
.get(AnalysisTabOperator.class)
.map(AnalysisTabOperator::getCurrentAnalyzer)
.orElse(new StandardAnalyzer());
return searchModel.parseQuery(expr, df, analyzer, config, rewrite);
}
@ -570,16 +619,27 @@ public final class SearchPanelProvider implements SearchTabOperator {
endLbl.setText(String.valueOf(res.getOffset() + res.size()));
prevBtn.setEnabled(res.getOffset() > 0);
nextBtn.setEnabled(res.getTotalHits().relation == TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO || res.getTotalHits().value > res.getOffset() + res.size());
nextBtn.setEnabled(
res.getTotalHits().relation == TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO
|| res.getTotalHits().value > res.getOffset() + res.size());
if (!indexHandler.getState().readOnly() && indexHandler.getState().hasDirectoryReader()) {
delBtn.setEnabled(true);
}
resultsTable.setModel(new SearchResultsTableModel(res));
resultsTable.getColumnModel().getColumn(SearchResultsTableModel.Column.DOCID.getIndex()).setPreferredWidth(SearchResultsTableModel.Column.DOCID.getColumnWidth());
resultsTable.getColumnModel().getColumn(SearchResultsTableModel.Column.SCORE.getIndex()).setPreferredWidth(SearchResultsTableModel.Column.SCORE.getColumnWidth());
resultsTable.getColumnModel().getColumn(SearchResultsTableModel.Column.VALUE.getIndex()).setPreferredWidth(SearchResultsTableModel.Column.VALUE.getColumnWidth());
resultsTable
.getColumnModel()
.getColumn(SearchResultsTableModel.Column.DOCID.getIndex())
.setPreferredWidth(SearchResultsTableModel.Column.DOCID.getColumnWidth());
resultsTable
.getColumnModel()
.getColumn(SearchResultsTableModel.Column.SCORE.getIndex())
.setPreferredWidth(SearchResultsTableModel.Column.SCORE.getColumnWidth());
resultsTable
.getColumnModel()
.getColumn(SearchResultsTableModel.Column.VALUE.getIndex())
.setPreferredWidth(SearchResultsTableModel.Column.VALUE.getColumnWidth());
} else {
startLbl.setText("0");
endLbl.setText("0");
@ -590,10 +650,15 @@ public final class SearchPanelProvider implements SearchTabOperator {
}
private void confirmDeletion() {
new DialogOpener<>(confirmDialogFactory).open("Confirm Deletion", 400, 200, (factory) -> {
factory.setMessage(MessageUtils.getLocalizedMessage("search.message.delete_confirm"));
factory.setCallback(this::deleteDocs);
});
new DialogOpener<>(confirmDialogFactory)
.open(
"Confirm Deletion",
400,
200,
(factory) -> {
factory.setMessage(MessageUtils.getLocalizedMessage("search.message.delete_confirm"));
factory.setCallback(this::deleteDocs);
});
}
private void deleteDocs() {
@ -601,7 +666,8 @@ public final class SearchPanelProvider implements SearchTabOperator {
if (query != null) {
toolsModel.deleteDocuments(query);
indexHandler.reOpen();
messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("search.message.delete_success", query.toString()));
messageBroker.showStatusMessage(
MessageUtils.getLocalizedMessage("search.message.delete_success", query.toString()));
}
delBtn.setEnabled(false);
}
@ -610,25 +676,47 @@ public final class SearchPanelProvider implements SearchTabOperator {
JPopupMenu popup = new JPopupMenu();
// show explanation
JMenuItem item1 = new JMenuItem(MessageUtils.getLocalizedMessage("search.results.menu.explain"));
item1.addActionListener(e -> {
int docid = (int) resultsTable.getModel().getValueAt(resultsTable.getSelectedRow(), SearchResultsTableModel.Column.DOCID.getIndex());
Explanation explanation = searchModel.explain(parse(false), docid);
new DialogOpener<>(explainDialogProvider).open("Explanation", 600, 400,
(factory) -> {
factory.setDocid(docid);
factory.setExplanation(explanation);
});
});
JMenuItem item1 =
new JMenuItem(MessageUtils.getLocalizedMessage("search.results.menu.explain"));
item1.addActionListener(
e -> {
int docid =
(int)
resultsTable
.getModel()
.getValueAt(
resultsTable.getSelectedRow(),
SearchResultsTableModel.Column.DOCID.getIndex());
Explanation explanation = searchModel.explain(parse(false), docid);
new DialogOpener<>(explainDialogProvider)
.open(
"Explanation",
600,
400,
(factory) -> {
factory.setDocid(docid);
factory.setExplanation(explanation);
});
});
popup.add(item1);
// show all fields
JMenuItem item2 = new JMenuItem(MessageUtils.getLocalizedMessage("search.results.menu.showdoc"));
item2.addActionListener(e -> {
int docid = (int) resultsTable.getModel().getValueAt(resultsTable.getSelectedRow(), SearchResultsTableModel.Column.DOCID.getIndex());
operatorRegistry.get(DocumentsTabOperator.class).ifPresent(operator -> operator.displayDoc(docid));
tabSwitcher.switchTab(TabbedPaneProvider.Tab.DOCUMENTS);
});
JMenuItem item2 =
new JMenuItem(MessageUtils.getLocalizedMessage("search.results.menu.showdoc"));
item2.addActionListener(
e -> {
int docid =
(int)
resultsTable
.getModel()
.getValueAt(
resultsTable.getSelectedRow(),
SearchResultsTableModel.Column.DOCID.getIndex());
operatorRegistry
.get(DocumentsTabOperator.class)
.ifPresent(operator -> operator.displayDoc(docid));
tabSwitcher.switchTab(TabbedPaneProvider.Tab.DOCUMENTS);
});
popup.add(item2);
return popup;
@ -691,11 +779,12 @@ public final class SearchPanelProvider implements SearchTabOperator {
void showContextMenuInResultsTable(MouseEvent e) {
if (e.getClickCount() == 2 && !e.isConsumed()) {
SearchPanelProvider.this.setupResultsContextMenuPopup().show(e.getComponent(), e.getX(), e.getY());
SearchPanelProvider.this
.setupResultsContextMenuPopup()
.show(e.getComponent(), e.getX(), e.getY());
setupResultsContextMenuPopup().show(e.getComponent(), e.getX(), e.getY());
}
}
}
private class Observer implements IndexObserver {
@ -703,21 +792,35 @@ public final class SearchPanelProvider implements SearchTabOperator {
@Override
public void openIndex(LukeState state) {
searchModel = searchFactory.newInstance(state.getIndexReader());
toolsModel = toolsFactory.newInstance(state.getIndexReader(), state.useCompound(), state.keepAllCommits());
operatorRegistry.get(QueryParserTabOperator.class).ifPresent(operator -> {
operator.setSearchableFields(searchModel.getSearchableFieldNames());
operator.setRangeSearchableFields(searchModel.getRangeSearchableFieldNames());
});
operatorRegistry.get(SortTabOperator.class).ifPresent(operator -> {
operator.setSearchModel(searchModel);
operator.setSortableFields(searchModel.getSortableFieldNames());
});
operatorRegistry.get(FieldValuesTabOperator.class).ifPresent(operator -> {
operator.setFields(searchModel.getFieldNames());
});
operatorRegistry.get(MLTTabOperator.class).ifPresent(operator -> {
operator.setFields(searchModel.getFieldNames());
});
toolsModel =
toolsFactory.newInstance(
state.getIndexReader(), state.useCompound(), state.keepAllCommits());
operatorRegistry
.get(QueryParserTabOperator.class)
.ifPresent(
operator -> {
operator.setSearchableFields(searchModel.getSearchableFieldNames());
operator.setRangeSearchableFields(searchModel.getRangeSearchableFieldNames());
});
operatorRegistry
.get(SortTabOperator.class)
.ifPresent(
operator -> {
operator.setSearchModel(searchModel);
operator.setSortableFields(searchModel.getSortableFieldNames());
});
operatorRegistry
.get(FieldValuesTabOperator.class)
.ifPresent(
operator -> {
operator.setFields(searchModel.getFieldNames());
});
operatorRegistry
.get(MLTTabOperator.class)
.ifPresent(
operator -> {
operator.setFields(searchModel.getFieldNames());
});
queryStringTA.setText("*:*");
parsedQueryTA.setText("");
@ -742,16 +845,24 @@ public final class SearchPanelProvider implements SearchTabOperator {
nextBtn.setEnabled(false);
prevBtn.setEnabled(false);
delBtn.setEnabled(false);
TableUtils.setupTable(resultsTable, ListSelectionModel.SINGLE_SELECTION, new SearchResultsTableModel(), null,
TableUtils.setupTable(
resultsTable,
ListSelectionModel.SINGLE_SELECTION,
new SearchResultsTableModel(),
null,
SearchResultsTableModel.Column.DOCID.getColumnWidth(),
SearchResultsTableModel.Column.SCORE.getColumnWidth());
}
}
/** tabs in the Search panel */
public enum Tab {
QPARSER(0), ANALYZER(1), SIMILARITY(2), SORT(3), VALUES(4), MLT(5);
QPARSER(0),
ANALYZER(1),
SIMILARITY(2),
SORT(3),
VALUES(4),
MLT(5);
private int tabIdx;
@ -764,7 +875,8 @@ public final class SearchPanelProvider implements SearchTabOperator {
}
}
static final class SearchResultsTableModel extends TableModelBase<SearchResultsTableModel.Column> {
static final class SearchResultsTableModel
extends TableModelBase<SearchResultsTableModel.Column> {
enum Column implements TableColumnInfo {
DOCID("Doc ID", 0, Integer.class, 50),
@ -818,10 +930,14 @@ public final class SearchPanelProvider implements SearchTabOperator {
} else {
data[i][Column.SCORE.getIndex()] = 1.0f;
}
List<String> concatValues = doc.getFieldValues().entrySet().stream().map(e -> {
String v = String.join(",", Arrays.asList(e.getValue()));
return e.getKey() + "=" + v + ";";
}).collect(Collectors.toList());
List<String> concatValues =
doc.getFieldValues().entrySet().stream()
.map(
e -> {
String v = String.join(",", Arrays.asList(e.getValue()));
return e.getKey() + "=" + v + ";";
})
.collect(Collectors.toList());
data[i][Column.VALUE.getIndex()] = String.join(" ", concatValues);
}
}

View File

@ -45,5 +45,4 @@ public class TabSwitcherProxy {
public interface TabSwitcher {
void switchTab(TabbedPaneProvider.Tab tab);
}
}

View File

@ -17,11 +17,10 @@
package org.apache.lucene.luke.app.desktop.components;
import java.io.IOException;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import java.io.IOException;
import org.apache.lucene.luke.app.DirectoryHandler;
import org.apache.lucene.luke.app.DirectoryObserver;
import org.apache.lucene.luke.app.IndexHandler;
@ -121,7 +120,11 @@ public final class TabbedPaneProvider implements TabSwitcherProxy.TabSwitcher {
/** tabs in the main frame */
public enum Tab {
OVERVIEW(0), DOCUMENTS(1), SEARCH(2), ANALYZER(3), COMMITS(4);
OVERVIEW(0),
DOCUMENTS(1),
SEARCH(2),
ANALYZER(3),
COMMITS(4);
private int tabIdx;
@ -133,5 +136,4 @@ public final class TabbedPaneProvider implements TabSwitcherProxy.TabSwitcher {
return tabIdx;
}
}
}

View File

@ -29,5 +29,4 @@ public interface TableColumnInfo {
default int getColumnWidth() {
return 0;
}
}

View File

@ -17,12 +17,14 @@
package org.apache.lucene.luke.app.desktop.components;
import javax.swing.table.AbstractTableModel;
import java.util.Map;
import javax.swing.table.AbstractTableModel;
import org.apache.lucene.luke.app.desktop.util.TableUtils;
/** Base table model that stores table's meta data and content. This also provides some default implementation of the {@link javax.swing.table.TableModel} interface. */
/**
* Base table model that stores table's meta data and content. This also provides some default
* implementation of the {@link javax.swing.table.TableModel} interface.
*/
public abstract class TableModelBase<T extends TableColumnInfo> extends AbstractTableModel {
private final Map<Integer, T> columnMap = TableUtils.columnMap(columnInfos());
@ -67,7 +69,6 @@ public abstract class TableModelBase<T extends TableColumnInfo> extends Abstract
return Object.class;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return data[rowIndex][columnIndex];

View File

@ -17,11 +17,6 @@
package org.apache.lucene.luke.app.desktop.components.dialog;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dialog;
@ -31,7 +26,11 @@ import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Window;
import java.io.IOException;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import org.apache.lucene.luke.app.desktop.Preferences;
import org.apache.lucene.luke.app.desktop.PreferencesFactory;
import org.apache.lucene.luke.app.desktop.util.DialogOpener;
@ -52,7 +51,7 @@ public final class ConfirmDialogFactory implements DialogOpener.DialogFactory {
private Callable callback;
public synchronized static ConfirmDialogFactory getInstance() throws IOException {
public static synchronized ConfirmDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new ConfirmDialogFactory();
}
@ -103,10 +102,11 @@ public final class ConfirmDialogFactory implements DialogOpener.DialogFactory {
JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING));
footer.setOpaque(false);
JButton okBtn = new JButton(MessageUtils.getLocalizedMessage("button.ok"));
okBtn.addActionListener(e -> {
callback.call();
dialog.dispose();
});
okBtn.addActionListener(
e -> {
callback.call();
dialog.dispose();
});
footer.add(okBtn);
JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close"));
closeBtn.addActionListener(e -> dialog.dispose());
@ -115,5 +115,4 @@ public final class ConfirmDialogFactory implements DialogOpener.DialogFactory {
return panel;
}
}

View File

@ -17,12 +17,6 @@
package org.apache.lucene.luke.app.desktop.components.dialog;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.Dialog;
import java.awt.Dimension;
@ -30,7 +24,12 @@ import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Window;
import java.io.IOException;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import org.apache.lucene.luke.app.desktop.Preferences;
import org.apache.lucene.luke.app.desktop.PreferencesFactory;
import org.apache.lucene.luke.app.desktop.util.DialogOpener;
@ -49,7 +48,7 @@ public final class HelpDialogFactory implements DialogOpener.DialogFactory {
private JComponent helpContent;
public synchronized static HelpDialogFactory getInstance() throws IOException {
public static synchronized HelpDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new HelpDialogFactory();
}

View File

@ -17,14 +17,6 @@
package org.apache.lucene.luke.app.desktop.components.dialog.analysis;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dialog;
@ -35,11 +27,18 @@ import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Window;
import java.io.IOException;
import org.apache.lucene.analysis.custom.CustomAnalyzer;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import org.apache.lucene.analysis.CharFilterFactory;
import org.apache.lucene.analysis.TokenFilterFactory;
import org.apache.lucene.analysis.TokenizerFactory;
import org.apache.lucene.analysis.custom.CustomAnalyzer;
import org.apache.lucene.luke.app.desktop.Preferences;
import org.apache.lucene.luke.app.desktop.PreferencesFactory;
import org.apache.lucene.luke.app.desktop.util.DialogOpener;
@ -56,7 +55,7 @@ public class AnalysisChainDialogFactory implements DialogOpener.DialogFactory {
private CustomAnalyzer analyzer;
public synchronized static AnalysisChainDialogFactory getInstance() throws IOException {
public static synchronized AnalysisChainDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new AnalysisChainDialogFactory();
}
@ -110,11 +109,16 @@ public class AnalysisChainDialogFactory implements DialogOpener.DialogFactory {
c.gridy = 0;
c.weightx = 0.1;
c.weighty = 0.5;
panel.add(new JLabel(MessageUtils.getLocalizedMessage("analysis.dialog.chain.label.charfilters")), c);
panel.add(
new JLabel(MessageUtils.getLocalizedMessage("analysis.dialog.chain.label.charfilters")), c);
String[] charFilters = analyzer.getCharFilterFactories().stream().map(f -> CharFilterFactory.findSPIName(f.getClass())).toArray(String[]::new);
String[] charFilters =
analyzer.getCharFilterFactories().stream()
.map(f -> CharFilterFactory.findSPIName(f.getClass()))
.toArray(String[]::new);
JList<String> charFilterList = new JList<>(charFilters);
charFilterList.setVisibleRowCount(charFilters.length == 0 ? 1 : Math.min(charFilters.length, 5));
charFilterList.setVisibleRowCount(
charFilters.length == 0 ? 1 : Math.min(charFilters.length, 5));
c.gridx = 1;
c.gridy = 0;
c.weightx = 0.5;
@ -125,7 +129,8 @@ public class AnalysisChainDialogFactory implements DialogOpener.DialogFactory {
c.gridy = 1;
c.weightx = 0.1;
c.weighty = 0.1;
panel.add(new JLabel(MessageUtils.getLocalizedMessage("analysis.dialog.chain.label.tokenizer")), c);
panel.add(
new JLabel(MessageUtils.getLocalizedMessage("analysis.dialog.chain.label.tokenizer")), c);
String tokenizer = TokenizerFactory.findSPIName(analyzer.getTokenizerFactory().getClass());
JTextField tokenizerTF = new JTextField(tokenizer);
@ -143,11 +148,17 @@ public class AnalysisChainDialogFactory implements DialogOpener.DialogFactory {
c.gridy = 2;
c.weightx = 0.1;
c.weighty = 0.5;
panel.add(new JLabel(MessageUtils.getLocalizedMessage("analysis.dialog.chain.label.tokenfilters")), c);
panel.add(
new JLabel(MessageUtils.getLocalizedMessage("analysis.dialog.chain.label.tokenfilters")),
c);
String[] tokenFilters = analyzer.getTokenFilterFactories().stream().map(f -> TokenFilterFactory.findSPIName(f.getClass())).toArray(String[]::new);
String[] tokenFilters =
analyzer.getTokenFilterFactories().stream()
.map(f -> TokenFilterFactory.findSPIName(f.getClass()))
.toArray(String[]::new);
JList<String> tokenFilterList = new JList<>(tokenFilters);
tokenFilterList.setVisibleRowCount(tokenFilters.length == 0 ? 1 : Math.min(tokenFilters.length, 5));
tokenFilterList.setVisibleRowCount(
tokenFilters.length == 0 ? 1 : Math.min(tokenFilters.length, 5));
tokenFilterList.setMinimumSize(new Dimension(300, 25));
c.gridx = 1;
c.gridy = 2;
@ -157,5 +168,4 @@ public class AnalysisChainDialogFactory implements DialogOpener.DialogFactory {
return panel;
}
}

View File

@ -17,15 +17,6 @@
package org.apache.lucene.luke.app.desktop.components.dialog.analysis;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.table.TableCellRenderer;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dialog;
@ -39,7 +30,15 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.table.TableCellRenderer;
import org.apache.lucene.luke.app.desktop.Preferences;
import org.apache.lucene.luke.app.desktop.PreferencesFactory;
import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry;
@ -78,7 +77,7 @@ public final class EditFiltersDialogFactory implements DialogOpener.DialogFactor
private EditFiltersMode mode;
public synchronized static EditFiltersDialogFactory getInstance() throws IOException {
public static synchronized EditFiltersDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new EditFiltersDialogFactory();
}
@ -124,37 +123,49 @@ public final class EditFiltersDialogFactory implements DialogOpener.DialogFactor
header.add(targetLbl);
panel.add(header, BorderLayout.PAGE_START);
TableUtils.setupTable(filtersTable, ListSelectionModel.SINGLE_SELECTION, new FiltersTableModel(selectedFilters), tableListener,
TableUtils.setupTable(
filtersTable,
ListSelectionModel.SINGLE_SELECTION,
new FiltersTableModel(selectedFilters),
tableListener,
FiltersTableModel.Column.DELETE.getColumnWidth(),
FiltersTableModel.Column.ORDER.getColumnWidth());
filtersTable.setShowGrid(true);
filtersTable.getColumnModel().getColumn(FiltersTableModel.Column.TYPE.getIndex()).setCellRenderer(new TypeCellRenderer());
filtersTable
.getColumnModel()
.getColumn(FiltersTableModel.Column.TYPE.getIndex())
.setCellRenderer(new TypeCellRenderer());
panel.add(new JScrollPane(filtersTable), BorderLayout.CENTER);
JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING, 10, 5));
footer.setOpaque(false);
JButton okBtn = new JButton(MessageUtils.getLocalizedMessage("button.ok"));
okBtn.addActionListener(e -> {
List<Integer> deletedIndexes = new ArrayList<>();
for (int i = 0; i < filtersTable.getRowCount(); i++) {
boolean deleted = (boolean) filtersTable.getValueAt(i, FiltersTableModel.Column.DELETE.getIndex());
if (deleted) {
deletedIndexes.add(i);
}
}
operatorRegistry.get(CustomAnalyzerPanelOperator.class).ifPresent(operator -> {
switch (mode) {
case CHARFILTER:
operator.updateCharFilters(deletedIndexes);
break;
case TOKENFILTER:
operator.updateTokenFilters(deletedIndexes);
break;
}
});
callback.call();
dialog.dispose();
});
okBtn.addActionListener(
e -> {
List<Integer> deletedIndexes = new ArrayList<>();
for (int i = 0; i < filtersTable.getRowCount(); i++) {
boolean deleted =
(boolean) filtersTable.getValueAt(i, FiltersTableModel.Column.DELETE.getIndex());
if (deleted) {
deletedIndexes.add(i);
}
}
operatorRegistry
.get(CustomAnalyzerPanelOperator.class)
.ifPresent(
operator -> {
switch (mode) {
case CHARFILTER:
operator.updateCharFilters(deletedIndexes);
break;
case TOKENFILTER:
operator.updateTokenFilters(deletedIndexes);
break;
}
});
callback.call();
dialog.dispose();
});
footer.add(okBtn);
JButton cancelBtn = new JButton(MessageUtils.getLocalizedMessage("button.cancel"));
cancelBtn.addActionListener(e -> dialog.dispose());
@ -188,28 +199,48 @@ public final class EditFiltersDialogFactory implements DialogOpener.DialogFactor
private void showEditParamsCharFilterDialog(int selectedIndex) {
int targetIndex = filtersTable.getSelectedRow();
String selectedItem = (String) filtersTable.getValueAt(selectedIndex, FiltersTableModel.Column.TYPE.getIndex());
Map<String, String> params = operatorRegistry.get(CustomAnalyzerPanelOperator.class).map(operator -> operator.getCharFilterParams(targetIndex)).orElse(Collections.emptyMap());
new DialogOpener<>(editParamsDialogFactory).open(dialog, MessageUtils.getLocalizedMessage("analysis.dialog.title.char_filter_params"), 400, 300,
factory -> {
factory.setMode(EditParamsMode.CHARFILTER);
factory.setTargetIndex(targetIndex);
factory.setTarget(selectedItem);
factory.setParams(params);
});
String selectedItem =
(String) filtersTable.getValueAt(selectedIndex, FiltersTableModel.Column.TYPE.getIndex());
Map<String, String> params =
operatorRegistry
.get(CustomAnalyzerPanelOperator.class)
.map(operator -> operator.getCharFilterParams(targetIndex))
.orElse(Collections.emptyMap());
new DialogOpener<>(editParamsDialogFactory)
.open(
dialog,
MessageUtils.getLocalizedMessage("analysis.dialog.title.char_filter_params"),
400,
300,
factory -> {
factory.setMode(EditParamsMode.CHARFILTER);
factory.setTargetIndex(targetIndex);
factory.setTarget(selectedItem);
factory.setParams(params);
});
}
private void showEditParamsTokenFilterDialog(int selectedIndex) {
int targetIndex = filtersTable.getSelectedRow();
String selectedItem = (String) filtersTable.getValueAt(selectedIndex, FiltersTableModel.Column.TYPE.getIndex());
Map<String, String> params = operatorRegistry.get(CustomAnalyzerPanelOperator.class).map(operator -> operator.getTokenFilterParams(targetIndex)).orElse(Collections.emptyMap());
new DialogOpener<>(editParamsDialogFactory).open(dialog, MessageUtils.getLocalizedMessage("analysis.dialog.title.char_filter_params"), 400, 300,
factory -> {
factory.setMode(EditParamsMode.TOKENFILTER);
factory.setTargetIndex(targetIndex);
factory.setTarget(selectedItem);
factory.setParams(params);
});
String selectedItem =
(String) filtersTable.getValueAt(selectedIndex, FiltersTableModel.Column.TYPE.getIndex());
Map<String, String> params =
operatorRegistry
.get(CustomAnalyzerPanelOperator.class)
.map(operator -> operator.getTokenFilterParams(targetIndex))
.orElse(Collections.emptyMap());
new DialogOpener<>(editParamsDialogFactory)
.open(
dialog,
MessageUtils.getLocalizedMessage("analysis.dialog.title.char_filter_params"),
400,
300,
factory -> {
factory.setMode(EditParamsMode.TOKENFILTER);
factory.setTargetIndex(targetIndex);
factory.setTarget(selectedItem);
factory.setParams(params);
});
}
}
@ -292,12 +323,11 @@ public final class EditFiltersDialogFactory implements DialogOpener.DialogFactor
static final class TypeCellRenderer implements TableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
String[] tmp = ((String) value).split("\\.");
String type = tmp[tmp.length - 1];
return new JLabel(type);
}
}
}

View File

@ -19,5 +19,6 @@ package org.apache.lucene.luke.app.desktop.components.dialog.analysis;
/** Edit filters mode */
public enum EditFiltersMode {
CHARFILTER, TOKENFILTER;
CHARFILTER,
TOKENFILTER;
}

View File

@ -17,14 +17,6 @@
package org.apache.lucene.luke.app.desktop.components.dialog.analysis;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import java.awt.BorderLayout;
import java.awt.Dialog;
import java.awt.Dimension;
@ -36,7 +28,14 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import org.apache.lucene.luke.app.desktop.Preferences;
import org.apache.lucene.luke.app.desktop.PreferencesFactory;
import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry;
@ -71,7 +70,7 @@ public final class EditParamsDialogFactory implements DialogOpener.DialogFactory
private Callable callback;
public synchronized static EditParamsDialogFactory getInstance() throws IOException {
public static synchronized EditParamsDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new EditParamsDialogFactory();
}
@ -126,7 +125,11 @@ public final class EditParamsDialogFactory implements DialogOpener.DialogFactory
header.add(targetLbl);
panel.add(header, BorderLayout.PAGE_START);
TableUtils.setupTable(paramsTable, ListSelectionModel.SINGLE_SELECTION, new ParamsTableModel(params), null,
TableUtils.setupTable(
paramsTable,
ListSelectionModel.SINGLE_SELECTION,
new ParamsTableModel(params),
null,
ParamsTableModel.Column.DELETE.getColumnWidth(),
ParamsTableModel.Column.NAME.getColumnWidth());
paramsTable.setShowGrid(true);
@ -135,28 +138,37 @@ public final class EditParamsDialogFactory implements DialogOpener.DialogFactory
JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING, 10, 5));
footer.setOpaque(false);
JButton okBtn = new JButton(MessageUtils.getLocalizedMessage("button.ok"));
okBtn.addActionListener(e -> {
Map<String, String> params = new HashMap<>();
for (int i = 0; i < paramsTable.getRowCount(); i++) {
boolean deleted = (boolean) paramsTable.getValueAt(i, ParamsTableModel.Column.DELETE.getIndex());
String name = (String) paramsTable.getValueAt(i, ParamsTableModel.Column.NAME.getIndex());
String value = (String) paramsTable.getValueAt(i, ParamsTableModel.Column.VALUE.getIndex());
if (deleted || Objects.isNull(name) || name.equals("") || Objects.isNull(value) || value.equals("")) {
continue;
}
params.put(name, value);
}
updateTargetParams(params);
callback.call();
this.params.clear();
dialog.dispose();
});
okBtn.addActionListener(
e -> {
Map<String, String> params = new HashMap<>();
for (int i = 0; i < paramsTable.getRowCount(); i++) {
boolean deleted =
(boolean) paramsTable.getValueAt(i, ParamsTableModel.Column.DELETE.getIndex());
String name =
(String) paramsTable.getValueAt(i, ParamsTableModel.Column.NAME.getIndex());
String value =
(String) paramsTable.getValueAt(i, ParamsTableModel.Column.VALUE.getIndex());
if (deleted
|| Objects.isNull(name)
|| name.equals("")
|| Objects.isNull(value)
|| value.equals("")) {
continue;
}
params.put(name, value);
}
updateTargetParams(params);
callback.call();
this.params.clear();
dialog.dispose();
});
footer.add(okBtn);
JButton cancelBtn = new JButton(MessageUtils.getLocalizedMessage("button.cancel"));
cancelBtn.addActionListener(e -> {
this.params.clear();
dialog.dispose();
});
cancelBtn.addActionListener(
e -> {
this.params.clear();
dialog.dispose();
});
footer.add(cancelBtn);
panel.add(footer, BorderLayout.PAGE_END);
@ -164,19 +176,22 @@ public final class EditParamsDialogFactory implements DialogOpener.DialogFactory
}
private void updateTargetParams(Map<String, String> params) {
operatorRegistry.get(CustomAnalyzerPanelOperator.class).ifPresent(operator -> {
switch (mode) {
case CHARFILTER:
operator.updateCharFilterParams(targetIndex, params);
break;
case TOKENIZER:
operator.updateTokenizerParams(params);
break;
case TOKENFILTER:
operator.updateTokenFilterParams(targetIndex, params);
break;
}
});
operatorRegistry
.get(CustomAnalyzerPanelOperator.class)
.ifPresent(
operator -> {
switch (mode) {
case CHARFILTER:
operator.updateCharFilterParams(targetIndex, params);
break;
case TOKENIZER:
operator.updateTokenizerParams(params);
break;
case TOKENFILTER:
operator.updateTokenFilterParams(targetIndex, params);
break;
}
});
}
static final class ParamsTableModel extends TableModelBase<ParamsTableModel.Column> {
@ -217,7 +232,6 @@ public final class EditParamsDialogFactory implements DialogOpener.DialogFactory
public int getColumnWidth() {
return width;
}
}
private static final int PARAM_SIZE = 20;
@ -249,6 +263,4 @@ public final class EditParamsDialogFactory implements DialogOpener.DialogFactory
return Column.values();
}
}
}

View File

@ -19,5 +19,7 @@ package org.apache.lucene.luke.app.desktop.components.dialog.analysis;
/** Edit parameters mode */
public enum EditParamsMode {
CHARFILTER, TOKENIZER, TOKENFILTER;
CHARFILTER,
TOKENIZER,
TOKENFILTER;
}

View File

@ -17,14 +17,6 @@
package org.apache.lucene.luke.app.desktop.components.dialog.analysis;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import java.awt.BorderLayout;
import java.awt.Dialog;
import java.awt.Dimension;
@ -33,7 +25,14 @@ import java.awt.Window;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import org.apache.lucene.luke.app.desktop.Preferences;
import org.apache.lucene.luke.app.desktop.PreferencesFactory;
import org.apache.lucene.luke.app.desktop.components.TableColumnInfo;
@ -58,7 +57,7 @@ public final class TokenAttributeDialogFactory implements DialogOpener.DialogFac
private List<Analysis.TokenAttribute> attributes;
public synchronized static TokenAttributeDialogFactory getInstance() throws IOException {
public static synchronized TokenAttributeDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new TokenAttributeDialogFactory();
}
@ -98,10 +97,18 @@ public final class TokenAttributeDialogFactory implements DialogOpener.DialogFac
header.add(new JLabel(term));
panel.add(header, BorderLayout.PAGE_START);
List<TokenAttValue> attrValues = attributes.stream()
.flatMap(att -> att.getAttValues().entrySet().stream().map(e -> TokenAttValue.of(att.getAttClass(), e.getKey(), e.getValue())))
.collect(Collectors.toList());
TableUtils.setupTable(attributesTable, ListSelectionModel.SINGLE_SELECTION, new AttributeTableModel(attrValues), null);
List<TokenAttValue> attrValues =
attributes.stream()
.flatMap(
att ->
att.getAttValues().entrySet().stream()
.map(e -> TokenAttValue.of(att.getAttClass(), e.getKey(), e.getValue())))
.collect(Collectors.toList());
TableUtils.setupTable(
attributesTable,
ListSelectionModel.SINGLE_SELECTION,
new AttributeTableModel(attrValues),
null);
panel.add(new JScrollPane(attributesTable), BorderLayout.CENTER);
JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING));
@ -117,7 +124,6 @@ public final class TokenAttributeDialogFactory implements DialogOpener.DialogFac
static final class AttributeTableModel extends TableModelBase<AttributeTableModel.Column> {
enum Column implements TableColumnInfo {
ATTR("Attribute", 0, String.class),
NAME("Name", 1, String.class),
VALUE("Value", 2, String.class);
@ -177,8 +183,7 @@ public final class TokenAttributeDialogFactory implements DialogOpener.DialogFac
return attValue;
}
private TokenAttValue() {
}
private TokenAttValue() {}
String getAttClass() {
return attClass;
@ -192,5 +197,4 @@ public final class TokenAttributeDialogFactory implements DialogOpener.DialogFac
return value;
}
}
}

View File

@ -16,4 +16,4 @@
*/
/** Dialogs used in the Analysis tab */
package org.apache.lucene.luke.app.desktop.components.dialog.analysis;
package org.apache.lucene.luke.app.desktop.components.dialog.analysis;

View File

@ -17,22 +17,6 @@
package org.apache.lucene.luke.app.desktop.components.dialog.documents;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.UIManager;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
@ -51,7 +35,22 @@ import java.lang.reflect.Constructor;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.UIManager;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
@ -98,13 +97,14 @@ import org.apache.lucene.luke.util.LoggerFactory;
import org.apache.lucene.util.BytesRef;
/** Factory of add document dialog */
public final class AddDocumentDialogFactory implements DialogOpener.DialogFactory, AddDocumentDialogOperator {
public final class AddDocumentDialogFactory
implements DialogOpener.DialogFactory, AddDocumentDialogOperator {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static AddDocumentDialogFactory instance;
private final static int ROW_COUNT = 50;
private static final int ROW_COUNT = 50;
private final Preferences prefs;
@ -136,11 +136,11 @@ public final class AddDocumentDialogFactory implements DialogOpener.DialogFactor
private JDialog dialog;
public synchronized static AddDocumentDialogFactory getInstance() throws IOException {
public static synchronized AddDocumentDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new AddDocumentDialogFactory();
}
return instance;
return instance;
}
private AddDocumentDialogFactory() throws IOException {
@ -150,7 +150,10 @@ public final class AddDocumentDialogFactory implements DialogOpener.DialogFactor
this.operatorRegistry = ComponentOperatorRegistry.getInstance();
this.indexOptionsDialogFactory = IndexOptionsDialogFactory.getInstance();
this.helpDialogFactory = HelpDialogFactory.getInstance();
this.newFieldList = IntStream.range(0, ROW_COUNT).mapToObj(i -> NewField.newInstance()).collect(Collectors.toList());
this.newFieldList =
IntStream.range(0, ROW_COUNT)
.mapToObj(i -> NewField.newInstance())
.collect(Collectors.toList());
operatorRegistry.register(AddDocumentDialogOperator.class, this);
indexHandler.addObserver(new Observer());
@ -204,14 +207,16 @@ public final class AddDocumentDialogFactory implements DialogOpener.DialogFactor
analyzerHeader.setOpaque(false);
analyzerHeader.add(new JLabel(MessageUtils.getLocalizedMessage("add_document.label.analyzer")));
analyzerHeader.add(analyzerNameLbl);
JLabel changeLbl = new JLabel(MessageUtils.getLocalizedMessage("add_document.hyperlink.change"));
changeLbl.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
dialog.dispose();
tabSwitcher.switchTab(TabbedPaneProvider.Tab.ANALYZER);
}
});
JLabel changeLbl =
new JLabel(MessageUtils.getLocalizedMessage("add_document.hyperlink.change"));
changeLbl.addMouseListener(
new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
dialog.dispose();
tabSwitcher.switchTab(TabbedPaneProvider.Tab.ANALYZER);
}
});
analyzerHeader.add(FontUtils.toLinkText(changeLbl));
panel.add(analyzerHeader);
@ -245,20 +250,46 @@ public final class AddDocumentDialogFactory implements DialogOpener.DialogFactor
private JTable fieldsTable() {
JTable fieldsTable = new JTable();
TableUtils.setupTable(fieldsTable, ListSelectionModel.SINGLE_SELECTION, new FieldsTableModel(newFieldList), null, 30, 150, 120, 80);
TableUtils.setupTable(
fieldsTable,
ListSelectionModel.SINGLE_SELECTION,
new FieldsTableModel(newFieldList),
null,
30,
150,
120,
80);
fieldsTable.setShowGrid(true);
JComboBox<Class<? extends IndexableField>> typesCombo = new JComboBox<>(presetFieldClasses);
typesCombo.setRenderer((list, value, index, isSelected, cellHasFocus) -> new JLabel(value.getSimpleName()));
fieldsTable.getColumnModel().getColumn(FieldsTableModel.Column.TYPE.getIndex()).setCellEditor(new DefaultCellEditor(typesCombo));
typesCombo.setRenderer(
(list, value, index, isSelected, cellHasFocus) -> new JLabel(value.getSimpleName()));
fieldsTable
.getColumnModel()
.getColumn(FieldsTableModel.Column.TYPE.getIndex())
.setCellEditor(new DefaultCellEditor(typesCombo));
for (int i = 0; i < fieldsTable.getModel().getRowCount(); i++) {
fieldsTable.getModel().setValueAt(TextField.class, i, FieldsTableModel.Column.TYPE.getIndex());
fieldsTable
.getModel()
.setValueAt(TextField.class, i, FieldsTableModel.Column.TYPE.getIndex());
}
fieldsTable.getColumnModel().getColumn(FieldsTableModel.Column.TYPE.getIndex()).setHeaderRenderer(
new HelpHeaderRenderer(
"About Type", "Select Field Class:",
createTypeHelpDialog(), helpDialogFactory, dialog));
fieldsTable.getColumnModel().getColumn(FieldsTableModel.Column.TYPE.getIndex()).setCellRenderer(new TypeCellRenderer());
fieldsTable.getColumnModel().getColumn(FieldsTableModel.Column.OPTIONS.getIndex()).setCellRenderer(new OptionsCellRenderer(dialog, indexOptionsDialogFactory, newFieldList));
fieldsTable
.getColumnModel()
.getColumn(FieldsTableModel.Column.TYPE.getIndex())
.setHeaderRenderer(
new HelpHeaderRenderer(
"About Type",
"Select Field Class:",
createTypeHelpDialog(),
helpDialogFactory,
dialog));
fieldsTable
.getColumnModel()
.getColumn(FieldsTableModel.Column.TYPE.getIndex())
.setCellRenderer(new TypeCellRenderer());
fieldsTable
.getColumnModel()
.getColumn(FieldsTableModel.Column.OPTIONS.getIndex())
.setCellRenderer(new OptionsCellRenderer(dialog, indexOptionsDialogFactory, newFieldList));
return fieldsTable;
}
@ -271,28 +302,30 @@ public final class AddDocumentDialogFactory implements DialogOpener.DialogFactor
JPanel header = new JPanel();
header.setOpaque(false);
header.setLayout(new BoxLayout(header, BoxLayout.PAGE_AXIS));
String[] typeList = new String[]{
"TextField",
"StringField",
"IntPoint",
"LongPoint",
"FloatPoint",
"DoublePoint",
"SortedDocValuesField",
"SortedSetDocValuesField",
"NumericDocValuesField",
"SortedNumericDocValuesField",
"StoredField",
"Field"
};
String[] typeList =
new String[] {
"TextField",
"StringField",
"IntPoint",
"LongPoint",
"FloatPoint",
"DoublePoint",
"SortedDocValuesField",
"SortedSetDocValuesField",
"NumericDocValuesField",
"SortedNumericDocValuesField",
"StoredField",
"Field"
};
JPanel wrapper1 = new JPanel(new FlowLayout(FlowLayout.LEADING));
wrapper1.setOpaque(false);
JComboBox<String> typeCombo = new JComboBox<>(typeList);
typeCombo.setSelectedItem(typeList[0]);
typeCombo.addActionListener(e -> {
String selected = (String) typeCombo.getSelectedItem();
descTA.setText(MessageUtils.getLocalizedMessage("help.fieldtype." + selected));
});
typeCombo.addActionListener(
e -> {
String selected = (String) typeCombo.getSelectedItem();
descTA.setText(MessageUtils.getLocalizedMessage("help.fieldtype." + selected));
});
wrapper1.add(typeCombo);
header.add(wrapper1);
JPanel wrapper2 = new JPanel(new FlowLayout(FlowLayout.LEADING));
@ -325,13 +358,21 @@ public final class AddDocumentDialogFactory implements DialogOpener.DialogFactor
}
@SuppressWarnings({"unchecked", "rawtypes"})
private final Class<? extends IndexableField>[] presetFieldClasses = new Class[]{
TextField.class, StringField.class,
IntPoint.class, LongPoint.class, FloatPoint.class, DoublePoint.class,
SortedDocValuesField.class, SortedSetDocValuesField.class,
NumericDocValuesField.class, SortedNumericDocValuesField.class,
StoredField.class, Field.class
};
private final Class<? extends IndexableField>[] presetFieldClasses =
new Class[] {
TextField.class,
StringField.class,
IntPoint.class,
LongPoint.class,
FloatPoint.class,
DoublePoint.class,
SortedDocValuesField.class,
SortedSetDocValuesField.class,
NumericDocValuesField.class,
SortedNumericDocValuesField.class,
StoredField.class,
Field.class
};
@Override
public void setAnalyzer(Analyzer analyzer) {
@ -341,11 +382,12 @@ public final class AddDocumentDialogFactory implements DialogOpener.DialogFactor
private class ListenerFunctions {
void addDocument(ActionEvent e) {
List<NewField> validFields = newFieldList.stream()
.filter(nf -> !nf.isDeleted())
.filter(nf -> !StringUtils.isNullOrEmpty(nf.getName()))
.filter(nf -> !StringUtils.isNullOrEmpty(nf.getValue()))
.collect(Collectors.toList());
List<NewField> validFields =
newFieldList.stream()
.filter(nf -> !nf.isDeleted())
.filter(nf -> !StringUtils.isNullOrEmpty(nf.getName()))
.filter(nf -> !StringUtils.isNullOrEmpty(nf.getValue()))
.collect(Collectors.toList());
if (validFields.isEmpty()) {
infoTA.setText("Please add one or more fields. Name and Value are both required.");
return;
@ -391,12 +433,12 @@ public final class AddDocumentDialogFactory implements DialogOpener.DialogFactor
constr = nf.getType().getConstructor(String.class, double[].class);
double[] values = NumericUtils.convertToDoubleArray(nf.getValue(), false);
return constr.newInstance(nf.getName(), values);
} else if (nf.getType().equals(SortedDocValuesField.class) ||
nf.getType().equals(SortedSetDocValuesField.class)) {
} else if (nf.getType().equals(SortedDocValuesField.class)
|| nf.getType().equals(SortedSetDocValuesField.class)) {
constr = nf.getType().getConstructor(String.class, BytesRef.class);
return constr.newInstance(nf.getName(), new BytesRef(nf.getValue()));
} else if (nf.getType().equals(NumericDocValuesField.class) ||
nf.getType().equals(SortedNumericDocValuesField.class)) {
} else if (nf.getType().equals(NumericDocValuesField.class)
|| nf.getType().equals(SortedNumericDocValuesField.class)) {
constr = nf.getType().getConstructor(String.class, long.class);
long value = NumericUtils.tryConvertToLongValue(nf.getValue());
return constr.newInstance(nf.getName(), value);
@ -414,12 +456,16 @@ public final class AddDocumentDialogFactory implements DialogOpener.DialogFactor
private void addDocument(Document doc) {
try {
Analyzer analyzer = operatorRegistry.get(AnalysisTabOperator.class)
.map(AnalysisTabOperator::getCurrentAnalyzer)
.orElse(new StandardAnalyzer());
Analyzer analyzer =
operatorRegistry
.get(AnalysisTabOperator.class)
.map(AnalysisTabOperator::getCurrentAnalyzer)
.orElse(new StandardAnalyzer());
toolsModel.addDocument(doc, analyzer);
indexHandler.reOpen();
operatorRegistry.get(DocumentsTabOperator.class).ifPresent(DocumentsTabOperator::displayLatestDoc);
operatorRegistry
.get(DocumentsTabOperator.class)
.ifPresent(DocumentsTabOperator::displayLatestDoc);
tabSwitcher.switchTab(TabbedPaneProvider.Tab.DOCUMENTS);
infoTA.setText(MessageUtils.getLocalizedMessage("add_document.message.success"));
addBtn.setEnabled(false);
@ -432,14 +478,15 @@ public final class AddDocumentDialogFactory implements DialogOpener.DialogFactor
throw new LukeException(e.getMessage(), e);
}
}
}
private class Observer implements IndexObserver {
@Override
public void openIndex(LukeState state) {
toolsModel = toolsFactory.newInstance(state.getIndexReader(), state.useCompound(), state.keepAllCommits());
toolsModel =
toolsFactory.newInstance(
state.getIndexReader(), state.useCompound(), state.keepAllCommits());
}
@Override
@ -481,7 +528,6 @@ public final class AddDocumentDialogFactory implements DialogOpener.DialogFactor
public Class<?> getType() {
return type;
}
}
private final List<NewField> newFieldList;
@ -533,7 +579,8 @@ public final class AddDocumentDialogFactory implements DialogOpener.DialogFactor
@SuppressWarnings("unchecked")
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
String simpleName = ((Class<? extends IndexableField>) value).getSimpleName();
return new JLabel(simpleName);
}
@ -551,7 +598,10 @@ public final class AddDocumentDialogFactory implements DialogOpener.DialogFactor
private JTable table;
public OptionsCellRenderer(JDialog dialog, IndexOptionsDialogFactory indexOptionsDialogFactory, List<NewField> newFieldList) {
public OptionsCellRenderer(
JDialog dialog,
IndexOptionsDialogFactory indexOptionsDialogFactory,
List<NewField> newFieldList) {
this.dialog = dialog;
this.indexOptionsDialogFactory = indexOptionsDialogFactory;
this.newFieldList = newFieldList;
@ -559,7 +609,8 @@ public final class AddDocumentDialogFactory implements DialogOpener.DialogFactor
@Override
@SuppressWarnings("unchecked")
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (table != null && this.table != table) {
this.table = table;
final JTableHeader header = table.getTableHeader();
@ -569,25 +620,30 @@ public final class AddDocumentDialogFactory implements DialogOpener.DialogFactor
panel.add(new JLabel(value.toString()));
JLabel optionsLbl = new JLabel("options");
table.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
int row = table.rowAtPoint(e.getPoint());
int col = table.columnAtPoint(e.getPoint());
if (row >= 0 && col == FieldsTableModel.Column.OPTIONS.getIndex()) {
String title = "Index options for:";
new DialogOpener<>(indexOptionsDialogFactory).open(dialog, title, 500, 500,
(factory) -> {
factory.setNewField(newFieldList.get(row));
});
}
}
});
table.addMouseListener(
new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
int row = table.rowAtPoint(e.getPoint());
int col = table.columnAtPoint(e.getPoint());
if (row >= 0 && col == FieldsTableModel.Column.OPTIONS.getIndex()) {
String title = "Index options for:";
new DialogOpener<>(indexOptionsDialogFactory)
.open(
dialog,
title,
500,
500,
(factory) -> {
factory.setNewField(newFieldList.get(row));
});
}
}
});
panel.add(FontUtils.toLinkText(optionsLbl));
}
}
return panel;
}
}
}

View File

@ -24,4 +24,3 @@ import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry;
public interface AddDocumentDialogOperator extends ComponentOperatorRegistry.ComponentOperator {
void setAnalyzer(Analyzer analyzer);
}

View File

@ -17,18 +17,6 @@
package org.apache.lucene.luke.app.desktop.components.dialog.documents;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import java.awt.BorderLayout;
import java.awt.Dialog;
import java.awt.Dimension;
@ -44,7 +32,18 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import org.apache.lucene.luke.app.desktop.Preferences;
import org.apache.lucene.luke.app.desktop.PreferencesFactory;
import org.apache.lucene.luke.app.desktop.util.DialogOpener;
@ -73,7 +72,7 @@ public final class DocValuesDialogFactory implements DialogOpener.DialogFactory
private DocValues docValues;
public synchronized static DocValuesDialogFactory getInstance() throws IOException {
public static synchronized DocValuesDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new DocValuesDialogFactory();
}
@ -91,14 +90,10 @@ public final class DocValuesDialogFactory implements DialogOpener.DialogFactory
DefaultListModel<String> values = new DefaultListModel<>();
if (docValues.getValues().size() > 0) {
decodersCombo.setEnabled(false);
docValues.getValues().stream()
.map(BytesRefUtils::decode)
.forEach(values::addElement);
docValues.getValues().stream().map(BytesRefUtils::decode).forEach(values::addElement);
} else if (docValues.getNumericValues().size() > 0) {
decodersCombo.setEnabled(true);
docValues.getNumericValues().stream()
.map(String::valueOf)
.forEach(values::addElement);
docValues.getNumericValues().stream().map(String::valueOf).forEach(values::addElement);
}
valueList.setModel(values);
@ -138,7 +133,8 @@ public final class DocValuesDialogFactory implements DialogOpener.DialogFactory
JPanel fieldHeader = new JPanel(new FlowLayout(FlowLayout.LEADING, 3, 3));
fieldHeader.setOpaque(false);
fieldHeader.add(new JLabel(MessageUtils.getLocalizedMessage("documents.docvalues.label.doc_values")));
fieldHeader.add(
new JLabel(MessageUtils.getLocalizedMessage("documents.docvalues.label.doc_values")));
fieldHeader.add(new JLabel(field));
header.add(fieldHeader);
@ -151,7 +147,8 @@ public final class DocValuesDialogFactory implements DialogOpener.DialogFactory
JPanel decodeHeader = new JPanel(new FlowLayout(FlowLayout.TRAILING, 3, 3));
decodeHeader.setOpaque(false);
decodeHeader.add(new JLabel("decoded as"));
String[] decoders = Arrays.stream(Decoder.values()).map(Decoder::toString).toArray(String[]::new);
String[] decoders =
Arrays.stream(Decoder.values()).map(Decoder::toString).toArray(String[]::new);
decodersCombo.setModel(new DefaultComboBoxModel<>(decoders));
decodersCombo.setSelectedItem(Decoder.LONG.toString());
decodersCombo.addActionListener(listeners::selectDecoder);
@ -171,13 +168,9 @@ public final class DocValuesDialogFactory implements DialogOpener.DialogFactory
DefaultListModel<String> values = new DefaultListModel<>();
if (docValues.getValues().size() > 0) {
docValues.getValues().stream()
.map(BytesRefUtils::decode)
.forEach(values::addElement);
docValues.getValues().stream().map(BytesRefUtils::decode).forEach(values::addElement);
} else {
docValues.getNumericValues().stream()
.map(String::valueOf)
.forEach(values::addElement);
docValues.getNumericValues().stream().map(String::valueOf).forEach(values::addElement);
}
valueList.setModel(values);
@ -188,7 +181,9 @@ public final class DocValuesDialogFactory implements DialogOpener.DialogFactory
JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING, 5, 5));
footer.setOpaque(false);
JButton copyBtn = new JButton(FontUtils.elegantIconHtml("&#xe0e6;", MessageUtils.getLocalizedMessage("button.copy")));
JButton copyBtn =
new JButton(
FontUtils.elegantIconHtml("&#xe0e6;", MessageUtils.getLocalizedMessage("button.copy")));
copyBtn.setMargin(new Insets(3, 0, 3, 0));
copyBtn.addActionListener(listeners::copyValues);
footer.add(copyBtn);
@ -214,9 +209,7 @@ public final class DocValuesDialogFactory implements DialogOpener.DialogFactory
DefaultListModel<String> values = new DefaultListModel<>();
switch (decoder) {
case LONG:
docValues.getNumericValues().stream()
.map(String::valueOf)
.forEach(values::addElement);
docValues.getNumericValues().stream().map(String::valueOf).forEach(values::addElement);
break;
case FLOAT:
docValues.getNumericValues().stream()
@ -266,11 +259,11 @@ public final class DocValuesDialogFactory implements DialogOpener.DialogFactory
}
}
/** doc value decoders */
public enum Decoder {
LONG("long"), FLOAT("float"), DOUBLE("double");
LONG("long"),
FLOAT("float"),
DOUBLE("double");
private final String label;
@ -292,5 +285,4 @@ public final class DocValuesDialogFactory implements DialogOpener.DialogFactory
throw new IllegalArgumentException("No such decoder: " + label);
}
}
}

View File

@ -17,6 +17,13 @@
package org.apache.lucene.luke.app.desktop.components.dialog.documents;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.Window;
import java.io.IOException;
import java.util.Arrays;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
@ -27,14 +34,6 @@ import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.Window;
import java.io.IOException;
import java.util.Arrays;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.StringField;
@ -80,7 +79,7 @@ public final class IndexOptionsDialogFactory implements DialogOpener.DialogFacto
private NewField nf;
public synchronized static IndexOptionsDialogFactory getInstance() throws IOException {
public static synchronized IndexOptionsDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new IndexOptionsDialogFactory();
}
@ -155,7 +154,8 @@ public final class IndexOptionsDialogFactory implements DialogOpener.DialogFacto
JPanel inner2 = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 1));
inner2.setOpaque(false);
JLabel idxOptLbl = new JLabel(MessageUtils.getLocalizedMessage("idx_options.label.index_options"));
JLabel idxOptLbl =
new JLabel(MessageUtils.getLocalizedMessage("idx_options.label.index_options"));
inner2.add(idxOptLbl);
inner2.add(idxOptCombo);
panel.add(inner2);
@ -249,9 +249,9 @@ public final class IndexOptionsDialogFactory implements DialogOpener.DialogFacto
dimCountTF.setText(String.valueOf(fieldType.pointDimensionCount()));
dimNumBytesTF.setText(String.valueOf(fieldType.pointNumBytes()));
if (nf.getType().equals(org.apache.lucene.document.TextField.class) ||
nf.getType().equals(StringField.class) ||
nf.getType().equals(Field.class)) {
if (nf.getType().equals(org.apache.lucene.document.TextField.class)
|| nf.getType().equals(StringField.class)
|| nf.getType().equals(Field.class)) {
storedCB.setEnabled(true);
} else {
storedCB.setEnabled(false);
@ -304,5 +304,4 @@ public final class IndexOptionsDialogFactory implements DialogOpener.DialogFacto
private static String[] availableDocValuesType() {
return Arrays.stream(DocValuesType.values()).map(DocValuesType::name).toArray(String[]::new);
}
}

View File

@ -17,13 +17,6 @@
package org.apache.lucene.luke.app.desktop.components.dialog.documents;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dialog;
@ -36,7 +29,13 @@ import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.io.IOException;
import java.util.Objects;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import org.apache.lucene.luke.app.desktop.Preferences;
import org.apache.lucene.luke.app.desktop.PreferencesFactory;
import org.apache.lucene.luke.app.desktop.util.DialogOpener;
@ -56,7 +55,7 @@ public final class StoredValueDialogFactory implements DialogOpener.DialogFactor
private String value;
public synchronized static StoredValueDialogFactory getInstance() throws IOException {
public static synchronized StoredValueDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new StoredValueDialogFactory();
}
@ -110,13 +109,16 @@ public final class StoredValueDialogFactory implements DialogOpener.DialogFactor
JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING, 5, 5));
footer.setOpaque(false);
JButton copyBtn = new JButton(FontUtils.elegantIconHtml("&#xe0e6;", MessageUtils.getLocalizedMessage("button.copy")));
JButton copyBtn =
new JButton(
FontUtils.elegantIconHtml("&#xe0e6;", MessageUtils.getLocalizedMessage("button.copy")));
copyBtn.setMargin(new Insets(3, 3, 3, 3));
copyBtn.addActionListener(e -> {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
StringSelection selection = new StringSelection(value);
clipboard.setContents(selection, null);
});
copyBtn.addActionListener(
e -> {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
StringSelection selection = new StringSelection(value);
clipboard.setContents(selection, null);
});
footer.add(copyBtn);
JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close"));
@ -127,6 +129,4 @@ public final class StoredValueDialogFactory implements DialogOpener.DialogFactor
return panel;
}
}

View File

@ -17,14 +17,6 @@
package org.apache.lucene.luke.app.desktop.components.dialog.documents;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import java.awt.BorderLayout;
import java.awt.Dialog;
import java.awt.Dimension;
@ -35,7 +27,14 @@ import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import org.apache.lucene.luke.app.desktop.Preferences;
import org.apache.lucene.luke.app.desktop.PreferencesFactory;
import org.apache.lucene.luke.app.desktop.components.TableColumnInfo;
@ -58,7 +57,7 @@ public final class TermVectorDialogFactory implements DialogOpener.DialogFactory
private List<TermVectorEntry> tvEntries;
public synchronized static TermVectorDialogFactory getInstance() throws IOException {
public static synchronized TermVectorDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new TermVectorDialogFactory();
}
@ -98,12 +97,20 @@ public final class TermVectorDialogFactory implements DialogOpener.DialogFactory
JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING, 5, 5));
header.setOpaque(false);
header.add(new JLabel(MessageUtils.getLocalizedMessage("documents.termvector.label.term_vector")));
header.add(
new JLabel(MessageUtils.getLocalizedMessage("documents.termvector.label.term_vector")));
header.add(new JLabel(field));
panel.add(header, BorderLayout.PAGE_START);
JTable tvTable = new JTable();
TableUtils.setupTable(tvTable, ListSelectionModel.SINGLE_SELECTION, new TermVectorTableModel(tvEntries), null, 100, 50, 100);
TableUtils.setupTable(
tvTable,
ListSelectionModel.SINGLE_SELECTION,
new TermVectorTableModel(tvEntries),
null,
100,
50,
100);
JScrollPane scrollPane = new JScrollPane(tvTable);
panel.add(scrollPane, BorderLayout.CENTER);
@ -121,7 +128,6 @@ public final class TermVectorDialogFactory implements DialogOpener.DialogFactory
static final class TermVectorTableModel extends TableModelBase<TermVectorTableModel.Column> {
enum Column implements TableColumnInfo {
TERM("Term", 0, String.class),
FREQ("Freq", 1, Long.class),
POSITIONS("Positions", 2, String.class),
@ -165,20 +171,27 @@ public final class TermVectorDialogFactory implements DialogOpener.DialogFactory
String termText = entry.getTermText();
long freq = tvEntries.get(i).getFreq();
String positions = String.join(",",
entry.getPositions().stream()
.map(pos -> Integer.toString(pos.getPosition()))
.collect(Collectors.toList()));
String offsets = String.join(",",
entry.getPositions().stream()
.filter(pos -> pos.getStartOffset().isPresent() && pos.getEndOffset().isPresent())
.map(pos -> Integer.toString(pos.getStartOffset().orElse(-1)) + "-" + Integer.toString(pos.getEndOffset().orElse(-1)))
.collect(Collectors.toList())
);
String positions =
String.join(
",",
entry.getPositions().stream()
.map(pos -> Integer.toString(pos.getPosition()))
.collect(Collectors.toList()));
String offsets =
String.join(
",",
entry.getPositions().stream()
.filter(
pos -> pos.getStartOffset().isPresent() && pos.getEndOffset().isPresent())
.map(
pos ->
Integer.toString(pos.getStartOffset().orElse(-1))
+ "-"
+ Integer.toString(pos.getEndOffset().orElse(-1)))
.collect(Collectors.toList()));
data[i] = new Object[]{termText, freq, positions, offsets};
data[i] = new Object[] {termText, freq, positions, offsets};
}
}
@Override

View File

@ -16,4 +16,4 @@
*/
/** Dialogs used in the Documents tab */
package org.apache.lucene.luke.app.desktop.components.dialog.documents;
package org.apache.lucene.luke.app.desktop.components.dialog.documents;

View File

@ -17,18 +17,6 @@
package org.apache.lucene.luke.app.desktop.components.dialog.menubar;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Desktop;
@ -42,7 +30,18 @@ import java.awt.Window;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Objects;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import org.apache.lucene.LucenePackage;
import org.apache.lucene.luke.app.desktop.Preferences;
import org.apache.lucene.luke.app.desktop.PreferencesFactory;
@ -62,7 +61,7 @@ public final class AboutDialogFactory implements DialogOpener.DialogFactory {
private JDialog dialog;
public synchronized static AboutDialogFactory getInstance() throws IOException {
public static synchronized AboutDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new AboutDialogFactory();
}
@ -143,12 +142,17 @@ public final class AboutDialogFactory implements DialogOpener.DialogFactory {
editorPane.setText(LICENSE_NOTICE);
editorPane.setEditable(false);
editorPane.addHyperlinkListener(hyperlinkListener);
JScrollPane scrollPane = new JScrollPane(editorPane, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
JScrollPane scrollPane =
new JScrollPane(
editorPane,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setBorder(BorderFactory.createLineBorder(Color.gray));
SwingUtilities.invokeLater(() -> {
// Set the scroll bar position to top
scrollPane.getVerticalScrollBar().setValue(0);
});
SwingUtilities.invokeLater(
() -> {
// Set the scroll bar position to top
scrollPane.getVerticalScrollBar().setValue(0);
});
return scrollPane;
}
@ -164,37 +168,37 @@ public final class AboutDialogFactory implements DialogOpener.DialogFactory {
return panel;
}
private static final String LUCENE_IMPLEMENTATION_VERSION = LucenePackage.get().getImplementationVersion();
private static final String LUCENE_IMPLEMENTATION_VERSION =
LucenePackage.get().getImplementationVersion();
private static final String LICENSE_NOTICE =
"<p>[Implementation Version]</p>" +
"<p>" + (Objects.nonNull(LUCENE_IMPLEMENTATION_VERSION) ? LUCENE_IMPLEMENTATION_VERSION : "") + "</p>" +
"<p>[License]</p>" +
"<p>Luke is distributed under <a href=\"http://www.apache.org/licenses/LICENSE-2.0\">Apache License Version 2.0</a> (http://www.apache.org/licenses/LICENSE-2.0) " +
"and includes <a href=\"https://www.elegantthemes.com/blog/resources/elegant-icon-font\">The Elegant Icon Font</a> (https://www.elegantthemes.com/blog/resources/elegant-icon-font) " +
"licensed under <a href=\"https://opensource.org/licenses/MIT\">MIT</a> (https://opensource.org/licenses/MIT)</p>" +
"<p>[Brief history]</p>" +
"<ul>" +
"<li>The original author is Andrzej Bialecki</li>" +
"<li>The project has been mavenized by Neil Ireson</li>" +
"<li>The project has been ported to Lucene trunk (marked as 5.0 at the time) by Dmitry Kan\n</li>" +
"<li>The project has been back-ported to Lucene 4.3 by sonarname</li>" +
"<li>There are updates to the (non-mavenized) project done by tarzanek</li>" +
"<li>The UI and core components has been re-implemented on top of Swing by Tomoko Uchida</li>" +
"</ul>"
;
private static final HyperlinkListener hyperlinkListener = e -> {
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
if (Desktop.isDesktopSupported()) {
try {
Desktop.getDesktop().browse(e.getURL().toURI());
} catch (IOException | URISyntaxException ex) {
throw new LukeException(ex.getMessage(), ex);
}
}
};
"<p>[Implementation Version]</p>"
+ "<p>"
+ (Objects.nonNull(LUCENE_IMPLEMENTATION_VERSION) ? LUCENE_IMPLEMENTATION_VERSION : "")
+ "</p>"
+ "<p>[License]</p>"
+ "<p>Luke is distributed under <a href=\"http://www.apache.org/licenses/LICENSE-2.0\">Apache License Version 2.0</a> (http://www.apache.org/licenses/LICENSE-2.0) "
+ "and includes <a href=\"https://www.elegantthemes.com/blog/resources/elegant-icon-font\">The Elegant Icon Font</a> (https://www.elegantthemes.com/blog/resources/elegant-icon-font) "
+ "licensed under <a href=\"https://opensource.org/licenses/MIT\">MIT</a> (https://opensource.org/licenses/MIT)</p>"
+ "<p>[Brief history]</p>"
+ "<ul>"
+ "<li>The original author is Andrzej Bialecki</li>"
+ "<li>The project has been mavenized by Neil Ireson</li>"
+ "<li>The project has been ported to Lucene trunk (marked as 5.0 at the time) by Dmitry Kan\n</li>"
+ "<li>The project has been back-ported to Lucene 4.3 by sonarname</li>"
+ "<li>There are updates to the (non-mavenized) project done by tarzanek</li>"
+ "<li>The UI and core components has been re-implemented on top of Swing by Tomoko Uchida</li>"
+ "</ul>";
private static final HyperlinkListener hyperlinkListener =
e -> {
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
if (Desktop.isDesktopSupported()) {
try {
Desktop.getDesktop().browse(e.getURL().toURI());
} catch (IOException | URISyntaxException ex) {
throw new LukeException(ex.getMessage(), ex);
}
}
};
}

View File

@ -17,16 +17,6 @@
package org.apache.lucene.luke.app.desktop.components.dialog.menubar;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import javax.swing.SwingWorker;
import java.awt.BorderLayout;
import java.awt.Dialog;
import java.awt.Dimension;
@ -39,7 +29,16 @@ import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import javax.swing.SwingWorker;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.index.CheckIndex;
import org.apache.lucene.luke.app.DirectoryHandler;
@ -95,7 +94,7 @@ public final class CheckIndexDialogFactory implements DialogOpener.DialogFactory
private final ListenerFunctions listeners = new ListenerFunctions();
public synchronized static CheckIndexDialogFactory getInstance() throws IOException {
public static synchronized CheckIndexDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new CheckIndexDialogFactory();
}
@ -115,7 +114,9 @@ public final class CheckIndexDialogFactory implements DialogOpener.DialogFactory
}
private void initialize() {
repairBtn.setText(FontUtils.elegantIconHtml("&#xe036;", MessageUtils.getLocalizedMessage("checkidx.button.fix")));
repairBtn.setText(
FontUtils.elegantIconHtml(
"&#xe036;", MessageUtils.getLocalizedMessage("checkidx.button.fix")));
repairBtn.setFont(StyleConstants.FONT_BUTTON_LARGE);
repairBtn.setMargin(new Insets(3, 3, 3, 3));
repairBtn.setEnabled(false);
@ -126,7 +127,6 @@ public final class CheckIndexDialogFactory implements DialogOpener.DialogFactory
logArea.setEditable(false);
}
@Override
public JDialog create(Window owner, String title, int width, int height) {
dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL);
@ -171,7 +171,10 @@ public final class CheckIndexDialogFactory implements DialogOpener.DialogFactory
JPanel execButtons = new JPanel(new FlowLayout(FlowLayout.TRAILING));
execButtons.setOpaque(false);
JButton checkBtn = new JButton(FontUtils.elegantIconHtml("&#xe0f7;", MessageUtils.getLocalizedMessage("checkidx.button.check")));
JButton checkBtn =
new JButton(
FontUtils.elegantIconHtml(
"&#xe0f7;", MessageUtils.getLocalizedMessage("checkidx.button.check")));
checkBtn.setFont(StyleConstants.FONT_BUTTON_LARGE);
checkBtn.setMargin(new Insets(3, 0, 3, 0));
checkBtn.addActionListener(listeners::checkIndex);
@ -199,7 +202,8 @@ public final class CheckIndexDialogFactory implements DialogOpener.DialogFactory
repair.setOpaque(false);
repair.add(repairBtn);
JTextArea warnArea = new JTextArea(MessageUtils.getLocalizedMessage("checkidx.label.warn"), 3, 30);
JTextArea warnArea =
new JTextArea(MessageUtils.getLocalizedMessage("checkidx.label.warn"), 3, 30);
warnArea.setLineWrap(true);
warnArea.setEditable(false);
warnArea.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
@ -234,7 +238,9 @@ public final class CheckIndexDialogFactory implements DialogOpener.DialogFactory
@Override
public void openIndex(LukeState state) {
lukeState = state;
toolsModel = indexToolsFactory.newInstance(state.getIndexReader(), state.useCompound(), state.keepAllCommits());
toolsModel =
indexToolsFactory.newInstance(
state.getIndexReader(), state.useCompound(), state.keepAllCommits());
}
@Override
@ -261,46 +267,48 @@ public final class CheckIndexDialogFactory implements DialogOpener.DialogFactory
private class ListenerFunctions {
void checkIndex(ActionEvent e) {
ExecutorService executor = Executors.newFixedThreadPool(1, new NamedThreadFactory("check-index-dialog-check"));
ExecutorService executor =
Executors.newFixedThreadPool(1, new NamedThreadFactory("check-index-dialog-check"));
SwingWorker<CheckIndex.Status, Void> task = new SwingWorker<CheckIndex.Status, Void>() {
SwingWorker<CheckIndex.Status, Void> task =
new SwingWorker<CheckIndex.Status, Void>() {
@Override
protected CheckIndex.Status doInBackground() {
setProgress(0);
statusLbl.setText("Running...");
indicatorLbl.setVisible(true);
TextAreaPrintStream ps;
try {
ps = new TextAreaPrintStream(logArea);
CheckIndex.Status status = toolsModel.checkIndex(ps);
ps.flush();
return status;
} catch (Exception e) {
statusLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown"));
throw e;
} finally {
setProgress(100);
}
}
@Override
protected void done() {
try {
CheckIndex.Status st = get();
resultLbl.setText(createResultsMessage(st));
indicatorLbl.setVisible(false);
statusLbl.setText("Done");
if (!st.clean) {
repairBtn.setEnabled(true);
@Override
protected CheckIndex.Status doInBackground() {
setProgress(0);
statusLbl.setText("Running...");
indicatorLbl.setVisible(true);
TextAreaPrintStream ps;
try {
ps = new TextAreaPrintStream(logArea);
CheckIndex.Status status = toolsModel.checkIndex(ps);
ps.flush();
return status;
} catch (Exception e) {
statusLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown"));
throw e;
} finally {
setProgress(100);
}
}
status = st;
} catch (Exception e) {
log.error("Error checking index", e);
statusLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown"));
}
}
};
@Override
protected void done() {
try {
CheckIndex.Status st = get();
resultLbl.setText(createResultsMessage(st));
indicatorLbl.setVisible(false);
statusLbl.setText("Done");
if (!st.clean) {
repairBtn.setEnabled(true);
}
status = st;
} catch (Exception e) {
log.error("Error checking index", e);
statusLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown"));
}
}
};
executor.submit(task);
executor.shutdown();
@ -337,44 +345,45 @@ public final class CheckIndexDialogFactory implements DialogOpener.DialogFactory
return;
}
ExecutorService executor = Executors.newFixedThreadPool(1, new NamedThreadFactory("check-index-dialog-repair"));
ExecutorService executor =
Executors.newFixedThreadPool(1, new NamedThreadFactory("check-index-dialog-repair"));
SwingWorker<CheckIndex.Status, Void> task = new SwingWorker<CheckIndex.Status, Void>() {
SwingWorker<CheckIndex.Status, Void> task =
new SwingWorker<CheckIndex.Status, Void>() {
@Override
protected CheckIndex.Status doInBackground() {
setProgress(0);
statusLbl.setText("Running...");
indicatorLbl.setVisible(true);
logArea.setText("");
TextAreaPrintStream ps;
try {
ps = new TextAreaPrintStream(logArea);
toolsModel.repairIndex(status, ps);
statusLbl.setText("Done");
ps.flush();
return status;
} catch (Exception e) {
statusLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown"));
throw e;
} finally {
setProgress(100);
}
}
@Override
protected CheckIndex.Status doInBackground() {
setProgress(0);
statusLbl.setText("Running...");
indicatorLbl.setVisible(true);
logArea.setText("");
TextAreaPrintStream ps;
try {
ps = new TextAreaPrintStream(logArea);
toolsModel.repairIndex(status, ps);
statusLbl.setText("Done");
ps.flush();
return status;
} catch (Exception e) {
statusLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown"));
throw e;
} finally {
setProgress(100);
}
}
@Override
protected void done() {
indexHandler.open(lukeState.getIndexPath(), lukeState.getDirImpl());
logArea.append("Repairing index done.");
resultLbl.setText("");
indicatorLbl.setVisible(false);
repairBtn.setEnabled(false);
}
};
@Override
protected void done() {
indexHandler.open(lukeState.getIndexPath(), lukeState.getDirImpl());
logArea.append("Repairing index done.");
resultLbl.setText("");
indicatorLbl.setVisible(false);
repairBtn.setEnabled(false);
}
};
executor.submit(task);
executor.shutdown();
}
}
}

View File

@ -17,18 +17,6 @@
package org.apache.lucene.luke.app.desktop.components.dialog.menubar;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingWorker;
import java.awt.BorderLayout;
import java.awt.Dialog;
import java.awt.Dimension;
@ -47,7 +35,18 @@ import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingWorker;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.luke.app.IndexHandler;
import org.apache.lucene.luke.app.desktop.Preferences;
@ -99,14 +98,14 @@ public class CreateIndexDialogFactory implements DialogOpener.DialogFactory {
private JDialog dialog;
public synchronized static CreateIndexDialogFactory getInstance() throws IOException {
public static synchronized CreateIndexDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new CreateIndexDialogFactory();
}
return instance;
}
private CreateIndexDialogFactory() throws IOException {
private CreateIndexDialogFactory() throws IOException {
this.prefs = PreferencesFactory.getInstance();
this.indexHandler = IndexHandler.getInstance();
initialize();
@ -117,7 +116,8 @@ public class CreateIndexDialogFactory implements DialogOpener.DialogFactory {
locationTF.setText(System.getProperty("user.home"));
locationTF.setEditable(false);
browseBtn.setText(FontUtils.elegantIconHtml("&#x6e;", MessageUtils.getLocalizedMessage("button.browse")));
browseBtn.setText(
FontUtils.elegantIconHtml("&#x6e;", MessageUtils.getLocalizedMessage("button.browse")));
browseBtn.setFont(StyleConstants.FONT_BUTTON_LARGE);
browseBtn.setPreferredSize(new Dimension(120, 30));
browseBtn.addActionListener(listeners::browseLocationDirectory);
@ -131,7 +131,8 @@ public class CreateIndexDialogFactory implements DialogOpener.DialogFactory {
clearBtn.setPreferredSize(new Dimension(70, 30));
clearBtn.addActionListener(listeners::clearDataDir);
dataBrowseBtn.setText(FontUtils.elegantIconHtml("&#x6e;", MessageUtils.getLocalizedMessage("button.browse")));
dataBrowseBtn.setText(
FontUtils.elegantIconHtml("&#x6e;", MessageUtils.getLocalizedMessage("button.browse")));
dataBrowseBtn.setFont(StyleConstants.FONT_BUTTON_LARGE);
dataBrowseBtn.setPreferredSize(new Dimension(100, 30));
dataBrowseBtn.addActionListener(listeners::browseDataDirectory);
@ -205,7 +206,8 @@ public class CreateIndexDialogFactory implements DialogOpener.DialogFactory {
name.add(nameLbl);
description.add(name);
JTextArea descTA1 = new JTextArea(MessageUtils.getLocalizedMessage("createindex.textarea.data_help1"));
JTextArea descTA1 =
new JTextArea(MessageUtils.getLocalizedMessage("createindex.textarea.data_help1"));
descTA1.setPreferredSize(new Dimension(550, 20));
descTA1.setBorder(BorderFactory.createEmptyBorder(2, 10, 10, 5));
descTA1.setOpaque(false);
@ -215,11 +217,14 @@ public class CreateIndexDialogFactory implements DialogOpener.DialogFactory {
JPanel link = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 1));
link.setOpaque(false);
JLabel linkLbl = FontUtils.toLinkText(new URLLabel(MessageUtils.getLocalizedMessage("createindex.label.data_link")));
JLabel linkLbl =
FontUtils.toLinkText(
new URLLabel(MessageUtils.getLocalizedMessage("createindex.label.data_link")));
link.add(linkLbl);
description.add(link);
JTextArea descTA2 = new JTextArea(MessageUtils.getLocalizedMessage("createindex.textarea.data_help2"));
JTextArea descTA2 =
new JTextArea(MessageUtils.getLocalizedMessage("createindex.textarea.data_help2"));
descTA2.setPreferredSize(new Dimension(550, 50));
descTA2.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 5));
descTA2.setOpaque(false);
@ -279,66 +284,73 @@ public class CreateIndexDialogFactory implements DialogOpener.DialogFactory {
Path path = Paths.get(locationTF.getText(), dirnameTF.getText());
if (Files.exists(path)) {
String message = "The directory " + path.toAbsolutePath().toString() + " already exists.";
JOptionPane.showMessageDialog(dialog, message, "Empty index path", JOptionPane.ERROR_MESSAGE);
JOptionPane.showMessageDialog(
dialog, message, "Empty index path", JOptionPane.ERROR_MESSAGE);
} else {
// create new index asynchronously
ExecutorService executor = Executors.newFixedThreadPool(1, new NamedThreadFactory("create-index-dialog"));
ExecutorService executor =
Executors.newFixedThreadPool(1, new NamedThreadFactory("create-index-dialog"));
SwingWorker<Void, Void> task = new SwingWorker<Void, Void>() {
SwingWorker<Void, Void> task =
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
setProgress(0);
indicatorLbl.setVisible(true);
createBtn.setEnabled(false);
@Override
protected Void doInBackground() throws Exception {
setProgress(0);
indicatorLbl.setVisible(true);
createBtn.setEnabled(false);
try {
Directory dir = FSDirectory.open(path);
IndexTools toolsModel = new IndexToolsFactory().newInstance(dir);
try {
Directory dir = FSDirectory.open(path);
IndexTools toolsModel = new IndexToolsFactory().newInstance(dir);
if (dataDirTF.getText().isEmpty()) {
// without sample documents
toolsModel.createNewIndex();
} else {
// with sample documents
Path dataPath = Paths.get(dataDirTF.getText());
toolsModel.createNewIndex(dataPath.toAbsolutePath().toString());
}
indexHandler.open(path.toAbsolutePath().toString(), null, false, false, false);
prefs.addHistory(path.toAbsolutePath().toString());
dirnameTF.setText("");
closeDialog();
} catch (Exception ex) {
// cleanup
try {
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
if (dataDirTF.getText().isEmpty()) {
// without sample documents
toolsModel.createNewIndex();
} else {
// with sample documents
Path dataPath = Paths.get(dataDirTF.getText());
toolsModel.createNewIndex(dataPath.toAbsolutePath().toString());
}
});
Files.deleteIfExists(path);
} catch (IOException ex2) {
indexHandler.open(path.toAbsolutePath().toString(), null, false, false, false);
prefs.addHistory(path.toAbsolutePath().toString());
dirnameTF.setText("");
closeDialog();
} catch (Exception ex) {
// cleanup
try {
Files.walkFileTree(
path,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
});
Files.deleteIfExists(path);
} catch (IOException ex2) {
}
log.error("Cannot create index", ex);
String message = "See Logs tab or log file for more details.";
JOptionPane.showMessageDialog(
dialog, message, "Cannot create index", JOptionPane.ERROR_MESSAGE);
} finally {
setProgress(100);
}
return null;
}
log.error("Cannot create index", ex);
String message = "See Logs tab or log file for more details.";
JOptionPane.showMessageDialog(dialog, message, "Cannot create index", JOptionPane.ERROR_MESSAGE);
} finally {
setProgress(100);
}
return null;
}
@Override
protected void done() {
indicatorLbl.setVisible(false);
createBtn.setEnabled(true);
}
};
@Override
protected void done() {
indicatorLbl.setVisible(false);
createBtn.setEnabled(true);
}
};
executor.submit(task);
executor.shutdown();

View File

@ -17,16 +17,6 @@
package org.apache.lucene.luke.app.desktop.components.dialog.menubar;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingWorker;
import java.awt.Color;
import java.awt.Dialog;
import java.awt.Dimension;
@ -42,7 +32,16 @@ import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Stream;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingWorker;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.luke.app.IndexHandler;
import org.apache.lucene.luke.app.IndexObserver;
@ -61,9 +60,7 @@ import org.apache.lucene.luke.util.LoggerFactory;
import org.apache.lucene.util.NamedThreadFactory;
import org.apache.lucene.util.SuppressForbidden;
/**
* Factory of export terms dialog
*/
/** Factory of export terms dialog */
public final class ExportTermsDialogFactory implements DialogOpener.DialogFactory {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@ -94,7 +91,7 @@ public final class ExportTermsDialogFactory implements DialogOpener.DialogFactor
private String selectedDelimiter;
public synchronized static ExportTermsDialogFactory getInstance() throws IOException {
public static synchronized ExportTermsDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new ExportTermsDialogFactory();
}
@ -105,8 +102,9 @@ public final class ExportTermsDialogFactory implements DialogOpener.DialogFactor
this.prefs = PreferencesFactory.getInstance();
this.indexHandler = IndexHandler.getInstance();
indexHandler.addObserver(new Observer());
Stream.of(Delimiter.values()).forEachOrdered(delimiterVal -> delimiterCombo.addItem(delimiterVal.getDescription()));
delimiterCombo.setSelectedItem(Delimiter.COMMA.getDescription());//Set default delimiter
Stream.of(Delimiter.values())
.forEachOrdered(delimiterVal -> delimiterCombo.addItem(delimiterVal.getDescription()));
delimiterCombo.setSelectedItem(Delimiter.COMMA.getDescription()); // Set default delimiter
}
@Override
@ -230,57 +228,68 @@ public final class ExportTermsDialogFactory implements DialogOpener.DialogFactor
}
void export(ActionEvent e) {
ExecutorService executor = Executors.newSingleThreadExecutor(new NamedThreadFactory("export-terms-dialog"));
ExecutorService executor =
Executors.newSingleThreadExecutor(new NamedThreadFactory("export-terms-dialog"));
SwingWorker<Void, Void> task = new SwingWorker<Void, Void>() {
SwingWorker<Void, Void> task =
new SwingWorker<Void, Void>() {
String filename;
String filename;
@Override
protected Void doInBackground() {
setProgress(0);
statusLbl.setText("Exporting...");
indicatorLbl.setVisible(true);
String field = (String) fieldCombo.getSelectedItem();
selectedDelimiter = Delimiter.getSelectedDelimiterValue((String) delimiterCombo.getSelectedItem());
@Override
protected Void doInBackground() {
setProgress(0);
statusLbl.setText("Exporting...");
indicatorLbl.setVisible(true);
String field = (String) fieldCombo.getSelectedItem();
selectedDelimiter =
Delimiter.getSelectedDelimiterValue((String) delimiterCombo.getSelectedItem());
String directory = destDir.getText();
try {
filename = toolsModel.exportTerms(directory, field, selectedDelimiter);
} catch (LukeException e) {
log.error("Error while exporting terms from field {}", field, e);
statusLbl.setText(MessageUtils.getLocalizedMessage("export.terms.label.error", e.getMessage()));
} catch (Exception e) {
log.error("Error while exporting terms from field {}", field, e);
statusLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown"));
throw e;
} finally {
setProgress(100);
}
return null;
}
String directory = destDir.getText();
try {
filename = toolsModel.exportTerms(directory, field, selectedDelimiter);
} catch (LukeException e) {
log.error("Error while exporting terms from field {}", field, e);
statusLbl.setText(
MessageUtils.getLocalizedMessage("export.terms.label.error", e.getMessage()));
} catch (Exception e) {
log.error("Error while exporting terms from field {}", field, e);
statusLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown"));
throw e;
} finally {
setProgress(100);
}
return null;
}
@Override
protected void done() {
indicatorLbl.setVisible(false);
if (filename != null) {
statusLbl.setText(MessageUtils.getLocalizedMessage("export.terms.label.success", filename, "[term]" + selectedDelimiter + "[doc frequency]"));
}
}
};
@Override
protected void done() {
indicatorLbl.setVisible(false);
if (filename != null) {
statusLbl.setText(
MessageUtils.getLocalizedMessage(
"export.terms.label.success",
filename,
"[term]" + selectedDelimiter + "[doc frequency]"));
}
}
};
executor.submit(task);
executor.shutdown();
}
}
private class Observer implements IndexObserver {
@Override
public void openIndex(LukeState state) {
toolsModel = indexToolsFactory.newInstance(state.getIndexReader(), state.useCompound(), state.keepAllCommits());
IndexUtils.getFieldNames(state.getIndexReader()).stream().sorted().forEach(fieldCombo::addItem);
toolsModel =
indexToolsFactory.newInstance(
state.getIndexReader(), state.useCompound(), state.keepAllCommits());
IndexUtils.getFieldNames(state.getIndexReader()).stream()
.sorted()
.forEach(fieldCombo::addItem);
}
@Override
@ -288,14 +297,13 @@ public final class ExportTermsDialogFactory implements DialogOpener.DialogFactor
fieldCombo.removeAllItems();
toolsModel = null;
}
}
/**
* Delimiters that can be selected
*/
/** Delimiters that can be selected */
private enum Delimiter {
COMMA("Comma", ","), WHITESPACE("Whitespace", " "), TAB("Tab", "\t");
COMMA("Comma", ","),
WHITESPACE("Whitespace", " "),
TAB("Tab", "\t");
private final String description;
private final String separator;
@ -321,5 +329,4 @@ public final class ExportTermsDialogFactory implements DialogOpener.DialogFactor
.getSeparator();
}
}
}

View File

@ -17,19 +17,6 @@
package org.apache.lucene.luke.app.desktop.components.dialog.menubar;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSeparator;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.FlowLayout;
@ -48,7 +35,19 @@ import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSeparator;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.luke.app.DirectoryHandler;
import org.apache.lucene.luke.app.IndexHandler;
@ -98,7 +97,7 @@ public final class OpenIndexDialogFactory implements DialogOpener.DialogFactory
private JDialog dialog;
public synchronized static OpenIndexDialogFactory getInstance() throws IOException {
public static synchronized OpenIndexDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new OpenIndexDialogFactory();
}
@ -115,7 +114,8 @@ public final class OpenIndexDialogFactory implements DialogOpener.DialogFactory
private void initialize() {
idxPathCombo.setPreferredSize(new Dimension(360, 40));
browseBtn.setText(FontUtils.elegantIconHtml("&#x6e;", MessageUtils.getLocalizedMessage("button.browse")));
browseBtn.setText(
FontUtils.elegantIconHtml("&#x6e;", MessageUtils.getLocalizedMessage("button.browse")));
browseBtn.setFont(StyleConstants.FONT_BUTTON_LARGE);
browseBtn.setPreferredSize(new Dimension(120, 40));
browseBtn.addActionListener(listeners::browseDirectory);
@ -126,12 +126,14 @@ public final class OpenIndexDialogFactory implements DialogOpener.DialogFactory
readOnlyCB.setOpaque(false);
// Scanning all Directory types will take time...
ExecutorService executorService = Executors.newFixedThreadPool(1, new NamedThreadFactory("load-directory-types"));
executorService.execute(() -> {
for (String clazzName : supportedDirImpls()) {
dirImplCombo.addItem(clazzName);
}
});
ExecutorService executorService =
Executors.newFixedThreadPool(1, new NamedThreadFactory("load-directory-types"));
executorService.execute(
() -> {
for (String clazzName : supportedDirImpls()) {
dirImplCombo.addItem(clazzName);
}
});
executorService.shutdown();
dirImplCombo.setPreferredSize(new Dimension(350, 30));
dirImplCombo.setSelectedItem(prefs.getDirImpl());
@ -144,14 +146,14 @@ public final class OpenIndexDialogFactory implements DialogOpener.DialogFactory
useCompoundCB.setSelected(prefs.isUseCompound());
useCompoundCB.setOpaque(false);
keepLastCommitRB.setText(MessageUtils.getLocalizedMessage("openindex.radio.keep_only_last_commit"));
keepLastCommitRB.setText(
MessageUtils.getLocalizedMessage("openindex.radio.keep_only_last_commit"));
keepLastCommitRB.setSelected(!prefs.isKeepAllCommits());
keepLastCommitRB.setOpaque(false);
keepAllCommitsRB.setText(MessageUtils.getLocalizedMessage("openindex.radio.keep_all_commits"));
keepAllCommitsRB.setSelected(prefs.isKeepAllCommits());
keepAllCommitsRB.setOpaque(false);
}
@Override
@ -334,24 +336,32 @@ public final class OpenIndexDialogFactory implements DialogOpener.DialogFactory
String selectedPath = (String) idxPathCombo.getSelectedItem();
String dirImplClazz = (String) dirImplCombo.getSelectedItem();
if (selectedPath == null || selectedPath.length() == 0) {
String message = MessageUtils.getLocalizedMessage("openindex.message.index_path_not_selected");
JOptionPane.showMessageDialog(dialog, message, "Empty index path", JOptionPane.ERROR_MESSAGE);
String message =
MessageUtils.getLocalizedMessage("openindex.message.index_path_not_selected");
JOptionPane.showMessageDialog(
dialog, message, "Empty index path", JOptionPane.ERROR_MESSAGE);
} else if (isNoReader()) {
directoryHandler.open(selectedPath, dirImplClazz);
addHistory(selectedPath);
} else {
indexHandler.open(selectedPath, dirImplClazz, isReadOnly(), useCompound(), keepAllCommits());
indexHandler.open(
selectedPath, dirImplClazz, isReadOnly(), useCompound(), keepAllCommits());
addHistory(selectedPath);
}
prefs.setIndexOpenerPrefs(
isReadOnly(), dirImplClazz,
isNoReader(), useCompound(), keepAllCommits());
isReadOnly(), dirImplClazz, isNoReader(), useCompound(), keepAllCommits());
closeDialog();
} catch (LukeException ex) {
String message = ex.getMessage() + System.lineSeparator() + "See Logs tab or log file for more details.";
JOptionPane.showMessageDialog(dialog, message, "Invalid index path", JOptionPane.ERROR_MESSAGE);
String message =
ex.getMessage() + System.lineSeparator() + "See Logs tab or log file for more details.";
JOptionPane.showMessageDialog(
dialog, message, "Invalid index path", JOptionPane.ERROR_MESSAGE);
} catch (Throwable cause) {
JOptionPane.showMessageDialog(dialog, MessageUtils.getLocalizedMessage("message.error.unknown"), "Unknown Error", JOptionPane.ERROR_MESSAGE);
JOptionPane.showMessageDialog(
dialog,
MessageUtils.getLocalizedMessage("message.error.unknown"),
"Unknown Error",
JOptionPane.ERROR_MESSAGE);
log.error("Error opening index or directory", cause);
}
}
@ -379,7 +389,5 @@ public final class OpenIndexDialogFactory implements DialogOpener.DialogFactory
private void addHistory(String indexPath) throws IOException {
prefs.addHistory(indexPath);
}
}
}

View File

@ -17,6 +17,18 @@
package org.apache.lucene.luke.app.desktop.components.dialog.menubar;
import java.awt.BorderLayout;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
@ -30,19 +42,6 @@ import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingWorker;
import java.awt.BorderLayout;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.luke.app.IndexHandler;
import org.apache.lucene.luke.app.IndexObserver;
@ -89,7 +88,7 @@ public final class OptimizeIndexDialogFactory implements DialogOpener.DialogFact
private IndexTools toolsModel;
public synchronized static OptimizeIndexDialogFactory getInstance() throws IOException {
public static synchronized OptimizeIndexDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new OptimizeIndexDialogFactory();
}
@ -165,7 +164,10 @@ public final class OptimizeIndexDialogFactory implements DialogOpener.DialogFact
JPanel execButtons = new JPanel(new FlowLayout(FlowLayout.TRAILING));
execButtons.setOpaque(false);
JButton optimizeBtn = new JButton(FontUtils.elegantIconHtml("&#xe0ff;", MessageUtils.getLocalizedMessage("optimize.button.optimize")));
JButton optimizeBtn =
new JButton(
FontUtils.elegantIconHtml(
"&#xe0ff;", MessageUtils.getLocalizedMessage("optimize.button.optimize")));
optimizeBtn.setFont(StyleConstants.FONT_BUTTON_LARGE);
optimizeBtn.setMargin(new Insets(3, 0, 3, 0));
optimizeBtn.addActionListener(listeners::optimize);
@ -206,55 +208,56 @@ public final class OptimizeIndexDialogFactory implements DialogOpener.DialogFact
private class ListenerFunctions {
void optimize(ActionEvent e) {
ExecutorService executor = Executors.newFixedThreadPool(1, new NamedThreadFactory("optimize-index-dialog"));
ExecutorService executor =
Executors.newFixedThreadPool(1, new NamedThreadFactory("optimize-index-dialog"));
SwingWorker<Void, Void> task = new SwingWorker<Void, Void>() {
SwingWorker<Void, Void> task =
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() {
setProgress(0);
statusLbl.setText("Running...");
indicatorLbl.setVisible(true);
TextAreaPrintStream ps;
try {
ps = new TextAreaPrintStream(logArea);
toolsModel.optimize(expungeCB.isSelected(), (int) maxSegSpnr.getValue(), ps);
ps.flush();
} catch (Exception e) {
statusLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown"));
throw e;
} finally {
setProgress(100);
}
return null;
}
@Override
protected Void doInBackground() {
setProgress(0);
statusLbl.setText("Running...");
indicatorLbl.setVisible(true);
TextAreaPrintStream ps;
try {
ps = new TextAreaPrintStream(logArea);
toolsModel.optimize(expungeCB.isSelected(), (int) maxSegSpnr.getValue(), ps);
ps.flush();
} catch (Exception e) {
statusLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown"));
throw e;
} finally {
setProgress(100);
}
return null;
}
@Override
protected void done() {
indicatorLbl.setVisible(false);
statusLbl.setText("Done");
indexHandler.reOpen();
}
};
@Override
protected void done() {
indicatorLbl.setVisible(false);
statusLbl.setText("Done");
indexHandler.reOpen();
}
};
executor.submit(task);
executor.shutdown();
}
}
private class Observer implements IndexObserver {
@Override
public void openIndex(LukeState state) {
toolsModel = indexToolsFactory.newInstance(state.getIndexReader(), state.useCompound(), state.keepAllCommits());
toolsModel =
indexToolsFactory.newInstance(
state.getIndexReader(), state.useCompound(), state.keepAllCommits());
}
@Override
public void closeIndex() {
toolsModel = null;
}
}
}

View File

@ -16,4 +16,4 @@
*/
/** Dialogs used in the menu bar */
package org.apache.lucene.luke.app.desktop.components.dialog.menubar;
package org.apache.lucene.luke.app.desktop.components.dialog.menubar;

View File

@ -16,4 +16,4 @@
*/
/** Dialogs */
package org.apache.lucene.luke.app.desktop.components.dialog;
package org.apache.lucene.luke.app.desktop.components.dialog;

View File

@ -17,15 +17,6 @@
package org.apache.lucene.luke.app.desktop.components.dialog.search;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import java.awt.BorderLayout;
import java.awt.Dialog;
import java.awt.Dimension;
@ -39,7 +30,15 @@ import java.awt.datatransfer.StringSelection;
import java.io.IOException;
import java.util.Objects;
import java.util.stream.IntStream;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import org.apache.lucene.luke.app.desktop.Preferences;
import org.apache.lucene.luke.app.desktop.PreferencesFactory;
import org.apache.lucene.luke.app.desktop.util.DialogOpener;
@ -60,11 +59,11 @@ public final class ExplainDialogFactory implements DialogOpener.DialogFactory {
private Explanation explanation;
public synchronized static ExplainDialogFactory getInstance() throws IOException {
public static synchronized ExplainDialogFactory getInstance() throws IOException {
if (instance == null) {
instance = new ExplainDialogFactory();
}
return instance;
return instance;
}
private ExplainDialogFactory() throws IOException {
@ -112,13 +111,16 @@ public final class ExplainDialogFactory implements DialogOpener.DialogFactory {
JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING, 5, 5));
footer.setOpaque(false);
JButton copyBtn = new JButton(FontUtils.elegantIconHtml("&#xe0e6;", MessageUtils.getLocalizedMessage("button.copy")));
JButton copyBtn =
new JButton(
FontUtils.elegantIconHtml("&#xe0e6;", MessageUtils.getLocalizedMessage("button.copy")));
copyBtn.setMargin(new Insets(3, 3, 3, 3));
copyBtn.addActionListener(e -> {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
StringSelection selection = new StringSelection(explanationToString());
clipboard.setContents(selection, null);
});
copyBtn.addActionListener(
e -> {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
StringSelection selection = new StringSelection(explanationToString());
clipboard.setContents(selection, null);
});
footer.add(copyBtn);
JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close"));

View File

@ -16,4 +16,4 @@
*/
/** Dialogs used in the Search tab */
package org.apache.lucene.luke.app.desktop.components.dialog.search;
package org.apache.lucene.luke.app.desktop.components.dialog.search;

View File

@ -19,7 +19,6 @@ package org.apache.lucene.luke.app.desktop.components.fragments.analysis;
import java.util.List;
import java.util.Map;
import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry;
import org.apache.lucene.luke.models.analysis.Analysis;

Some files were not shown because too many files have changed in this diff Show More