mirror of https://github.com/apache/lucene.git
LUCENE-9570: code reformatting [final].
This commit is contained in:
parent
2695624a9f
commit
2cbf261032
|
@ -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 ->
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <= groupOffset.
|
||||
/**
|
||||
* Returns the grouped results. Returns null if the number of groups collected is <=
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <= 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 <= 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<BytesRef> 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<Document> 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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<>()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,5 +23,4 @@ public interface DirectoryObserver extends Observer {
|
|||
void openDirectory(LukeState state);
|
||||
|
||||
void closeDirectory();
|
||||
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -23,5 +23,4 @@ public interface IndexObserver extends Observer {
|
|||
void openIndex(LukeState state);
|
||||
|
||||
void closeIndex();
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,5 +18,4 @@
|
|||
package org.apache.lucene.luke.app;
|
||||
|
||||
/** Marker interface for observers */
|
||||
public interface Observer {
|
||||
}
|
||||
public interface Observer {}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,5 +61,4 @@ public class MessageBroker {
|
|||
|
||||
void clearStatusMessage();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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("",
|
||||
MessageUtils.getLocalizedMessage("analysis.button.test")));
|
||||
JButton executeBtn =
|
||||
new JButton(
|
||||
FontUtils.elegantIconHtml(
|
||||
"", 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) {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,4 @@ public interface AnalysisTabOperator extends ComponentOperatorRegistry.Component
|
|||
void setAnalyzerByCustomConfiguration(CustomAnalyzerConfig config);
|
||||
|
||||
Analyzer getCurrentAnalyzer();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,5 @@ public class ComponentOperatorRegistry {
|
|||
}
|
||||
|
||||
/** marker interface for operators */
|
||||
public interface ComponentOperator {
|
||||
}
|
||||
|
||||
public interface ComponentOperator {}
|
||||
}
|
||||
|
|
|
@ -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("8", MessageUtils.getLocalizedMessage("documents.button.first_term")));
|
||||
firstTermBtn.setText(
|
||||
FontUtils.elegantIconHtml(
|
||||
"8", 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("8", MessageUtils.getLocalizedMessage("documents.button.first_termdoc")));
|
||||
firstTermDocBtn.setText(
|
||||
FontUtils.elegantIconHtml(
|
||||
"8", 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("h", MessageUtils.getLocalizedMessage("documents.label.browse_doc_by_idx")));
|
||||
JLabel label =
|
||||
new JLabel(
|
||||
FontUtils.elegantIconHtml(
|
||||
"h", 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("", MessageUtils.getLocalizedMessage("documents.buttont.copy_values")));
|
||||
copyDocValuesBtn.setText(
|
||||
FontUtils.elegantIconHtml(
|
||||
"", 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("", MessageUtils.getLocalizedMessage("documents.button.mlt")));
|
||||
mltBtn.setText(
|
||||
FontUtils.elegantIconHtml(
|
||||
"", MessageUtils.getLocalizedMessage("documents.button.mlt")));
|
||||
mltBtn.setMargin(new Insets(5, 0, 5, 0));
|
||||
mltBtn.addActionListener(listeners::mltSearch);
|
||||
right.add(mltBtn);
|
||||
addDocBtn.setText(FontUtils.elegantIconHtml("Y", MessageUtils.getLocalizedMessage("documents.button.add")));
|
||||
addDocBtn.setText(
|
||||
FontUtils.elegantIconHtml(
|
||||
"Y", 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -28,4 +28,4 @@ public interface DocumentsTabOperator extends ComponentOperatorRegistry.Componen
|
|||
void seekNextTerm();
|
||||
|
||||
void showFirstTermDoc();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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(""));
|
||||
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() {}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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("", MessageUtils.getLocalizedMessage("search.button.parse")));
|
||||
parseBtn.setText(
|
||||
FontUtils.elegantIconHtml(
|
||||
"", 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("U", MessageUtils.getLocalizedMessage("search.button.search")));
|
||||
searchBtn.setText(
|
||||
FontUtils.elegantIconHtml(
|
||||
"U", 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("", MessageUtils.getLocalizedMessage("search.button.mlt")));
|
||||
mltBtn.setText(
|
||||
FontUtils.elegantIconHtml(
|
||||
"", 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("", MessageUtils.getLocalizedMessage("search.label.results")));
|
||||
JLabel label =
|
||||
new JLabel(
|
||||
FontUtils.elegantIconHtml(
|
||||
"", 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("", MessageUtils.getLocalizedMessage("search.button.del_all")));
|
||||
delBtn.setText(
|
||||
FontUtils.elegantIconHtml(
|
||||
"", 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,5 +45,4 @@ public class TabSwitcherProxy {
|
|||
public interface TabSwitcher {
|
||||
void switchTab(TabbedPaneProvider.Tab tab);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,5 +29,4 @@ public interface TableColumnInfo {
|
|||
default int getColumnWidth() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,5 +19,6 @@ package org.apache.lucene.luke.app.desktop.components.dialog.analysis;
|
|||
|
||||
/** Edit filters mode */
|
||||
public enum EditFiltersMode {
|
||||
CHARFILTER, TOKENFILTER;
|
||||
CHARFILTER,
|
||||
TOKENFILTER;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,4 +24,3 @@ import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry;
|
|||
public interface AddDocumentDialogOperator extends ComponentOperatorRegistry.ComponentOperator {
|
||||
void setAnalyzer(Analyzer analyzer);
|
||||
}
|
||||
|
||||
|
|
|
@ -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("", MessageUtils.getLocalizedMessage("button.copy")));
|
||||
JButton copyBtn =
|
||||
new JButton(
|
||||
FontUtils.elegantIconHtml("", 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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("", MessageUtils.getLocalizedMessage("button.copy")));
|
||||
JButton copyBtn =
|
||||
new JButton(
|
||||
FontUtils.elegantIconHtml("", 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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("", MessageUtils.getLocalizedMessage("checkidx.button.fix")));
|
||||
repairBtn.setText(
|
||||
FontUtils.elegantIconHtml(
|
||||
"", 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("", MessageUtils.getLocalizedMessage("checkidx.button.check")));
|
||||
JButton checkBtn =
|
||||
new JButton(
|
||||
FontUtils.elegantIconHtml(
|
||||
"", 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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("n", MessageUtils.getLocalizedMessage("button.browse")));
|
||||
browseBtn.setText(
|
||||
FontUtils.elegantIconHtml("n", 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("n", MessageUtils.getLocalizedMessage("button.browse")));
|
||||
dataBrowseBtn.setText(
|
||||
FontUtils.elegantIconHtml("n", 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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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("n", MessageUtils.getLocalizedMessage("button.browse")));
|
||||
browseBtn.setText(
|
||||
FontUtils.elegantIconHtml("n", 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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("", MessageUtils.getLocalizedMessage("optimize.button.optimize")));
|
||||
JButton optimizeBtn =
|
||||
new JButton(
|
||||
FontUtils.elegantIconHtml(
|
||||
"", 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -16,4 +16,4 @@
|
|||
*/
|
||||
|
||||
/** Dialogs */
|
||||
package org.apache.lucene.luke.app.desktop.components.dialog;
|
||||
package org.apache.lucene.luke.app.desktop.components.dialog;
|
||||
|
|
|
@ -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("", MessageUtils.getLocalizedMessage("button.copy")));
|
||||
JButton copyBtn =
|
||||
new JButton(
|
||||
FontUtils.elegantIconHtml("", 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"));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue