You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by to...@apache.org on 2019/04/10 10:30:30 UTC
[lucene-solr] branch branch_8x updated: LUCENE-2562: Add Luke as a
Lucene module
This is an automated email from the ASF dual-hosted git repository.
tomoko pushed a commit to branch branch_8x
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git
The following commit(s) were added to refs/heads/branch_8x by this push:
new 9915056 LUCENE-2562: Add Luke as a Lucene module
new c18f788 Merge branch 'branch_8x' of https://gitbox.apache.org/repos/asf/lucene-solr into branch_8x
9915056 is described below
commit 9915056cbc8380658ae2cf4e413969642d96a815
Author: Tomoko Uchida <to...@apache.org>
AuthorDate: Wed Apr 10 19:15:57 2019 +0900
LUCENE-2562: Add Luke as a Lucene module
---
dev-tools/idea/.idea/ant.xml | 1 +
dev-tools/idea/.idea/modules.xml | 1 +
dev-tools/idea/.idea/workspace.xml | 8 +
dev-tools/idea/lucene/luke/luke.iml | 33 +
lucene/CHANGES.txt | 15 +
lucene/build.xml | 2 +
lucene/ivy-ignore-conflicts.properties | 3 +-
lucene/licenses/elegant-icon-font-LICENSE-MIT.txt | 21 +
lucene/licenses/elegant-icon-font-NOTICE.txt | 3 +
lucene/licenses/log4j-LICENSE-ASL.txt | 202 ++++
lucene/licenses/log4j-NOTICE.txt | 5 +
lucene/licenses/log4j-api-2.11.2.jar.sha1 | 1 +
lucene/licenses/log4j-api-LICENSE-ASL.txt | 201 ++++
lucene/licenses/log4j-api-NOTICE.txt | 17 +
lucene/licenses/log4j-core-2.11.2.jar.sha1 | 1 +
lucene/licenses/log4j-core-LICENSE-ASL.txt | 201 ++++
lucene/licenses/log4j-core-NOTICE.txt | 17 +
lucene/luke/bin/luke.bat | 13 +
lucene/luke/bin/luke.sh | 18 +
lucene/luke/build.xml | 77 ++
lucene/luke/ivy.xml | 34 +
.../apache/lucene/luke/app/AbstractHandler.java | 47 +
.../apache/lucene/luke/app/DirectoryHandler.java | 112 ++
.../apache/lucene/luke/app/DirectoryObserver.java | 27 +
.../org/apache/lucene/luke/app/IndexHandler.java | 147 +++
.../org/apache/lucene/luke/app/IndexObserver.java | 27 +
.../java/org/apache/lucene/luke/app/LukeState.java | 57 +
.../java/org/apache/lucene/luke/app/Observer.java | 22 +
.../apache/lucene/luke/app/desktop/LukeMain.java | 94 ++
.../lucene/luke/app/desktop/MessageBroker.java | 65 ++
.../lucene/luke/app/desktop/Preferences.java | 69 ++
.../luke/app/desktop/PreferencesFactory.java | 34 +
.../lucene/luke/app/desktop/PreferencesImpl.java | 143 +++
.../desktop/components/AnalysisPanelProvider.java | 441 ++++++++
.../desktop/components/AnalysisTabOperator.java | 33 +
.../desktop/components/CommitsPanelProvider.java | 575 ++++++++++
.../components/ComponentOperatorRegistry.java | 50 +
.../desktop/components/DocumentsPanelProvider.java | 1115 ++++++++++++++++++++
.../desktop/components/DocumentsTabOperator.java | 31 +
.../app/desktop/components/LogsPanelProvider.java | 58 +
.../app/desktop/components/LukeWindowOperator.java | 25 +
.../app/desktop/components/LukeWindowProvider.java | 250 +++++
.../app/desktop/components/MenuBarProvider.java | 303 ++++++
.../desktop/components/OverviewPanelProvider.java | 644 +++++++++++
.../desktop/components/SearchPanelProvider.java | 834 +++++++++++++++
.../app/desktop/components/SearchTabOperator.java | 29 +
.../app/desktop/components/TabSwitcherProxy.java | 49 +
.../app/desktop/components/TabbedPaneProvider.java | 137 +++
.../app/desktop/components/TableColumnInfo.java | 33 +
.../app/desktop/components/TableModelBase.java | 75 ++
.../components/dialog/ConfirmDialogFactory.java | 119 +++
.../components/dialog/HelpDialogFactory.java | 106 ++
.../analysis/AnalysisChainDialogFactory.java | 158 +++
.../dialog/analysis/EditFiltersDialogFactory.java | 303 ++++++
.../dialog/analysis/EditFiltersMode.java | 23 +
.../dialog/analysis/EditParamsDialogFactory.java | 254 +++++
.../components/dialog/analysis/EditParamsMode.java | 23 +
.../analysis/TokenAttributeDialogFactory.java | 196 ++++
.../components/dialog/analysis/package-info.java | 19 +
.../dialog/documents/AddDocumentDialogFactory.java | 593 +++++++++++
.../documents/AddDocumentDialogOperator.java | 27 +
.../dialog/documents/DocValuesDialogFactory.java | 296 ++++++
.../documents/IndexOptionsDialogFactory.java | 308 ++++++
.../dialog/documents/StoredValueDialogFactory.java | 132 +++
.../dialog/documents/TermVectorDialogFactory.java | 189 ++++
.../components/dialog/documents/package-info.java | 19 +
.../dialog/menubar/AboutDialogFactory.java | 200 ++++
.../dialog/menubar/CheckIndexDialogFactory.java | 387 +++++++
.../dialog/menubar/CreateIndexDialogFactory.java | 356 +++++++
.../dialog/menubar/OpenIndexDialogFactory.java | 385 +++++++
.../dialog/menubar/OptimizeIndexDialogFactory.java | 263 +++++
.../components/dialog/menubar/package-info.java | 19 +
.../desktop/components/dialog/package-info.java | 19 +
.../dialog/search/ExplainDialogFactory.java | 182 ++++
.../components/dialog/search/package-info.java | 19 +
.../analysis/CustomAnalyzerPanelOperator.java | 45 +
.../analysis/CustomAnalyzerPanelProvider.java | 751 +++++++++++++
.../analysis/PresetAnalyzerPanelOperator.java | 30 +
.../analysis/PresetAnalyzerPanelProvider.java | 96 ++
.../fragments/analysis/package-info.java | 19 +
.../desktop/components/fragments/package-info.java | 19 +
.../fragments/search/AnalyzerPaneProvider.java | 200 ++++
.../fragments/search/AnalyzerTabOperator.java | 27 +
.../fragments/search/FieldValuesPaneProvider.java | 206 ++++
.../fragments/search/FieldValuesTabOperator.java | 30 +
.../fragments/search/MLTPaneProvider.java | 303 ++++++
.../fragments/search/MLTTabOperator.java | 33 +
.../fragments/search/QueryParserPaneProvider.java | 513 +++++++++
.../fragments/search/QueryParserTabOperator.java | 35 +
.../fragments/search/SimilarityPaneProvider.java | 145 +++
.../fragments/search/SimilarityTabOperator.java | 26 +
.../fragments/search/SortPaneProvider.java | 255 +++++
.../fragments/search/SortTabOperator.java | 34 +
.../components/fragments/search/package-info.java | 19 +
.../luke/app/desktop/components/package-info.java | 19 +
.../luke/app/desktop/dto/documents/NewField.java | 148 +++
.../app/desktop/dto/documents/package-info.java | 19 +
.../lucene/luke/app/desktop/package-info.java | 19 +
.../lucene/luke/app/desktop/util/DialogOpener.java | 52 +
.../luke/app/desktop/util/ExceptionHandler.java | 44 +
.../lucene/luke/app/desktop/util/FontUtils.java | 71 ++
.../luke/app/desktop/util/HelpHeaderRenderer.java | 129 +++
.../lucene/luke/app/desktop/util/ImageUtils.java | 45 +
.../lucene/luke/app/desktop/util/ListUtils.java | 43 +
.../lucene/luke/app/desktop/util/MessageUtils.java | 61 ++
.../lucene/luke/app/desktop/util/NumericUtils.java | 103 ++
.../lucene/luke/app/desktop/util/StringUtils.java | 31 +
.../luke/app/desktop/util/StyleConstants.java | 43 +
.../lucene/luke/app/desktop/util/TabUtils.java | 41 +
.../lucene/luke/app/desktop/util/TableUtils.java | 85 ++
.../luke/app/desktop/util/TextAreaAppender.java | 102 ++
.../luke/app/desktop/util/TextAreaPrintStream.java | 50 +
.../lucene/luke/app/desktop/util/URLLabel.java | 65 ++
.../luke/app/desktop/util/inifile/IniFile.java | 36 +
.../app/desktop/util/inifile/IniFileReader.java | 29 +
.../app/desktop/util/inifile/IniFileWriter.java | 29 +
.../luke/app/desktop/util/inifile/OptionMap.java | 33 +
.../app/desktop/util/inifile/SimpleIniFile.java | 82 ++
.../desktop/util/inifile/SimpleIniFileReader.java | 63 ++
.../desktop/util/inifile/SimpleIniFileWriter.java | 47 +
.../app/desktop/util/inifile/package-info.java | 19 +
.../luke/app/desktop/util/lang/Callable.java | 24 +
.../luke/app/desktop/util/lang/package-info.java | 19 +
.../lucene/luke/app/desktop/util/package-info.java | 19 +
.../org/apache/lucene/luke/app/package-info.java | 19 +
.../apache/lucene/luke/models/LukeException.java | 35 +
.../org/apache/lucene/luke/models/LukeModel.java | 71 ++
.../lucene/luke/models/analysis/Analysis.java | 152 +++
.../luke/models/analysis/AnalysisFactory.java | 27 +
.../lucene/luke/models/analysis/AnalysisImpl.java | 217 ++++
.../luke/models/analysis/CustomAnalyzerConfig.java | 133 +++
.../lucene/luke/models/analysis/package-info.java | 19 +
.../apache/lucene/luke/models/commits/Commit.java | 68 ++
.../apache/lucene/luke/models/commits/Commits.java | 82 ++
.../lucene/luke/models/commits/CommitsFactory.java | 34 +
.../lucene/luke/models/commits/CommitsImpl.java | 224 ++++
.../apache/lucene/luke/models/commits/File.java | 52 +
.../apache/lucene/luke/models/commits/Segment.java | 95 ++
.../lucene/luke/models/commits/package-info.java | 19 +
.../lucene/luke/models/documents/DocValues.java | 84 ++
.../luke/models/documents/DocValuesAdapter.java | 168 +++
.../luke/models/documents/DocumentField.java | 169 +++
.../lucene/luke/models/documents/Documents.java | 143 +++
.../luke/models/documents/DocumentsFactory.java | 29 +
.../luke/models/documents/DocumentsImpl.java | 347 ++++++
.../lucene/luke/models/documents/TermPosting.java | 90 ++
.../luke/models/documents/TermVectorEntry.java | 177 ++++
.../luke/models/documents/TermVectorsAdapter.java | 71 ++
.../lucene/luke/models/documents/package-info.java | 19 +
.../lucene/luke/models/overview/Overview.java | 121 +++
.../luke/models/overview/OverviewFactory.java | 29 +
.../lucene/luke/models/overview/OverviewImpl.java | 171 +++
.../lucene/luke/models/overview/TermCounts.java | 82 ++
.../luke/models/overview/TermCountsOrder.java | 43 +
.../lucene/luke/models/overview/TermStats.java | 76 ++
.../lucene/luke/models/overview/TopTerms.java | 68 ++
.../lucene/luke/models/overview/package-info.java | 19 +
.../apache/lucene/luke/models/package-info.java | 19 +
.../lucene/luke/models/search/MLTConfig.java | 96 ++
.../luke/models/search/QueryParserConfig.java | 252 +++++
.../apache/lucene/luke/models/search/Search.java | 158 +++
.../lucene/luke/models/search/SearchFactory.java | 29 +
.../lucene/luke/models/search/SearchImpl.java | 471 +++++++++
.../lucene/luke/models/search/SearchResults.java | 161 +++
.../luke/models/search/SimilarityConfig.java | 100 ++
.../lucene/luke/models/search/package-info.java | 19 +
.../lucene/luke/models/tools/IndexTools.java | 97 ++
.../luke/models/tools/IndexToolsFactory.java | 34 +
.../lucene/luke/models/tools/IndexToolsImpl.java | 187 ++++
.../lucene/luke/models/tools/package-info.java | 19 +
.../apache/lucene/luke/models/util/IndexUtils.java | 497 +++++++++
.../lucene/luke/models/util/package-info.java | 19 +
.../luke/models/util/twentynewsgroups/Message.java | 182 ++++
.../util/twentynewsgroups/MessageFilesParser.java | 123 +++
.../models/util/twentynewsgroups/package-info.java | 19 +
.../java/org/apache/lucene/luke/package-info.java | 19 +
.../org/apache/lucene/luke/util/BytesRefUtils.java | 37 +
.../org/apache/lucene/luke/util/LoggerFactory.java | 73 ++
.../org/apache/lucene/luke/util/package-info.java | 19 +
.../lucene/luke/util/reflection/ClassScanner.java | 113 ++
.../luke/util/reflection/SubtypeCollector.java | 101 ++
.../lucene/luke/util/reflection/package-info.java | 19 +
lucene/luke/src/java/overview.html | 26 +
.../lucene/luke/app/desktop/font/ElegantIcons.ttf | Bin 0 -> 59388 bytes
.../lucene/luke/app/desktop/img/indicator.gif | Bin 0 -> 673 bytes
.../lucene/luke/app/desktop/img/lucene-logo.gif | Bin 0 -> 1337 bytes
.../apache/lucene/luke/app/desktop/img/lucene.gif | Bin 0 -> 335 bytes
.../lucene/luke/app/desktop/img/luke-logo.gif | Bin 0 -> 2408 bytes
.../luke/app/desktop/messages/messages.properties | 280 +++++
.../desktop/util/inifile/SimpleIniFileTest.java | 115 ++
.../luke/models/analysis/AnalysisImplTest.java | 136 +++
.../luke/models/commits/CommitsImplTest.java | 214 ++++
.../models/documents/DocValuesAdapterTest.java | 114 ++
.../luke/models/documents/DocumentsImplTest.java | 248 +++++
.../luke/models/documents/DocumentsTestBase.java | 152 +++
.../models/documents/TermVectorsAdapterTest.java | 165 +++
.../luke/models/overview/OverviewImplTest.java | 140 +++
.../luke/models/overview/OverviewTestBase.java | 95 ++
.../luke/models/overview/TermCountsTest.java | 82 ++
.../lucene/luke/models/overview/TopTermsTest.java | 40 +
.../lucene/luke/models/search/SearchImplTest.java | 380 +++++++
lucene/module-build.xml | 22 +
lucene/tools/junit4/tests.policy | 6 +-
203 files changed, 23580 insertions(+), 2 deletions(-)
diff --git a/dev-tools/idea/.idea/ant.xml b/dev-tools/idea/.idea/ant.xml
index 229d832..d3f9655 100644
--- a/dev-tools/idea/.idea/ant.xml
+++ b/dev-tools/idea/.idea/ant.xml
@@ -24,6 +24,7 @@
<buildFile url="file://$PROJECT_DIR$/lucene/grouping/build.xml" />
<buildFile url="file://$PROJECT_DIR$/lucene/highlighter/build.xml" />
<buildFile url="file://$PROJECT_DIR$/lucene/join/build.xml" />
+ <buildFile url="file://$PROJECT_DIR$/lucene/luke/build.xml" />
<buildFile url="file://$PROJECT_DIR$/lucene/memory/build.xml" />
<buildFile url="file://$PROJECT_DIR$/lucene/misc/build.xml" />
<buildFile url="file://$PROJECT_DIR$/lucene/queries/build.xml" />
diff --git a/dev-tools/idea/.idea/modules.xml b/dev-tools/idea/.idea/modules.xml
index 65b57fb..4974f19 100644
--- a/dev-tools/idea/.idea/modules.xml
+++ b/dev-tools/idea/.idea/modules.xml
@@ -30,6 +30,7 @@
<module group="Lucene/Other" filepath="$PROJECT_DIR$/lucene/grouping/grouping.iml" />
<module group="Lucene/Other" filepath="$PROJECT_DIR$/lucene/highlighter/highlighter.iml" />
<module group="Lucene/Other" filepath="$PROJECT_DIR$/lucene/join/join.iml" />
+ <module group="Lucene/Other" filepath="$PROJECT_DIR$/lucene/luke/luke.iml" />
<module group="Lucene/Other" filepath="$PROJECT_DIR$/lucene/memory/memory.iml" />
<module group="Lucene/Other" filepath="$PROJECT_DIR$/lucene/misc/misc.iml" />
<module group="Lucene/Other" filepath="$PROJECT_DIR$/lucene/queries/queries.iml" />
diff --git a/dev-tools/idea/.idea/workspace.xml b/dev-tools/idea/.idea/workspace.xml
index 6a1fd0a..bbc271e 100644
--- a/dev-tools/idea/.idea/workspace.xml
+++ b/dev-tools/idea/.idea/workspace.xml
@@ -148,6 +148,14 @@
<option name="TEST_SEARCH_SCOPE"><value defaultName="singleModule" /></option>
<patterns><pattern testClass=".*\.Test[^.]*|.*\.[^.]*Test" /></patterns>
</configuration>
+ <configuration default="false" name="Module luke" type="JUnit" factoryName="JUnit">
+ <module name="luke" />
+ <option name="TEST_OBJECT" value="pattern" />
+ <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/idea-build/lucene/luke" />
+ <option name="VM_PARAMETERS" value="-ea -DtempDir=temp" />
+ <option name="TEST_SEARCH_SCOPE"><value defaultName="singleModule" /></option>
+ <patterns><pattern testClass=".*\.Test[^.]*|.*\.[^.]*Test" /></patterns>
+ </configuration>
<configuration default="false" name="Module memory" type="JUnit" factoryName="JUnit">
<module name="memory" />
<option name="TEST_OBJECT" value="pattern" />
diff --git a/dev-tools/idea/lucene/luke/luke.iml b/dev-tools/idea/lucene/luke/luke.iml
new file mode 100644
index 0000000..9bd08ef
--- /dev/null
+++ b/dev-tools/idea/lucene/luke/luke.iml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="false">
+ <output url="file://$MODULE_DIR$/../../idea-build/lucene/luke/classes/java" />
+ <output-test url="file://$MODULE_DIR$/../../idea-build/lucene/luke/classes/test" />
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/resources" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test" isTestSource="true" />
+ <excludeFolder url="file://$MODULE_DIR$/work" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module-library">
+ <library>
+ <CLASSES>
+ <root url="file://$MODULE_DIR$/lib" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ <jarDirectory url="file://$MODULE_DIR$/lib" recursive="false" />
+ </library>
+ </orderEntry>
+ <orderEntry type="library" scope="TEST" name="JUnit" level="project" />
+ <orderEntry type="module" scope="TEST" module-name="lucene-test-framework" />
+ <orderEntry type="module" module-name="lucene-core" />
+ <orderEntry type="module" module-name="analysis-common" />
+ <orderEntry type="module" module-name="misc" />
+ <orderEntry type="module" module-name="queries" />
+ <orderEntry type="module" module-name="queryparser" />
+ </component>
+</module>
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 93b2d6a..024a9f9 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -19,6 +19,21 @@ API Changes
subclasses override it. FilterDirectory now delegates the call, ensuring
correct default behaviour for subclasses. (Henning Andersen)
+New Features
+
+* LUCENE-2562: The well-known graphical user interface for inspecting Lucene
+ indexes "Luke" was added as a Lucene module. It can be started from the
+ binary distribution by calling the shell scripts in the module folder
+ or from the source checkout by using `ant -f lucene/luke/build.xml run`.
+ Luke provides a Swing-based user interface and can be used to open
+ Lucene or Solr (or Elasticsearch) indexes, inspect documents, check index
+ commits and segments, or test (custom) analyzers. It also has maintenance
+ functions to check index structures and force merge indexes for archival.
+ Luke was originally developed by Andrzej Bialecki, later maintained by
+ Dmitry Kan and finally rewritten by Tomoko Uchida to use the ASF licensing
+ compatible Swing framework (as shipped with JDKs).
+ (Tomoko Uchida, Uwe Schindler)
+
Bug fixes
* LUCENE-8712: Polygon2D does not detect crossings through segment edges.
diff --git a/lucene/build.xml b/lucene/build.xml
index 3c1439c..e3cf905 100644
--- a/lucene/build.xml
+++ b/lucene/build.xml
@@ -287,6 +287,7 @@
<zipfileset prefix="lucene-${version}" dir="${build.dir}">
<patternset refid="binary.build.dist.patterns"/>
</zipfileset>
+ <zipfileset prefix="lucene-${version}" dir="${build.dir}" includes="**/*.sh,**/*.bat" filemode="755"/>
</zip>
<make-checksums file="${dist.dir}/lucene-${version}.zip"/>
</target>
@@ -310,6 +311,7 @@
<tarfileset prefix="lucene-${version}" dir="${build.dir}">
<patternset refid="binary.build.dist.patterns"/>
</tarfileset>
+ <tarfileset prefix="lucene-${version}" dir="${build.dir}" includes="**/*.sh,**/*.bat" filemode="755"/>
</tar>
<make-checksums file="${dist.dir}/lucene-${version}.tgz"/>
</target>
diff --git a/lucene/ivy-ignore-conflicts.properties b/lucene/ivy-ignore-conflicts.properties
index 6300bdf..df3a2e5 100644
--- a/lucene/ivy-ignore-conflicts.properties
+++ b/lucene/ivy-ignore-conflicts.properties
@@ -10,4 +10,5 @@
# trigger a conflict) when the ant check-lib-versions target is run.
/com.google.guava/guava = 16.0.1
-/org.ow2.asm/asm = 5.0_BETA
\ No newline at end of file
+/org.ow2.asm/asm = 5.0_BETA
+
diff --git a/lucene/licenses/elegant-icon-font-LICENSE-MIT.txt b/lucene/licenses/elegant-icon-font-LICENSE-MIT.txt
new file mode 100644
index 0000000..effefee
--- /dev/null
+++ b/lucene/licenses/elegant-icon-font-LICENSE-MIT.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) <2013> <Elegant Themes, Inc.>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/lucene/licenses/elegant-icon-font-NOTICE.txt b/lucene/licenses/elegant-icon-font-NOTICE.txt
new file mode 100644
index 0000000..ea97d9b
--- /dev/null
+++ b/lucene/licenses/elegant-icon-font-NOTICE.txt
@@ -0,0 +1,3 @@
+The Elegant Icon Font web page: https://www.elegantthemes.com/blog/resources/elegant-icon-font
+
+These icons are dual licensed under the GPL 2.0 and MIT, and are completely free to use.
diff --git a/lucene/licenses/log4j-LICENSE-ASL.txt b/lucene/licenses/log4j-LICENSE-ASL.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/lucene/licenses/log4j-LICENSE-ASL.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/lucene/licenses/log4j-NOTICE.txt b/lucene/licenses/log4j-NOTICE.txt
new file mode 100644
index 0000000..d697542
--- /dev/null
+++ b/lucene/licenses/log4j-NOTICE.txt
@@ -0,0 +1,5 @@
+Apache log4j
+Copyright 2010 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/lucene/licenses/log4j-api-2.11.2.jar.sha1 b/lucene/licenses/log4j-api-2.11.2.jar.sha1
new file mode 100644
index 0000000..0cdea10
--- /dev/null
+++ b/lucene/licenses/log4j-api-2.11.2.jar.sha1
@@ -0,0 +1 @@
+f5e9a2ffca496057d6891a3de65128efc636e26e
diff --git a/lucene/licenses/log4j-api-LICENSE-ASL.txt b/lucene/licenses/log4j-api-LICENSE-ASL.txt
new file mode 100644
index 0000000..f49a4e1
--- /dev/null
+++ b/lucene/licenses/log4j-api-LICENSE-ASL.txt
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/lucene/licenses/log4j-api-NOTICE.txt b/lucene/licenses/log4j-api-NOTICE.txt
new file mode 100644
index 0000000..ebba5ac
--- /dev/null
+++ b/lucene/licenses/log4j-api-NOTICE.txt
@@ -0,0 +1,17 @@
+Apache Log4j
+Copyright 1999-2017 Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+ResolverUtil.java
+Copyright 2005-2006 Tim Fennell
+
+Dumbster SMTP test server
+Copyright 2004 Jason Paul Kitchen
+
+TypeUtil.java
+Copyright 2002-2012 Ramnivas Laddad, Juergen Hoeller, Chris Beams
+
+picocli (http://picocli.info)
+Copyright 2017 Remko Popma
\ No newline at end of file
diff --git a/lucene/licenses/log4j-core-2.11.2.jar.sha1 b/lucene/licenses/log4j-core-2.11.2.jar.sha1
new file mode 100644
index 0000000..ec2acae
--- /dev/null
+++ b/lucene/licenses/log4j-core-2.11.2.jar.sha1
@@ -0,0 +1 @@
+6c2fb3f5b7cd27504726aef1b674b542a0c9cf53
diff --git a/lucene/licenses/log4j-core-LICENSE-ASL.txt b/lucene/licenses/log4j-core-LICENSE-ASL.txt
new file mode 100644
index 0000000..f49a4e1
--- /dev/null
+++ b/lucene/licenses/log4j-core-LICENSE-ASL.txt
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/lucene/licenses/log4j-core-NOTICE.txt b/lucene/licenses/log4j-core-NOTICE.txt
new file mode 100644
index 0000000..ebba5ac
--- /dev/null
+++ b/lucene/licenses/log4j-core-NOTICE.txt
@@ -0,0 +1,17 @@
+Apache Log4j
+Copyright 1999-2017 Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+ResolverUtil.java
+Copyright 2005-2006 Tim Fennell
+
+Dumbster SMTP test server
+Copyright 2004 Jason Paul Kitchen
+
+TypeUtil.java
+Copyright 2002-2012 Ramnivas Laddad, Juergen Hoeller, Chris Beams
+
+picocli (http://picocli.info)
+Copyright 2017 Remko Popma
\ No newline at end of file
diff --git a/lucene/luke/bin/luke.bat b/lucene/luke/bin/luke.bat
new file mode 100644
index 0000000..4d83d8b
--- /dev/null
+++ b/lucene/luke/bin/luke.bat
@@ -0,0 +1,13 @@
+@echo off
+@setlocal enabledelayedexpansion
+
+cd /d %~dp0
+
+set JAVA_OPTIONS=%JAVA_OPTIONS% -Xmx1024m -Xms512m -XX:MaxMetaspaceSize=256m
+
+set CLASSPATHS=.\*;.\lib\*;..\core\*;..\codecs\*;..\backward-codecs\*;..\queries\*;..\queryparser\*;..\suggest\*;..\misc\*
+for /d %%A in (..\analysis\*) do (
+ set "CLASSPATHS=!CLASSPATHS!;%%A\*;%%A\lib\*"
+)
+
+start javaw -cp %CLASSPATHS% %JAVA_OPTIONS% org.apache.lucene.luke.app.desktop.LukeMain
diff --git a/lucene/luke/bin/luke.sh b/lucene/luke/bin/luke.sh
new file mode 100755
index 0000000..7c7d919
--- /dev/null
+++ b/lucene/luke/bin/luke.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+LUKE_HOME=$(cd $(dirname $0) && pwd)
+cd ${LUKE_HOME}
+
+JAVA_OPTIONS="${JAVA_OPTIONS} -Xmx1024m -Xms512m -XX:MaxMetaspaceSize=256m"
+
+CLASSPATHS="./*:./lib/*:../core/*:../codecs/*:../backward-codecs/*:../queries/*:../queryparser/*:../suggest/*:../misc/*"
+for dir in `ls ../analysis`; do
+ CLASSPATHS="${CLASSPATHS}:../analysis/${dir}/*:../analysis/${dir}/lib/*"
+done
+
+LOG_DIR=${HOME}/.luke.d/
+ if [[ ! -d ${LOG_DIR} ]]; then
+ mkdir ${LOG_DIR}
+ fi
+
+nohup java -cp ${CLASSPATHS} ${JAVA_OPTIONS} org.apache.lucene.luke.app.desktop.LukeMain > ${LOG_DIR}/luke_out.log 2>&1 &
diff --git a/lucene/luke/build.xml b/lucene/luke/build.xml
new file mode 100644
index 0000000..9064d26
--- /dev/null
+++ b/lucene/luke/build.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0"?>
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<project name="luke" default="default">
+
+ <description>
+ Luke - Lucene Toolbox
+ </description>
+
+ <!-- use full Java SE API (project default 'compact2' does not include Swing) -->
+ <property name="javac.profile.args" value=""/>
+
+ <import file="../module-build.xml"/>
+
+ <target name="init" depends="module-build.init,jar-lucene-core"/>
+
+ <path id="classpath">
+ <pathelement path="${lucene-core.jar}"/>
+ <pathelement path="${codecs.jar}"/>
+ <pathelement path="${backward-codecs.jar}"/>
+ <pathelement path="${analyzers-common.jar}"/>
+ <pathelement path="${misc.jar}"/>
+ <pathelement path="${queryparser.jar}"/>
+ <pathelement path="${queries.jar}"/>
+ <fileset dir="lib"/>
+ <path refid="base.classpath"/>
+ </path>
+
+ <target name="javadocs" depends="compile-core,javadocs-lucene-core,javadocs-analyzers-common,check-javadocs-uptodate"
+ unless="javadocs-uptodate-${name}">
+ <invoke-module-javadoc>
+ <links>
+ <link href="../analyzers-common"/>
+ </links>
+ </invoke-module-javadoc>
+ </target>
+
+ <target name="build-artifacts-and-tests" depends="jar, compile-test">
+ <!-- copy start scripts -->
+ <copy todir="${build.dir}">
+ <fileset dir="${common.dir}/luke/bin">
+ <include name="**/*.sh"/>
+ <include name="**/*.bat"/>
+ </fileset>
+ </copy>
+ </target>
+
+ <!-- launch Luke -->
+ <target name="run" depends="compile-core" description="Launch Luke GUI">
+ <java classname="org.apache.lucene.luke.app.desktop.LukeMain"
+ classpath="${build.dir}/classes/java"
+ fork="true"
+ maxmemory="512m">
+ <classpath refid="classpath"/>
+ </java>
+ </target>
+
+ <target name="compile-core"
+ depends="jar-codecs,jar-backward-codecs,jar-analyzers-common,jar-misc,jar-queryparser,jar-queries,jar-misc,common.compile-core"/>
+
+</project>
diff --git a/lucene/luke/ivy.xml b/lucene/luke/ivy.xml
new file mode 100644
index 0000000..88d9d8c
--- /dev/null
+++ b/lucene/luke/ivy.xml
@@ -0,0 +1,34 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<ivy-module version="2.0">
+ <info organisation="org.apache.lucene" module="luke"/>
+
+ <configurations defaultconfmapping="compile->default;logging->default">
+ <conf name="compile" transitive="false"/>
+ <conf name="logging" transitive="false"/>
+ </configurations>
+
+ <dependencies>
+ <dependency org="org.apache.logging.log4j" name="log4j-api" rev="${/org.apache.logging.log4j/log4j-api}"
+ conf="logging"/>
+ <dependency org="org.apache.logging.log4j" name="log4j-core" rev="${/org.apache.logging.log4j/log4j-core}"
+ conf="logging"/>
+ <exclude org="*" ext="*" matcher="regexp" type="${ivy.exclude.types}"/>
+ </dependencies>
+</ivy-module>
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/AbstractHandler.java b/lucene/luke/src/java/org/apache/lucene/luke/app/AbstractHandler.java
new file mode 100644
index 0000000..ab967a8
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/AbstractHandler.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+
+/** Abstract handler class */
+public abstract class AbstractHandler<T extends Observer> {
+
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private List<T> observers = new ArrayList<>();
+
+ public void addObserver(T observer) {
+ observers.add(observer);
+ log.debug("{} registered.", observer.getClass().getName());
+ }
+
+ void notifyObservers() {
+ for (T observer : observers) {
+ notifyOne(observer);
+ }
+ }
+
+ protected abstract void notifyOne(T observer);
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/DirectoryHandler.java b/lucene/luke/src/java/org/apache/lucene/luke/app/DirectoryHandler.java
new file mode 100644
index 0000000..ec4e7e5
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/DirectoryHandler.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+import org.apache.lucene.store.Directory;
+
+/** Directory open/close handler */
+public final class DirectoryHandler extends AbstractHandler<DirectoryObserver> {
+
+ private static final DirectoryHandler instance = new DirectoryHandler();
+
+ private LukeStateImpl state;
+
+ public static DirectoryHandler getInstance() {
+ return instance;
+ }
+
+ @Override
+ protected void notifyOne(DirectoryObserver observer) {
+ if (state.closed) {
+ observer.closeDirectory();
+ } else {
+ observer.openDirectory(state);
+ }
+ }
+
+ public boolean directoryOpened() {
+ return state != null && !state.closed;
+ }
+
+ public void open(String indexPath, String dirImpl) {
+ Objects.requireNonNull(indexPath);
+
+ if (directoryOpened()) {
+ close();
+ }
+
+ Directory dir;
+ try {
+ dir = IndexUtils.openDirectory(indexPath, dirImpl);
+ } catch (IOException e) {
+ throw new LukeException(MessageUtils.getLocalizedMessage("openindex.message.index_path_invalid", indexPath), e);
+ }
+
+ state = new LukeStateImpl();
+ state.indexPath = indexPath;
+ state.dirImpl = dirImpl;
+ state.dir = dir;
+
+ notifyObservers();
+ }
+
+ public void close() {
+ if (state == null) {
+ return;
+ }
+
+ IndexUtils.close(state.dir);
+
+ state.closed = true;
+ notifyObservers();
+ }
+
+ public LukeState getState() {
+ return state;
+ }
+
+ private static class LukeStateImpl implements LukeState {
+ private boolean closed = false;
+
+ private String indexPath;
+ private String dirImpl;
+ private Directory dir;
+
+ @Override
+ public String getIndexPath() {
+ return indexPath;
+ }
+
+ @Override
+ public String getDirImpl() {
+ return dirImpl;
+ }
+
+ @Override
+ public Directory getDirectory() {
+ return dir;
+ }
+ }
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/DirectoryObserver.java b/lucene/luke/src/java/org/apache/lucene/luke/app/DirectoryObserver.java
new file mode 100644
index 0000000..6437115
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/DirectoryObserver.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app;
+
+/** Directory open/close observer */
+public interface DirectoryObserver extends Observer {
+
+ void openDirectory(LukeState state);
+
+ void closeDirectory();
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/IndexHandler.java b/lucene/luke/src/java/org/apache/lucene/luke/app/IndexHandler.java
new file mode 100644
index 0000000..17e4070
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/IndexHandler.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+import org.apache.lucene.luke.models.LukeException;
+import org.apache.lucene.luke.models.util.IndexUtils;
+import org.apache.lucene.luke.util.LoggerFactory;
+
+/** Index open/close handler */
+public final class IndexHandler extends AbstractHandler<IndexObserver> {
+
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private static final IndexHandler instance = new IndexHandler();
+
+ private LukeStateImpl state;
+
+ public static IndexHandler getInstance() {
+ return instance;
+ }
+
+ @Override
+ protected void notifyOne(IndexObserver observer) {
+ if (state.closed) {
+ observer.closeIndex();
+ } else {
+ observer.openIndex(state);
+ }
+ }
+
+ public boolean indexOpened() {
+ return state != null && !state.closed;
+ }
+
+ public void open(String indexPath, String dirImpl) {
+ open(indexPath, dirImpl, false, false, false);
+ }
+
+ public void open(String indexPath, String dirImpl, boolean readOnly, boolean useCompound, boolean keepAllCommits) {
+ Objects.requireNonNull(indexPath);
+
+ if (indexOpened()) {
+ close();
+ }
+
+ IndexReader reader;
+ try {
+ reader = IndexUtils.openIndex(indexPath, dirImpl);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ throw new LukeException(MessageUtils.getLocalizedMessage("openindex.message.index_path_invalid", indexPath), e);
+ }
+
+ state = new LukeStateImpl();
+ state.indexPath = indexPath;
+ state.reader = reader;
+ state.dirImpl = dirImpl;
+ state.readOnly = readOnly;
+ state.useCompound = useCompound;
+ state.keepAllCommits = keepAllCommits;
+
+ notifyObservers();
+ }
+
+ public void close() {
+ if (state == null) {
+ return;
+ }
+
+ IndexUtils.close(state.reader);
+
+ state.closed = true;
+ notifyObservers();
+ }
+
+ public void reOpen() {
+ close();
+ open(state.getIndexPath(), state.getDirImpl(), state.readOnly(), state.useCompound(), state.keepAllCommits());
+ }
+
+ public LukeState getState() {
+ return state;
+ }
+
+ private static class LukeStateImpl implements LukeState {
+
+ private boolean closed = false;
+
+ private String indexPath;
+ private IndexReader reader;
+ private String dirImpl;
+ private boolean readOnly;
+ private boolean useCompound;
+ private boolean keepAllCommits;
+
+ @Override
+ public String getIndexPath() {
+ return indexPath;
+ }
+
+ @Override
+ public IndexReader getIndexReader() {
+ return reader;
+ }
+
+ @Override
+ public String getDirImpl() {
+ return dirImpl;
+ }
+
+ @Override
+ public boolean readOnly() {
+ return readOnly;
+ }
+
+ @Override
+ public boolean useCompound() {
+ return useCompound;
+ }
+
+ @Override
+ public boolean keepAllCommits() {
+ return keepAllCommits;
+ }
+ }
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/IndexObserver.java b/lucene/luke/src/java/org/apache/lucene/luke/app/IndexObserver.java
new file mode 100644
index 0000000..599b109
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/IndexObserver.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app;
+
+/** Index open/close observer */
+public interface IndexObserver extends Observer {
+
+ void openIndex(LukeState state);
+
+ void closeIndex();
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/LukeState.java b/lucene/luke/src/java/org/apache/lucene/luke/app/LukeState.java
new file mode 100644
index 0000000..33ca829
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/LukeState.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app;
+
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.store.Directory;
+
+/**
+ * Holder for current index/directory.
+ */
+public interface LukeState {
+
+ String getIndexPath();
+
+ String getDirImpl();
+
+ default Directory getDirectory() {
+ throw new UnsupportedOperationException();
+ }
+
+ default IndexReader getIndexReader() {
+ throw new UnsupportedOperationException();
+ }
+
+ default boolean readOnly() {
+ throw new UnsupportedOperationException();
+ }
+
+ default boolean useCompound() {
+ throw new UnsupportedOperationException();
+ }
+
+ default boolean keepAllCommits() {
+ throw new UnsupportedOperationException();
+ }
+
+ default boolean hasDirectoryReader() {
+ return getIndexReader() instanceof DirectoryReader;
+ }
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/Observer.java b/lucene/luke/src/java/org/apache/lucene/luke/app/Observer.java
new file mode 100644
index 0000000..290865b
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/Observer.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app;
+
+/** Marker interface for observers */
+public interface Observer {
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/LukeMain.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/LukeMain.java
new file mode 100644
index 0000000..fae52f2
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/LukeMain.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop;
+
+import javax.swing.JFrame;
+import javax.swing.UIManager;
+import java.awt.GraphicsEnvironment;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.nio.file.FileSystems;
+
+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;
+import org.apache.lucene.luke.app.desktop.util.DialogOpener;
+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";
+
+ static {
+ LoggerFactory.initGuiLogging(LOG_FILE);
+ }
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private static JFrame frame;
+
+ public static JFrame getOwnerFrame() {
+ return frame;
+ }
+
+ private static void createAndShowGUI() {
+ // uncaught error handler
+ MessageBroker messageBroker = MessageBroker.getInstance();
+ Thread.setDefaultUncaughtExceptionHandler((thread, cause) ->
+ handle(cause, messageBroker)
+ );
+
+ try {
+ frame = new LukeWindowProvider().get();
+ frame.setLocation(200, 100);
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ frame.pack();
+ frame.setVisible(true);
+
+ // show open index dialog
+ OpenIndexDialogFactory openIndexDialogFactory = OpenIndexDialogFactory.getInstance();
+ new DialogOpener<>(openIndexDialogFactory).open(MessageUtils.getLocalizedMessage("openindex.dialog.title"), 600, 420,
+ (factory) -> {
+ });
+ } catch (IOException e) {
+ messageBroker.showUnknownErrorMessage();
+ log.error("Cannot initialize components.", e);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ String lookAndFeelClassName = UIManager.getSystemLookAndFeelClassName();
+ if (!lookAndFeelClassName.contains("AquaLookAndFeel") && !lookAndFeelClassName.contains("PlasticXPLookAndFeel")) {
+ // may be running on linux platform
+ lookAndFeelClassName = "javax.swing.plaf.metal.MetalLookAndFeel";
+ }
+ UIManager.setLookAndFeel(lookAndFeelClassName);
+
+ GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
+ genv.registerFont(FontUtils.createElegantIconFont());
+
+ javax.swing.SwingUtilities.invokeLater(LukeMain::createAndShowGUI);
+
+ }
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/MessageBroker.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/MessageBroker.java
new file mode 100644
index 0000000..9609a2f
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/MessageBroker.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Message broker */
+public class MessageBroker {
+
+ private static final MessageBroker instance = new MessageBroker();
+
+ private List<MessageReceiver> receivers = new ArrayList<>();
+
+ public static MessageBroker getInstance() {
+ return instance;
+ }
+
+ public void registerReceiver(MessageReceiver receiver) {
+ receivers.add(receiver);
+ }
+
+ public void showStatusMessage(String message) {
+ for (MessageReceiver receiver : receivers) {
+ receiver.showStatusMessage(message);
+ }
+ }
+
+ public void showUnknownErrorMessage() {
+ for (MessageReceiver receiver : receivers) {
+ receiver.showUnknownErrorMessage();
+ }
+ }
+
+ public void clearStatusMessage() {
+ for (MessageReceiver receiver : receivers) {
+ receiver.clearStatusMessage();
+ }
+ }
+
+ /** Message receiver in charge of rendering the message. */
+ public interface MessageReceiver {
+ void showStatusMessage(String message);
+
+ void showUnknownErrorMessage();
+
+ void clearStatusMessage();
+ }
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/Preferences.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/Preferences.java
new file mode 100644
index 0000000..b0df660
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/Preferences.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop;
+
+import java.awt.Color;
+import java.io.IOException;
+import java.util.List;
+
+/** Preference */
+public interface Preferences {
+
+ List<String> getHistory();
+
+ void addHistory(String indexPath) throws IOException;
+
+ boolean isReadOnly();
+
+ String getDirImpl();
+
+ boolean isNoReader();
+
+ boolean isUseCompound();
+
+ boolean isKeepAllCommits();
+
+ void setIndexOpenerPrefs(boolean readOnly, String dirImpl, boolean noReader, boolean useCompound, boolean keepAllCommits) throws IOException;
+
+ ColorTheme getColorTheme();
+
+ void setColorTheme(ColorTheme theme) throws IOException;
+
+ /** color themes */
+ enum ColorTheme {
+
+ /* Gray theme */
+ GRAY(Color.decode("#e6e6e6")),
+ /* Classic theme */
+ CLASSIC(Color.decode("#ece9d0")),
+ /* Sandstone theme */
+ SANDSTONE(Color.decode("#ddd9d4")),
+ /* Navy theme */
+ NAVY(Color.decode("#e6e6ff"));
+
+ private Color backgroundColor;
+
+ ColorTheme(Color backgroundColor) {
+ this.backgroundColor = backgroundColor;
+ }
+
+ public Color getBackgroundColor() {
+ return backgroundColor;
+ }
+ }
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/PreferencesFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/PreferencesFactory.java
new file mode 100644
index 0000000..2502553
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/PreferencesFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop;
+
+import java.io.IOException;
+
+/** Factory of {@link Preferences} */
+public class PreferencesFactory {
+
+ private static Preferences prefs;
+
+ public synchronized static Preferences getInstance() throws IOException {
+ if (prefs == null) {
+ prefs = new PreferencesImpl();
+ }
+ return prefs;
+ }
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/PreferencesImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/PreferencesImpl.java
new file mode 100644
index 0000000..ebf78c5
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/PreferencesImpl.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop;
+
+import java.io.IOException;
+import java.nio.file.FileSystems;
+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;
+
+/** 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 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 {
+ // create config dir if not exists
+ Path confDir = FileSystems.getDefault().getPath(CONFIG_DIR);
+ if (!Files.exists(confDir)) {
+ Files.createDirectory(confDir);
+ }
+
+ // load configs
+ if (Files.exists(iniFile())) {
+ ini.load(iniFile());
+ } else {
+ ini.store(iniFile());
+ }
+
+ // load history
+ Path histFile = historyFile();
+ if (Files.exists(histFile)) {
+ List<String> allHistory = Files.readAllLines(histFile);
+ history.addAll(allHistory.subList(0, Math.min(MAX_HISTORY, allHistory.size())));
+ }
+
+ }
+
+ public List<String> getHistory() {
+ return history;
+ }
+
+ @Override
+ public void addHistory(String indexPath) throws IOException {
+ if (history.indexOf(indexPath) >= 0) {
+ history.remove(indexPath);
+ }
+ history.add(0, indexPath);
+ saveHistory();
+ }
+
+ private void saveHistory() throws IOException {
+ Files.write(historyFile(), history);
+ }
+
+ private Path historyFile() {
+ return FileSystems.getDefault().getPath(CONFIG_DIR, HISTORY_FILE);
+ }
+
+ @Override
+ public ColorTheme getColorTheme() {
+ String theme = ini.getString("settings", "theme");
+ return (theme == null) ? ColorTheme.GRAY : ColorTheme.valueOf(theme);
+ }
+
+ @Override
+ public void setColorTheme(ColorTheme theme) throws IOException {
+ ini.put("settings", "theme", theme.name());
+ ini.store(iniFile());
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ Boolean readOnly = ini.getBoolean("opener", "readOnly");
+ return (readOnly == null) ? false : readOnly;
+ }
+
+ @Override
+ public String getDirImpl() {
+ String dirImpl = ini.getString("opener", "dirImpl");
+ return (dirImpl == null) ? FSDirectory.class.getName() : dirImpl;
+ }
+
+ @Override
+ public boolean isNoReader() {
+ Boolean noReader = ini.getBoolean("opener", "noReader");
+ return (noReader == null) ? false : noReader;
+ }
+
+ @Override
+ public boolean isUseCompound() {
+ Boolean useCompound = ini.getBoolean("opener", "useCompound");
+ return (useCompound == null) ? false : useCompound;
+ }
+
+ @Override
+ public boolean isKeepAllCommits() {
+ Boolean keepAllCommits = ini.getBoolean("opener", "keepAllCommits");
+ return (keepAllCommits == null) ? false : keepAllCommits;
+ }
+
+ @Override
+ 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);
+ ini.put("opener", "useCompound", useCompound);
+ ini.put("opener", "keepAllCommits", keepAllCommits);
+ ini.store(iniFile());
+ }
+
+ private Path iniFile() {
+ return FileSystems.getDefault().getPath(CONFIG_DIR, INIT_FILE);
+ }
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/AnalysisPanelProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/AnalysisPanelProvider.java
new file mode 100644
index 0000000..70c2291
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/AnalysisPanelProvider.java
@@ -0,0 +1,441 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+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.GridLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.custom.CustomAnalyzer;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.luke.app.desktop.MessageBroker;
+import org.apache.lucene.luke.app.desktop.components.dialog.analysis.AnalysisChainDialogFactory;
+import org.apache.lucene.luke.app.desktop.components.dialog.analysis.TokenAttributeDialogFactory;
+import org.apache.lucene.luke.app.desktop.components.dialog.documents.AddDocumentDialogOperator;
+import org.apache.lucene.luke.app.desktop.components.fragments.analysis.CustomAnalyzerPanelOperator;
+import org.apache.lucene.luke.app.desktop.components.fragments.analysis.CustomAnalyzerPanelProvider;
+import org.apache.lucene.luke.app.desktop.components.fragments.analysis.PresetAnalyzerPanelOperator;
+import org.apache.lucene.luke.app.desktop.components.fragments.analysis.PresetAnalyzerPanelProvider;
+import org.apache.lucene.luke.app.desktop.components.fragments.search.AnalyzerTabOperator;
+import org.apache.lucene.luke.app.desktop.components.fragments.search.MLTTabOperator;
+import org.apache.lucene.luke.app.desktop.util.DialogOpener;
+import org.apache.lucene.luke.app.desktop.util.FontUtils;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+import org.apache.lucene.luke.app.desktop.util.StyleConstants;
+import org.apache.lucene.luke.app.desktop.util.TableUtils;
+import org.apache.lucene.luke.models.analysis.Analysis;
+import org.apache.lucene.luke.models.analysis.AnalysisFactory;
+import org.apache.lucene.luke.models.analysis.CustomAnalyzerConfig;
+import org.apache.lucene.util.NamedThreadFactory;
+
+/** Provider of the Analysis panel */
+public final class AnalysisPanelProvider implements AnalysisTabOperator {
+
+ private static final String TYPE_PRESET = "preset";
+
+ private static final String TYPE_CUSTOM = "custom";
+
+ private final ComponentOperatorRegistry operatorRegistry;
+
+ private final AnalysisChainDialogFactory analysisChainDialogFactory;
+
+ private final TokenAttributeDialogFactory tokenAttrDialogFactory;
+
+ private final MessageBroker messageBroker;
+
+ private final JPanel mainPanel = new JPanel();
+
+ private final JPanel preset;
+
+ private final JPanel custom;
+
+ private final JRadioButton presetRB = new JRadioButton();
+
+ private final JRadioButton customRB = new JRadioButton();
+
+ private final JLabel analyzerNameLbl = new JLabel();
+
+ private final JLabel showChainLbl = new JLabel();
+
+ private final JTextArea inputArea = new JTextArea();
+
+ private final JTable tokensTable = new JTable();
+
+ private final ListenerFunctions listeners = new ListenerFunctions();
+
+ private List<Analysis.Token> tokens;
+
+ private Analysis analysisModel;
+
+ public AnalysisPanelProvider() throws IOException {
+ this.preset = new PresetAnalyzerPanelProvider().get();
+ this.custom = new CustomAnalyzerPanelProvider().get();
+
+ this.operatorRegistry = ComponentOperatorRegistry.getInstance();
+ this.analysisChainDialogFactory = AnalysisChainDialogFactory.getInstance();
+ this.tokenAttrDialogFactory = TokenAttributeDialogFactory.getInstance();
+ this.messageBroker = MessageBroker.getInstance();
+
+ this.analysisModel = new AnalysisFactory().newInstance();
+ analysisModel.createAnalyzerFromClassName(StandardAnalyzer.class.getName());
+
+ 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();
+ });
+ }
+
+ public JPanel get() {
+ JPanel panel = new JPanel(new GridLayout(1, 1));
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createLineBorder(Color.gray));
+
+ JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel());
+ splitPane.setOpaque(false);
+ splitPane.setDividerLocation(320);
+ panel.add(splitPane);
+
+ return panel;
+ }
+
+ private JPanel initUpperPanel() {
+ mainPanel.setOpaque(false);
+ mainPanel.setLayout(new BorderLayout());
+ mainPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+
+ mainPanel.add(initSwitcherPanel(), BorderLayout.PAGE_START);
+ mainPanel.add(preset, BorderLayout.CENTER);
+
+ return mainPanel;
+ }
+
+ private JPanel initSwitcherPanel() {
+ JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ panel.setOpaque(false);
+
+ presetRB.setText(MessageUtils.getLocalizedMessage("analysis.radio.preset"));
+ presetRB.setActionCommand(TYPE_PRESET);
+ presetRB.addActionListener(listeners::toggleMainPanel);
+ presetRB.setOpaque(false);
+ presetRB.setSelected(true);
+
+ customRB.setText(MessageUtils.getLocalizedMessage("analysis.radio.custom"));
+ customRB.setActionCommand(TYPE_CUSTOM);
+ customRB.addActionListener(listeners::toggleMainPanel);
+ customRB.setOpaque(false);
+ customRB.setSelected(false);
+
+ ButtonGroup group = new ButtonGroup();
+ group.add(presetRB);
+ group.add(customRB);
+
+ panel.add(presetRB);
+ panel.add(customRB);
+
+ return panel;
+ }
+
+ private JPanel initLowerPanel() {
+ JPanel inner1 = new JPanel(new BorderLayout());
+ inner1.setOpaque(false);
+
+ JPanel analyzerName = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 2));
+ analyzerName.setOpaque(false);
+ 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.setVisible(analysisModel.currentAnalyzer() instanceof CustomAnalyzer);
+ analyzerName.add(FontUtils.toLinkText(showChainLbl));
+ inner1.add(analyzerName, BorderLayout.PAGE_START);
+
+ JPanel input = new JPanel(new FlowLayout(FlowLayout.LEADING, 5, 2));
+ input.setOpaque(false);
+ inputArea.setRows(3);
+ inputArea.setColumns(50);
+ inputArea.setLineWrap(true);
+ inputArea.setWrapStyleWord(true);
+ inputArea.setText(MessageUtils.getLocalizedMessage("analysis.textarea.prompt"));
+ input.add(new JScrollPane(inputArea));
+
+ 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);
+ input.add(executeBtn);
+
+ 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("");
+ TableUtils.setupTable(tokensTable, ListSelectionModel.SINGLE_SELECTION, new TokensTableModel(),
+ null,
+ TokensTableModel.Column.TERM.getColumnWidth(),
+ TokensTableModel.Column.ATTR.getColumnWidth());
+ });
+ input.add(clearBtn);
+
+ inner1.add(input, BorderLayout.CENTER);
+
+ JPanel inner2 = new JPanel(new BorderLayout());
+ inner2.setOpaque(false);
+
+ JPanel hint = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ hint.setOpaque(false);
+ hint.add(new JLabel(MessageUtils.getLocalizedMessage("analysis.hint.show_attributes")));
+ inner2.add(hint, BorderLayout.PAGE_START);
+
+
+ TableUtils.setupTable(tokensTable, ListSelectionModel.SINGLE_SELECTION, new TokensTableModel(),
+ new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ listeners.showAttributeValues(e);
+ }
+ },
+ TokensTableModel.Column.TERM.getColumnWidth(),
+ TokensTableModel.Column.ATTR.getColumnWidth());
+ inner2.add(new JScrollPane(tokensTable), BorderLayout.CENTER);
+
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+ panel.add(inner1, BorderLayout.PAGE_START);
+ panel.add(inner2, BorderLayout.CENTER);
+
+ return panel;
+ }
+
+ // control methods
+
+ void toggleMainPanel(String command) {
+ if (command.equalsIgnoreCase(TYPE_PRESET)) {
+ mainPanel.remove(custom);
+ mainPanel.add(preset, BorderLayout.CENTER);
+
+ operatorRegistry.get(PresetAnalyzerPanelOperator.class).ifPresent(operator -> {
+ operator.setPresetAnalyzers(analysisModel.getPresetAnalyzerTypes());
+ operator.setSelectedAnalyzer(analysisModel.currentAnalyzer().getClass());
+ });
+
+ } 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();
+ });
+ }
+ mainPanel.setVisible(false);
+ mainPanel.setVisible(true);
+ }
+
+ void executeAnalysis() {
+ String text = inputArea.getText();
+ if (Objects.isNull(text) || text.isEmpty()) {
+ messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("analysis.message.empry_input"));
+ }
+
+ tokens = analysisModel.analyze(text);
+ tokensTable.setModel(new TokensTableModel(tokens));
+ tokensTable.setShowGrid(true);
+ tokensTable.getColumnModel().getColumn(TokensTableModel.Column.TERM.getIndex()).setPreferredWidth(TokensTableModel.Column.TERM.getColumnWidth());
+ tokensTable.getColumnModel().getColumn(TokensTableModel.Column.ATTR.getIndex()).setPreferredWidth(TokensTableModel.Column.ATTR.getColumnWidth());
+ }
+
+ void showAnalysisChainDialog() {
+ if (getCurrentAnalyzer() instanceof CustomAnalyzer) {
+ CustomAnalyzer analyzer = (CustomAnalyzer) getCurrentAnalyzer();
+ new DialogOpener<>(analysisChainDialogFactory).open("Analysis chain", 600, 320,
+ (factory) -> {
+ factory.setAnalyzer(analyzer);
+ });
+ }
+ }
+
+ void showAttributeValues(int selectedIndex) {
+ String term = tokens.get(selectedIndex).getTerm();
+ List<Analysis.TokenAttribute> attributes = tokens.get(selectedIndex).getAttributes();
+ new DialogOpener<>(tokenAttrDialogFactory).open("Token Attributes", 650, 400,
+ factory -> {
+ factory.setTerm(term);
+ factory.setAttributes(attributes);
+ });
+ }
+
+
+ @Override
+ public void setAnalyzerByType(String analyzerType) {
+ 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()));
+ }
+
+ @Override
+ public void setAnalyzerByCustomConfiguration(CustomAnalyzerConfig config) {
+ 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()));
+ }
+
+ @Override
+ public Analyzer getCurrentAnalyzer() {
+ return analysisModel.currentAnalyzer();
+ }
+
+ private class ListenerFunctions {
+
+ void toggleMainPanel(ActionEvent e) {
+ AnalysisPanelProvider.this.toggleMainPanel(e.getActionCommand());
+ }
+
+ void showAnalysisChain(MouseEvent e) {
+ AnalysisPanelProvider.this.showAnalysisChainDialog();
+ }
+
+ void executeAnalysis(ActionEvent e) {
+ AnalysisPanelProvider.this.executeAnalysis();
+ }
+
+ void showAttributeValues(MouseEvent e) {
+ if (e.getClickCount() != 2 || e.isConsumed()) {
+ return;
+ }
+ int selectedIndex = tokensTable.rowAtPoint(e.getPoint());
+ if (selectedIndex < 0 || selectedIndex >= tokensTable.getRowCount()) {
+ return;
+ }
+ AnalysisPanelProvider.this.showAttributeValues(selectedIndex);
+ }
+
+ }
+
+ static final class TokensTableModel extends TableModelBase<TokensTableModel.Column> {
+
+ enum Column implements TableColumnInfo {
+ TERM("Term", 0, String.class, 150),
+ ATTR("Attributes", 1, String.class, 1000);
+
+ private final String colName;
+ private final int index;
+ private final Class<?> type;
+ private final int width;
+
+ Column(String colName, int index, Class<?> type, int width) {
+ this.colName = colName;
+ this.index = index;
+ this.type = type;
+ this.width = width;
+ }
+
+ @Override
+ public String getColName() {
+ return colName;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public Class<?> getType() {
+ return type;
+ }
+
+ @Override
+ public int getColumnWidth() {
+ return width;
+ }
+ }
+
+ TokensTableModel() {
+ super();
+ }
+
+ TokensTableModel(List<Analysis.Token> tokens) {
+ super(tokens.size());
+ for (int i = 0; i < tokens.size(); i++) {
+ Analysis.Token token = tokens.get(i);
+ data[i][Column.TERM.getIndex()] = token.getTerm();
+ List<String> attValues = token.getAttributes().stream()
+ .flatMap(att -> att.getAttValues().entrySet().stream()
+ .map(e -> e.getKey() + "=" + e.getValue()))
+ .collect(Collectors.toList());
+ data[i][Column.ATTR.getIndex()] = String.join(",", attValues);
+ }
+ }
+
+ @Override
+ protected Column[] columnInfos() {
+ return Column.values();
+ }
+ }
+
+}
+
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/AnalysisTabOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/AnalysisTabOperator.java
new file mode 100644
index 0000000..555f1c0
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/AnalysisTabOperator.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.luke.models.analysis.CustomAnalyzerConfig;
+
+/** Operator for the Analysis tab */
+public interface AnalysisTabOperator extends ComponentOperatorRegistry.ComponentOperator {
+
+ void setAnalyzerByType(String analyzerType);
+
+ void setAnalyzerByCustomConfiguration(CustomAnalyzerConfig config);
+
+ Analyzer getCurrentAnalyzer();
+
+}
+
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/CommitsPanelProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/CommitsPanelProvider.java
new file mode 100644
index 0000000..d06abcc
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/CommitsPanelProvider.java
@@ -0,0 +1,575 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.DefaultListModel;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+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;
+import org.apache.lucene.luke.app.IndexHandler;
+import org.apache.lucene.luke.app.IndexObserver;
+import org.apache.lucene.luke.app.LukeState;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+import org.apache.lucene.luke.app.desktop.util.TableUtils;
+import org.apache.lucene.luke.models.commits.Commit;
+import org.apache.lucene.luke.models.commits.Commits;
+import org.apache.lucene.luke.models.commits.CommitsFactory;
+import org.apache.lucene.luke.models.commits.File;
+import org.apache.lucene.luke.models.commits.Segment;
+
+/** Provider of the Commits panel */
+public final class CommitsPanelProvider {
+
+ private final CommitsFactory commitsFactory = new CommitsFactory();
+
+ private final JComboBox<Long> commitGenCombo = new JComboBox<>();
+
+ private final JLabel deletedLbl = new JLabel();
+
+ private final JLabel segCntLbl = new JLabel();
+
+ private final JTextArea userDataTA = new JTextArea();
+
+ private final JTable filesTable = new JTable();
+
+ private final JTable segmentsTable = new JTable();
+
+ private final JRadioButton diagRB = new JRadioButton();
+
+ private final JRadioButton attrRB = new JRadioButton();
+
+ private final JRadioButton codecRB = new JRadioButton();
+
+ private final ButtonGroup rbGroup = new ButtonGroup();
+
+ private final JList<String> segDetailList = new JList<>();
+
+ private ListenerFunctions listeners = new ListenerFunctions();
+
+ private Commits commitsModel;
+
+ public CommitsPanelProvider() {
+ IndexHandler.getInstance().addObserver(new Observer());
+ DirectoryHandler.getInstance().addObserver(new Observer());
+ }
+
+ public JPanel get() {
+ JPanel panel = new JPanel(new GridLayout(1, 1));
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createLineBorder(Color.gray));
+
+ JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel());
+ splitPane.setOpaque(false);
+ splitPane.setBorder(BorderFactory.createEmptyBorder());
+ splitPane.setDividerLocation(120);
+ panel.add(splitPane);
+
+ return panel;
+ }
+
+ private JPanel initUpperPanel() {
+ JPanel panel = new JPanel(new BorderLayout(20, 0));
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+
+ JPanel left = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ left.setOpaque(false);
+ left.add(new JLabel(MessageUtils.getLocalizedMessage("commits.label.select_gen")));
+ commitGenCombo.addActionListener(listeners::selectGeneration);
+ left.add(commitGenCombo);
+ panel.add(left, BorderLayout.LINE_START);
+
+ JPanel right = new JPanel(new GridBagLayout());
+ right.setOpaque(false);
+ GridBagConstraints c1 = new GridBagConstraints();
+ c1.ipadx = 5;
+ c1.ipady = 5;
+
+ c1.gridx = 0;
+ c1.gridy = 0;
+ c1.weightx = 0.2;
+ c1.anchor = GridBagConstraints.EAST;
+ right.add(new JLabel(MessageUtils.getLocalizedMessage("commits.label.deleted")), c1);
+
+ c1.gridx = 1;
+ c1.gridy = 0;
+ c1.weightx = 0.5;
+ c1.anchor = GridBagConstraints.WEST;
+ right.add(deletedLbl, c1);
+
+ c1.gridx = 0;
+ c1.gridy = 1;
+ c1.weightx = 0.2;
+ c1.anchor = GridBagConstraints.EAST;
+ right.add(new JLabel(MessageUtils.getLocalizedMessage("commits.label.segcount")), c1);
+
+ c1.gridx = 1;
+ c1.gridy = 1;
+ c1.weightx = 0.5;
+ c1.anchor = GridBagConstraints.WEST;
+ right.add(segCntLbl, c1);
+
+ c1.gridx = 0;
+ c1.gridy = 2;
+ c1.weightx = 0.2;
+ c1.anchor = GridBagConstraints.EAST;
+ right.add(new JLabel(MessageUtils.getLocalizedMessage("commits.label.userdata")), c1);
+
+ userDataTA.setRows(3);
+ userDataTA.setColumns(30);
+ userDataTA.setLineWrap(true);
+ userDataTA.setWrapStyleWord(true);
+ userDataTA.setEditable(false);
+ JScrollPane userDataScroll = new JScrollPane(userDataTA, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ c1.gridx = 1;
+ c1.gridy = 2;
+ c1.weightx = 0.5;
+ c1.anchor = GridBagConstraints.WEST;
+ right.add(userDataScroll, c1);
+
+ panel.add(right, BorderLayout.CENTER);
+
+ return panel;
+ }
+
+ private JPanel initLowerPanel() {
+ JPanel panel = new JPanel(new GridLayout(1, 1));
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+
+ JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, initFilesPanel(), initSegmentsPanel());
+ splitPane.setOpaque(false);
+ splitPane.setBorder(BorderFactory.createEmptyBorder());
+ splitPane.setDividerLocation(300);
+ panel.add(splitPane);
+ return panel;
+ }
+
+ private JPanel initFilesPanel() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+
+ JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ header.setOpaque(false);
+ 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());
+ panel.add(new JScrollPane(filesTable), BorderLayout.CENTER);
+
+ return panel;
+ }
+
+ private JPanel initSegmentsPanel() {
+ JPanel panel = new JPanel();
+ panel.setOpaque(false);
+ panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
+
+ JPanel segments = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ segments.setOpaque(false);
+ segments.add(new JLabel(MessageUtils.getLocalizedMessage("commits.label.segments")));
+ panel.add(segments);
+
+ TableUtils.setupTable(segmentsTable, ListSelectionModel.SINGLE_SELECTION, new SegmentsTableModel(),
+ new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ listeners.showSegmentDetails(e);
+ }
+ },
+ SegmentsTableModel.Column.NAME.getColumnWidth(),
+ SegmentsTableModel.Column.MAXDOCS.getColumnWidth(),
+ SegmentsTableModel.Column.DELS.getColumnWidth(),
+ SegmentsTableModel.Column.DELGEN.getColumnWidth(),
+ SegmentsTableModel.Column.VERSION.getColumnWidth(),
+ SegmentsTableModel.Column.CODEC.getColumnWidth());
+ panel.add(new JScrollPane(segmentsTable));
+
+ JPanel segDetails = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ segDetails.setOpaque(false);
+ segDetails.add(new JLabel(MessageUtils.getLocalizedMessage("commits.label.segdetails")));
+ panel.add(segDetails);
+
+ JPanel buttons = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ buttons.setOpaque(false);
+
+ diagRB.setText("Diagnostics");
+ diagRB.setActionCommand(ActionCommand.DIAGNOSTICS.name());
+ diagRB.setSelected(true);
+ diagRB.setEnabled(false);
+ diagRB.setOpaque(false);
+ diagRB.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ listeners.showSegmentDetails(e);
+ }
+ });
+ buttons.add(diagRB);
+
+ attrRB.setText("Attributes");
+ attrRB.setActionCommand(ActionCommand.ATTRIBUTES.name());
+ attrRB.setSelected(false);
+ attrRB.setEnabled(false);
+ attrRB.setOpaque(false);
+ attrRB.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ listeners.showSegmentDetails(e);
+ }
+ });
+ buttons.add(attrRB);
+
+ codecRB.setText("Codec");
+ codecRB.setActionCommand(ActionCommand.CODEC.name());
+ codecRB.setSelected(false);
+ codecRB.setEnabled(false);
+ codecRB.setOpaque(false);
+ codecRB.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ listeners.showSegmentDetails(e);
+ }
+ });
+ buttons.add(codecRB);
+
+ rbGroup.add(diagRB);
+ rbGroup.add(attrRB);
+ rbGroup.add(codecRB);
+
+ panel.add(buttons);
+
+ segDetailList.setVisibleRowCount(10);
+ panel.add(new JScrollPane(segDetailList));
+
+ return panel;
+ }
+
+ // control methods
+
+ private void selectGeneration() {
+ diagRB.setEnabled(false);
+ attrRB.setEnabled(false);
+ codecRB.setEnabled(false);
+ 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());
+ });
+
+ filesTable.setModel(new FilesTableModel(commitsModel.getFiles(commitGen)));
+ filesTable.setShowGrid(true);
+ 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());
+ }
+
+ private void showSegmentDetails() {
+ int selectedRow = segmentsTable.getSelectedRow();
+ if (commitGenCombo.getSelectedItem() == null ||
+ selectedRow < 0 || selectedRow >= segmentsTable.getRowCount()) {
+ return;
+ }
+
+ diagRB.setEnabled(true);
+ attrRB.setEnabled(true);
+ codecRB.setEnabled(true);
+
+ long commitGen = (long) commitGenCombo.getSelectedItem();
+ String segName = (String) segmentsTable.getValueAt(selectedRow, SegmentsTableModel.Column.NAME.getIndex());
+ ActionCommand command = ActionCommand.valueOf(rbGroup.getSelection().getActionCommand());
+
+ final DefaultListModel<String> detailsModel = new DefaultListModel<>();
+ switch (command) {
+ case DIAGNOSTICS:
+ commitsModel.getSegmentDiagnostics(commitGen, segName).entrySet().stream()
+ .map(entry -> entry.getKey() + " = " + entry.getValue())
+ .forEach(detailsModel::addElement);
+ break;
+ case ATTRIBUTES:
+ commitsModel.getSegmentAttributes(commitGen, segName).entrySet().stream()
+ .map(entry -> entry.getKey() + " = " + entry.getValue())
+ .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);
+ });
+ break;
+ }
+ segDetailList.setModel(detailsModel);
+
+ }
+
+ private class ListenerFunctions {
+
+ void selectGeneration(ActionEvent e) {
+ CommitsPanelProvider.this.selectGeneration();
+ }
+
+ void showSegmentDetails(MouseEvent e) {
+ CommitsPanelProvider.this.showSegmentDetails();
+ }
+
+ }
+
+ private class Observer implements IndexObserver, DirectoryObserver {
+
+ @Override
+ public void openDirectory(LukeState state) {
+ commitsModel = commitsFactory.newInstance(state.getDirectory(), state.getIndexPath());
+ populateCommitGenerations();
+ }
+
+ @Override
+ public void closeDirectory() {
+ close();
+ }
+
+ @Override
+ public void openIndex(LukeState state) {
+ if (state.hasDirectoryReader()) {
+ DirectoryReader dr = (DirectoryReader) state.getIndexReader();
+ commitsModel = commitsFactory.newInstance(dr, state.getIndexPath());
+ populateCommitGenerations();
+ }
+ }
+
+ @Override
+ public void closeIndex() {
+ close();
+ }
+
+ private void populateCommitGenerations() {
+ DefaultComboBoxModel<Long> segGenList = new DefaultComboBoxModel<>();
+ for (Commit commit : commitsModel.listCommits()) {
+ segGenList.addElement(commit.getGeneration());
+ }
+ commitGenCombo.setModel(segGenList);
+
+ if (segGenList.getSize() > 0) {
+ commitGenCombo.setSelectedIndex(0);
+ }
+ }
+
+ private void close() {
+ commitsModel = null;
+
+ commitGenCombo.setModel(new DefaultComboBoxModel<>());
+ 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,
+ SegmentsTableModel.Column.NAME.getColumnWidth(),
+ SegmentsTableModel.Column.MAXDOCS.getColumnWidth(),
+ SegmentsTableModel.Column.DELS.getColumnWidth(),
+ SegmentsTableModel.Column.DELGEN.getColumnWidth(),
+ SegmentsTableModel.Column.VERSION.getColumnWidth(),
+ SegmentsTableModel.Column.CODEC.getColumnWidth());
+ diagRB.setEnabled(false);
+ attrRB.setEnabled(false);
+ codecRB.setEnabled(false);
+ segDetailList.setModel(new DefaultListModel<>());
+ }
+ }
+
+ enum ActionCommand {
+ 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);
+
+ private final String colName;
+ private final int index;
+ private final Class<?> type;
+ private final int width;
+
+ Column(String colName, int index, Class<?> type, int width) {
+ this.colName = colName;
+ this.index = index;
+ this.type = type;
+ this.width = width;
+ }
+
+ @Override
+ public String getColName() {
+ return colName;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public Class<?> getType() {
+ return type;
+ }
+
+ @Override
+ public int getColumnWidth() {
+ return width;
+ }
+ }
+
+ FilesTableModel() {
+ super();
+ }
+
+ FilesTableModel(List<File> files) {
+ super(files.size());
+ for (int i = 0; i < files.size(); i++) {
+ File file = files.get(i);
+ data[i][Column.FILENAME.getIndex()] = file.getFileName();
+ data[i][Column.SIZE.getIndex()] = file.getDisplaySize();
+ }
+ }
+
+ @Override
+ protected Column[] columnInfos() {
+ return Column.values();
+ }
+ }
+
+ 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),
+ DELGEN("Del gen", 3, Long.class, 60),
+ VERSION("Lucene ver.", 4, String.class, 60),
+ CODEC("Codec", 5, String.class, 100),
+ SIZE("Size", 6, String.class, 150);
+
+ private final String colName;
+ private final int index;
+ private final Class<?> type;
+ private final int width;
+
+ Column(String colName, int index, Class<?> type, int width) {
+ this.colName = colName;
+ this.index = index;
+ this.type = type;
+ this.width = width;
+ }
+
+ @Override
+ public String getColName() {
+ return colName;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public Class<?> getType() {
+ return type;
+ }
+
+ @Override
+ public int getColumnWidth() {
+ return width;
+ }
+ }
+
+ SegmentsTableModel() {
+ super();
+ }
+
+ SegmentsTableModel(List<Segment> segments) {
+ super(segments.size());
+ for (int i = 0; i < segments.size(); i++) {
+ Segment segment = segments.get(i);
+ data[i][Column.NAME.getIndex()] = segment.getName();
+ data[i][Column.MAXDOCS.getIndex()] = segment.getMaxDoc();
+ data[i][Column.DELS.getIndex()] = segment.getDelCount();
+ data[i][Column.DELGEN.getIndex()] = segment.getDelGen();
+ data[i][Column.VERSION.getIndex()] = segment.getLuceneVer();
+ data[i][Column.CODEC.getIndex()] = segment.getCodecName();
+ data[i][Column.SIZE.getIndex()] = segment.getDisplaySize();
+ }
+ }
+
+ @Override
+ protected Column[] columnInfos() {
+ return Column.values();
+ }
+ }
+}
+
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/ComponentOperatorRegistry.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/ComponentOperatorRegistry.java
new file mode 100644
index 0000000..0d9c99b
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/ComponentOperatorRegistry.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/** An utility class for interaction between components */
+public class ComponentOperatorRegistry {
+
+ private static final ComponentOperatorRegistry instance = new ComponentOperatorRegistry();
+
+ private final Map<Class<?>, Object> operators = new HashMap<>();
+
+ public static ComponentOperatorRegistry getInstance() {
+ return instance;
+ }
+
+ public <T extends ComponentOperator> void register(Class<T> type, T operator) {
+ if (!operators.containsKey(type)) {
+ operators.put(type, operator);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T extends ComponentOperator> Optional<T> get(Class<T> type) {
+ return Optional.ofNullable((T) operators.get(type));
+ }
+
+ /** marker interface for operators */
+ public interface ComponentOperator {
+ }
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/DocumentsPanelProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/DocumentsPanelProvider.java
new file mode 100644
index 0000000..e9daece
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/DocumentsPanelProvider.java
@@ -0,0 +1,1115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.lucene.index.DocValuesType;
+import org.apache.lucene.index.IndexOptions;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.luke.app.IndexHandler;
+import org.apache.lucene.luke.app.IndexObserver;
+import org.apache.lucene.luke.app.LukeState;
+import org.apache.lucene.luke.app.desktop.MessageBroker;
+import org.apache.lucene.luke.app.desktop.components.dialog.HelpDialogFactory;
+import org.apache.lucene.luke.app.desktop.components.dialog.documents.AddDocumentDialogFactory;
+import org.apache.lucene.luke.app.desktop.components.dialog.documents.DocValuesDialogFactory;
+import org.apache.lucene.luke.app.desktop.components.dialog.documents.StoredValueDialogFactory;
+import org.apache.lucene.luke.app.desktop.components.dialog.documents.TermVectorDialogFactory;
+import org.apache.lucene.luke.app.desktop.util.DialogOpener;
+import org.apache.lucene.luke.app.desktop.util.FontUtils;
+import org.apache.lucene.luke.app.desktop.util.HelpHeaderRenderer;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+import org.apache.lucene.luke.app.desktop.util.StyleConstants;
+import org.apache.lucene.luke.app.desktop.util.TableUtils;
+import org.apache.lucene.luke.models.documents.DocValues;
+import org.apache.lucene.luke.models.documents.DocumentField;
+import org.apache.lucene.luke.models.documents.Documents;
+import org.apache.lucene.luke.models.documents.DocumentsFactory;
+import org.apache.lucene.luke.models.documents.TermPosting;
+import org.apache.lucene.luke.models.documents.TermVectorEntry;
+import org.apache.lucene.luke.util.BytesRefUtils;
+
+/** Provider of the Documents panel */
+public final class DocumentsPanelProvider implements DocumentsTabOperator {
+
+ private final DocumentsFactory documentsFactory = new DocumentsFactory();
+
+ private final MessageBroker messageBroker;
+
+ private final ComponentOperatorRegistry operatorRegistry;
+
+ private final TabSwitcherProxy tabSwitcher;
+
+ private final AddDocumentDialogFactory addDocDialogFactory;
+
+ private final TermVectorDialogFactory tvDialogFactory;
+
+ private final DocValuesDialogFactory dvDialogFactory;
+
+ private final StoredValueDialogFactory valueDialogFactory;
+
+ private final TableCellRenderer tableHeaderRenderer;
+
+ private final JComboBox<String> fieldsCombo = new JComboBox<>();
+
+ private final JButton firstTermBtn = new JButton();
+
+ private final JTextField termTF = new JTextField();
+
+ private final JButton nextTermBtn = new JButton();
+
+ private final JTextField selectedTermTF = new JTextField();
+
+ private final JButton firstTermDocBtn = new JButton();
+
+ private final JTextField termDocIdxTF = new JTextField();
+
+ private final JButton nextTermDocBtn = new JButton();
+
+ private final JLabel termDocsNumLbl = new JLabel();
+
+ private final JTable posTable = new JTable();
+
+ private final JSpinner docNumSpnr = new JSpinner();
+
+ private final JLabel maxDocsLbl = new JLabel();
+
+ private final JButton mltBtn = new JButton();
+
+ private final JButton addDocBtn = new JButton();
+
+ private final JButton copyDocValuesBtn = new JButton();
+
+ private final JTable documentTable = new JTable();
+
+ private final JPopupMenu documentContextMenu = new JPopupMenu();
+
+ private final ListenerFunctions listeners = new ListenerFunctions();
+
+ private Documents documentsModel;
+
+ public DocumentsPanelProvider() throws IOException {
+ this.messageBroker = MessageBroker.getInstance();
+ this.operatorRegistry = ComponentOperatorRegistry.getInstance();
+ this.tabSwitcher = TabSwitcherProxy.getInstance();
+ this.addDocDialogFactory = AddDocumentDialogFactory.getInstance();
+ this.tvDialogFactory = TermVectorDialogFactory.getInstance();
+ this.dvDialogFactory = DocValuesDialogFactory.getInstance();
+ this.valueDialogFactory = StoredValueDialogFactory.getInstance();
+ HelpDialogFactory helpDialogFactory = HelpDialogFactory.getInstance();
+ 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)"
+ };
+ JList<String> list = new JList<>(values);
+ return new JScrollPane(list);
+ }
+
+ public JPanel get() {
+ JPanel panel = new JPanel(new GridLayout(1, 1));
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createLineBorder(Color.gray));
+
+ JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel());
+ splitPane.setOpaque(false);
+ splitPane.setDividerLocation(0.4);
+ panel.add(splitPane);
+
+ setUpDocumentContextMenu();
+
+ return panel;
+ }
+
+ private JPanel initUpperPanel() {
+ JPanel panel = new JPanel(new GridBagLayout());
+ panel.setOpaque(false);
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0.5;
+ c.anchor = GridBagConstraints.FIRST_LINE_START;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ panel.add(initBrowseTermsPanel(), c);
+
+ c.gridx = 1;
+ c.gridy = 0;
+ c.weightx = 0.5;
+ c.anchor = GridBagConstraints.FIRST_LINE_START;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ panel.add(initBrowseDocsByTermPanel(), c);
+
+ return panel;
+ }
+
+ private JPanel initBrowseTermsPanel() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+
+ JPanel top = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ top.setOpaque(false);
+ JLabel label = new JLabel(MessageUtils.getLocalizedMessage("documents.label.browse_terms"));
+ top.add(label);
+
+ panel.add(top, BorderLayout.PAGE_START);
+
+ JPanel center = new JPanel(new GridBagLayout());
+ center.setOpaque(false);
+ GridBagConstraints c = new GridBagConstraints();
+ c.fill = GridBagConstraints.BOTH;
+
+ fieldsCombo.addActionListener(listeners::showFirstTerm);
+ c.gridx = 0;
+ c.gridy = 0;
+ c.insets = new Insets(5, 5, 5, 5);
+ c.weightx = 0.0;
+ c.gridwidth = 2;
+ center.add(fieldsCombo, c);
+
+ firstTermBtn.setText(FontUtils.elegantIconHtml("8", MessageUtils.getLocalizedMessage("documents.button.first_term")));
+ firstTermBtn.setMaximumSize(new Dimension(80, 30));
+ firstTermBtn.addActionListener(listeners::showFirstTerm);
+ c.gridx = 0;
+ c.gridy = 1;
+ c.insets = new Insets(5, 5, 5, 5);
+ c.weightx = 0.2;
+ c.gridwidth = 1;
+ center.add(firstTermBtn, c);
+
+ termTF.setColumns(20);
+ termTF.setMinimumSize(new Dimension(50, 25));
+ termTF.setFont(StyleConstants.FONT_MONOSPACE_LARGE);
+ termTF.addActionListener(listeners::seekNextTerm);
+ c.gridx = 1;
+ c.gridy = 1;
+ c.insets = new Insets(5, 5, 5, 5);
+ c.weightx = 0.5;
+ c.gridwidth = 1;
+ center.add(termTF, c);
+
+ nextTermBtn.setText(MessageUtils.getLocalizedMessage("documents.button.next"));
+ nextTermBtn.addActionListener(listeners::showNextTerm);
+ c.gridx = 2;
+ c.gridy = 1;
+ c.insets = new Insets(5, 5, 5, 5);
+ c.weightx = 0.1;
+ c.gridwidth = 1;
+ center.add(nextTermBtn, c);
+
+ panel.add(center, BorderLayout.CENTER);
+
+ JPanel footer = new JPanel(new FlowLayout(FlowLayout.LEADING, 20, 5));
+ footer.setOpaque(false);
+ JLabel hintLbl = new JLabel(MessageUtils.getLocalizedMessage("documents.label.browse_terms_hint"));
+ footer.add(hintLbl);
+ panel.add(footer, BorderLayout.PAGE_END);
+
+ return panel;
+ }
+
+ private JPanel initBrowseDocsByTermPanel() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+
+ JPanel center = new JPanel(new GridBagLayout());
+ center.setOpaque(false);
+ GridBagConstraints c = new GridBagConstraints();
+ c.fill = GridBagConstraints.BOTH;
+
+ JLabel label = new JLabel(MessageUtils.getLocalizedMessage("documents.label.browse_doc_by_term"));
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0.0;
+ c.gridwidth = 2;
+ c.insets = new Insets(5, 5, 5, 5);
+ center.add(label, c);
+
+ selectedTermTF.setColumns(20);
+ selectedTermTF.setFont(StyleConstants.FONT_MONOSPACE_LARGE);
+ selectedTermTF.setEditable(false);
+ selectedTermTF.setBackground(Color.white);
+ c.gridx = 0;
+ c.gridy = 1;
+ c.weightx = 0.0;
+ c.gridwidth = 2;
+ c.insets = new Insets(5, 5, 5, 5);
+ center.add(selectedTermTF, c);
+
+ firstTermDocBtn.setText(FontUtils.elegantIconHtml("8", MessageUtils.getLocalizedMessage("documents.button.first_termdoc")));
+ firstTermDocBtn.addActionListener(listeners::showFirstTermDoc);
+ c.gridx = 0;
+ c.gridy = 2;
+ c.weightx = 0.2;
+ c.gridwidth = 1;
+ c.insets = new Insets(5, 3, 5, 5);
+ center.add(firstTermDocBtn, c);
+
+ termDocIdxTF.setEditable(false);
+ termDocIdxTF.setBackground(Color.white);
+ c.gridx = 1;
+ c.gridy = 2;
+ c.weightx = 0.5;
+ c.gridwidth = 1;
+ c.insets = new Insets(5, 5, 5, 5);
+ center.add(termDocIdxTF, c);
+
+ nextTermDocBtn.setText(MessageUtils.getLocalizedMessage("documents.button.next"));
+ nextTermDocBtn.addActionListener(listeners::showNextTermDoc);
+ c.gridx = 2;
+ c.gridy = 2;
+ c.weightx = 0.2;
+ c.gridwidth = 1;
+ c.insets = new Insets(5, 5, 5, 5);
+ center.add(nextTermDocBtn, c);
+
+ termDocsNumLbl.setText("in ? docs");
+ c.gridx = 3;
+ c.gridy = 2;
+ c.weightx = 0.3;
+ c.gridwidth = 1;
+ 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());
+ JScrollPane scrollPane = new JScrollPane(posTable);
+ scrollPane.setMinimumSize(new Dimension(100, 100));
+ c.gridx = 0;
+ c.gridy = 3;
+ c.gridwidth = 4;
+ c.insets = new Insets(5, 5, 5, 5);
+ center.add(scrollPane, c);
+
+ panel.add(center, BorderLayout.CENTER);
+
+ return panel;
+ }
+
+ private JPanel initLowerPanel() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+
+ JPanel browseDocsPanel = new JPanel();
+ browseDocsPanel.setOpaque(false);
+ browseDocsPanel.setLayout(new BoxLayout(browseDocsPanel, BoxLayout.PAGE_AXIS));
+ browseDocsPanel.add(initBrowseDocsBar());
+
+ JPanel browseDocsNote1 = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ browseDocsNote1.setOpaque(false);
+ 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")));
+ browseDocsPanel.add(browseDocsNote2);
+
+ panel.add(browseDocsPanel, BorderLayout.PAGE_START);
+
+ TableUtils.setupTable(documentTable, ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, new DocumentsTableModel(), new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ listeners.showDocumentContextMenu(e);
+ }
+ },
+ DocumentsTableModel.Column.FIELD.getColumnWidth(),
+ DocumentsTableModel.Column.FLAGS.getColumnWidth(),
+ DocumentsTableModel.Column.NORM.getColumnWidth(),
+ DocumentsTableModel.Column.VALUE.getColumnWidth());
+ JPanel flagsHeader = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ flagsHeader.setOpaque(false);
+ flagsHeader.add(new JLabel("Flags"));
+ flagsHeader.add(new JLabel("Help"));
+ documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.FLAGS.getIndex()).setHeaderValue(flagsHeader);
+
+ JScrollPane scrollPane = new JScrollPane(documentTable);
+ scrollPane.getHorizontalScrollBar().setAutoscrolls(false);
+ panel.add(scrollPane, BorderLayout.CENTER);
+
+ return panel;
+ }
+
+ private JPanel initBrowseDocsBar() {
+ JPanel panel = new JPanel(new GridLayout(1, 2));
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 5));
+
+ 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")));
+ label.setHorizontalTextPosition(JLabel.LEFT);
+ left.add(label);
+ docNumSpnr.setPreferredSize(new Dimension(100, 25));
+ docNumSpnr.addChangeListener(listeners::showCurrentDoc);
+ left.add(docNumSpnr);
+ maxDocsLbl.setText("in ? docs");
+ left.add(maxDocsLbl);
+ panel.add(left);
+
+ JPanel right = new JPanel(new FlowLayout(FlowLayout.TRAILING));
+ right.setOpaque(false);
+ 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.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.setMargin(new Insets(5, 0, 5, 0));
+ addDocBtn.addActionListener(listeners::showAddDocumentDialog);
+ right.add(addDocBtn);
+ panel.add(right);
+
+ return panel;
+ }
+
+ private void setUpDocumentContextMenu() {
+ // show term vector
+ 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"));
+ item2.addActionListener(listeners::showDocValuesDialog);
+ documentContextMenu.add(item2);
+
+ // show stored value
+ 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"));
+ item4.addActionListener(listeners::copyStoredValue);
+ documentContextMenu.add(item4);
+ }
+
+ // control methods
+
+ private void showFirstTerm() {
+ String fieldName = (String) fieldsCombo.getSelectedItem();
+ if (fieldName == null || fieldName.length() == 0) {
+ messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.field.message.not_selected"));
+ return;
+ }
+
+ termDocIdxTF.setText("");
+ clearPosTable();
+
+ Optional<Term> firstTerm = documentsModel.firstTerm(fieldName);
+ String firstTermText = firstTerm.map(Term::text).orElse("");
+ termTF.setText(firstTermText);
+ selectedTermTF.setText(firstTermText);
+ if (firstTerm.isPresent()) {
+ String num = documentsModel.getDocFreq().map(String::valueOf).orElse("?");
+ termDocsNumLbl.setText("in " + num + " docs");
+
+ nextTermBtn.setEnabled(true);
+ termTF.setEditable(true);
+ firstTermDocBtn.setEnabled(true);
+ } else {
+ nextTermBtn.setEnabled(false);
+ termTF.setEditable(false);
+ firstTermDocBtn.setEnabled(false);
+ }
+ nextTermDocBtn.setEnabled(false);
+ messageBroker.clearStatusMessage();
+ }
+
+ private void showNextTerm() {
+ termDocIdxTF.setText("");
+ clearPosTable();
+
+ Optional<Term> nextTerm = documentsModel.nextTerm();
+ String nextTermText = nextTerm.map(Term::text).orElse("");
+ termTF.setText(nextTermText);
+ selectedTermTF.setText(nextTermText);
+ if (nextTerm.isPresent()) {
+ String num = documentsModel.getDocFreq().map(String::valueOf).orElse("?");
+ termDocsNumLbl.setText("in " + num + " docs");
+
+ termTF.setEditable(true);
+ firstTermDocBtn.setEnabled(true);
+ } else {
+ nextTermBtn.setEnabled(false);
+ termTF.setEditable(false);
+ firstTermDocBtn.setEnabled(false);
+ }
+ nextTermDocBtn.setEnabled(false);
+ messageBroker.clearStatusMessage();
+ }
+
+ @Override
+ public void seekNextTerm() {
+ termDocIdxTF.setText("");
+ posTable.setModel(new PosTableModel());
+
+ String termText = termTF.getText();
+
+ Optional<Term> nextTerm = documentsModel.seekTerm(termText);
+ String nextTermText = nextTerm.map(Term::text).orElse("");
+ termTF.setText(nextTermText);
+ selectedTermTF.setText(nextTermText);
+ if (nextTerm.isPresent()) {
+ String num = documentsModel.getDocFreq().map(String::valueOf).orElse("?");
+ termDocsNumLbl.setText("in " + num + " docs");
+
+ termTF.setEditable(true);
+ firstTermDocBtn.setEnabled(true);
+ } else {
+ nextTermBtn.setEnabled(false);
+ termTF.setEditable(false);
+ firstTermDocBtn.setEnabled(false);
+ }
+ nextTermDocBtn.setEnabled(false);
+ messageBroker.clearStatusMessage();
+ }
+
+
+ private void clearPosTable() {
+ TableUtils.setupTable(posTable, ListSelectionModel.SINGLE_SELECTION, new PosTableModel(), null,
+ PosTableModel.Column.POSITION.getColumnWidth(),
+ PosTableModel.Column.OFFSETS.getColumnWidth(),
+ PosTableModel.Column.PAYLOAD.getColumnWidth());
+ }
+
+ @Override
+ public void showFirstTermDoc() {
+ int docid = documentsModel.firstTermDoc().orElse(-1);
+ if (docid < 0) {
+ nextTermDocBtn.setEnabled(false);
+ messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.termdocs.message.not_available"));
+ return;
+ }
+ termDocIdxTF.setText(String.valueOf(1));
+ displayDoc(docid);
+
+ 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());
+
+ nextTermDocBtn.setEnabled(true);
+ messageBroker.clearStatusMessage();
+ }
+
+ private void showNextTermDoc() {
+ int docid = documentsModel.nextTermDoc().orElse(-1);
+ if (docid < 0) {
+ nextTermDocBtn.setEnabled(false);
+ messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.termdocs.message.not_available"));
+ return;
+ }
+ int curIdx = Integer.parseInt(termDocIdxTF.getText());
+ termDocIdxTF.setText(String.valueOf(curIdx + 1));
+ displayDoc(docid);
+
+ List<TermPosting> postings = documentsModel.getTermPositions();
+ posTable.setModel(new PosTableModel(postings));
+
+ nextTermDocBtn.setDefaultCapable(true);
+ messageBroker.clearStatusMessage();
+ }
+
+ private void showCurrentDoc() {
+ int docid = (Integer) docNumSpnr.getValue();
+ displayDoc(docid);
+ }
+
+ private void mltSearch() {
+ int docNum = (int) docNumSpnr.getValue();
+ 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) -> {
+ });
+ }
+
+ private void showTermVectorDialog() {
+ int docid = (Integer) docNumSpnr.getValue();
+ 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));
+ return;
+ }
+
+ 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());
+ Optional<DocValues> docValues = documentsModel.getDocValues(docid, field);
+ if (docValues.isPresent()) {
+ 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));
+ }
+ }
+
+ 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());
+ if (Objects.isNull(value)) {
+ 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);
+ });
+ 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());
+ if (Objects.isNull(value)) {
+ messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.stored.message.not_availabe", field, docid));
+ return;
+ }
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ StringSelection selection = new StringSelection(value);
+ clipboard.setContents(selection, null);
+ messageBroker.clearStatusMessage();
+ }
+
+ private void copySelectedOrAllStoredValues() {
+ StringSelection selection;
+ if (documentTable.getSelectedRowCount() == 0) {
+ selection = copyAllValues();
+ } else {
+ selection = copySelectedValues();
+ }
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ clipboard.setContents(selection, null);
+ messageBroker.clearStatusMessage();
+ }
+
+ 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());
+ if (Objects.nonNull(value)) {
+ sb.append((i == 0) ? value : System.lineSeparator() + value);
+ }
+ }
+ return new StringSelection(sb.toString());
+ }
+
+ private StringSelection copySelectedValues() {
+ StringBuilder sb = new StringBuilder();
+ boolean isFirst = true;
+ for (int rowIndex : documentTable.getSelectedRows()) {
+ String value = (String) documentTable.getModel().getValueAt(rowIndex, DocumentsTableModel.Column.VALUE.getIndex());
+ if (Objects.nonNull(value)) {
+ sb.append(isFirst ? value : System.lineSeparator() + value);
+ isFirst = false;
+ }
+ }
+ return new StringSelection(sb.toString());
+ }
+
+ @Override
+ public void browseTerm(String field, String term) {
+ fieldsCombo.setSelectedItem(field);
+ termTF.setText(term);
+ seekNextTerm();
+ showFirstTermDoc();
+ }
+
+ @Override
+ public void displayLatestDoc() {
+ int docid = documentsModel.getMaxDoc() - 1;
+ showDoc(docid);
+ }
+
+ @Override
+ public void displayDoc(int docid) {
+ showDoc(docid);
+ }
+
+ ;
+
+ private void showDoc(int docid) {
+ docNumSpnr.setValue(docid);
+
+ 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);
+
+ messageBroker.clearStatusMessage();
+ }
+
+ private class ListenerFunctions {
+
+ void showFirstTerm(ActionEvent e) {
+ DocumentsPanelProvider.this.showFirstTerm();
+ }
+
+ void seekNextTerm(ActionEvent e) {
+ DocumentsPanelProvider.this.seekNextTerm();
+ }
+
+ void showNextTerm(ActionEvent e) {
+ DocumentsPanelProvider.this.showNextTerm();
+ }
+
+ void showFirstTermDoc(ActionEvent e) {
+ DocumentsPanelProvider.this.showFirstTermDoc();
+ }
+
+ void showNextTermDoc(ActionEvent e) {
+ DocumentsPanelProvider.this.showNextTermDoc();
+ }
+
+ void showCurrentDoc(ChangeEvent e) {
+ DocumentsPanelProvider.this.showCurrentDoc();
+ }
+
+ void mltSearch(ActionEvent e) {
+ DocumentsPanelProvider.this.mltSearch();
+ }
+
+ void showAddDocumentDialog(ActionEvent e) {
+ DocumentsPanelProvider.this.showAddDocumentDialog();
+ }
+
+ void showDocumentContextMenu(MouseEvent e) {
+ if (e.getClickCount() == 2 && !e.isConsumed()) {
+ int row = documentTable.rowAtPoint(e.getPoint());
+ if (row != documentTable.getSelectedRow()) {
+ documentTable.changeSelection(row, documentTable.getSelectedColumn(), false, false);
+ }
+ documentContextMenu.show(e.getComponent(), e.getX(), e.getY());
+ }
+ }
+
+ void showTermVectorDialog(ActionEvent e) {
+ DocumentsPanelProvider.this.showTermVectorDialog();
+ }
+
+ void showDocValuesDialog(ActionEvent e) {
+ DocumentsPanelProvider.this.showDocValuesDialog();
+ }
+
+ void showStoredValueDialog(ActionEvent e) {
+ DocumentsPanelProvider.this.showStoredValueDialog();
+ }
+
+ void copyStoredValue(ActionEvent e) {
+ DocumentsPanelProvider.this.copyStoredValue();
+ }
+
+ void copySelectedOrAllStoredValues(ActionEvent e) {
+ DocumentsPanelProvider.this.copySelectedOrAllStoredValues();
+ }
+
+ }
+
+ private class Observer implements IndexObserver {
+
+ @Override
+ public void openIndex(LukeState state) {
+ documentsModel = documentsFactory.newInstance(state.getIndexReader());
+
+ addDocBtn.setEnabled(!state.readOnly() && state.hasDirectoryReader());
+
+ int maxDoc = documentsModel.getMaxDoc();
+ maxDocsLbl.setText("in " + maxDoc + " docs");
+ if (maxDoc > 0) {
+ int max = Math.max(maxDoc - 1, 0);
+ SpinnerModel spinnerModel = new SpinnerNumberModel(0, 0, max, 1);
+ docNumSpnr.setModel(spinnerModel);
+ docNumSpnr.setEnabled(true);
+ displayDoc(0);
+ } else {
+ docNumSpnr.setEnabled(false);
+ }
+
+ documentsModel.getFieldNames().stream().sorted().forEach(fieldsCombo::addItem);
+ }
+
+ @Override
+ public void closeIndex() {
+ maxDocsLbl.setText("in ? docs");
+ docNumSpnr.setEnabled(false);
+ fieldsCombo.removeAllItems();
+ termTF.setText("");
+ selectedTermTF.setText("");
+ termDocsNumLbl.setText("");
+ termDocIdxTF.setText("");
+
+ posTable.setModel(new PosTableModel());
+ documentTable.setModel(new DocumentsTableModel());
+ }
+ }
+
+ 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);
+
+ private final String colName;
+ private final int index;
+ private final Class<?> type;
+ private final int width;
+
+ Column(String colName, int index, Class<?> type, int width) {
+ this.colName = colName;
+ this.index = index;
+ this.type = type;
+ this.width = width;
+ }
+
+ @Override
+ public String getColName() {
+ return colName;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public Class<?> getType() {
+ return type;
+ }
+
+ @Override
+ public int getColumnWidth() {
+ return width;
+ }
+ }
+
+ PosTableModel() {
+ super();
+ }
+
+ PosTableModel(List<TermPosting> postings) {
+ super(postings.size());
+
+ for (int i = 0; i < postings.size(); i++) {
+ TermPosting p = postings.get(i);
+
+ int position = postings.get(i).getPosition();
+ String offset = null;
+ if (p.getStartOffset() >= 0 && p.getEndOffset() >= 0) {
+ offset = p.getStartOffset() + "-" + p.getEndOffset();
+ }
+ String payload = null;
+ if (p.getPayload() != null) {
+ payload = BytesRefUtils.decode(p.getPayload());
+ }
+
+ data[i] = new Object[]{position, offset, payload};
+ }
+ }
+
+ @Override
+ protected Column[] columnInfos() {
+ return Column.values();
+ }
+ }
+
+ static final class DocumentsTableModel extends TableModelBase<DocumentsTableModel.Column> {
+
+ enum Column implements TableColumnInfo {
+ FIELD("Field", 0, String.class, 150),
+ FLAGS("Flags", 1, String.class, 200),
+ NORM("Norm", 2, Long.class, 80),
+ VALUE("Value", 3, String.class, 500);
+
+ private final String colName;
+ private final int index;
+ private final Class<?> type;
+ private final int width;
+
+ Column(String colName, int index, Class<?> type, int width) {
+ this.colName = colName;
+ this.index = index;
+ this.type = type;
+ this.width = width;
+ }
+
+ @Override
+ public String getColName() {
+ return colName;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public Class<?> getType() {
+ return type;
+ }
+
+ @Override
+ public int getColumnWidth() {
+ return width;
+ }
+ }
+
+ DocumentsTableModel() {
+ super();
+ }
+
+ DocumentsTableModel(List<DocumentField> doc) {
+ super(doc.size());
+
+ for (int i = 0; i < doc.size(); i++) {
+ DocumentField docField = doc.get(i);
+ String field = docField.getName();
+ String flags = flags(docField);
+ long norm = docField.getNorm();
+ String value = null;
+ if (docField.getStringValue() != null) {
+ value = docField.getStringValue();
+ } else if (docField.getNumericValue() != null) {
+ value = String.valueOf(docField.getNumericValue());
+ } else if (docField.getBinaryValue() != null) {
+ value = String.valueOf(docField.getBinaryValue());
+ }
+ data[i] = new Object[]{field, flags, norm, value};
+ }
+ }
+
+ private static String flags(org.apache.lucene.luke.models.documents.DocumentField f) {
+ StringBuilder sb = new StringBuilder();
+ // index options
+ if (f.getIdxOptions() == null || f.getIdxOptions() == IndexOptions.NONE) {
+ sb.append("-----");
+ } else {
+ sb.append("I");
+ switch (f.getIdxOptions()) {
+ case DOCS:
+ sb.append("d---");
+ break;
+ case DOCS_AND_FREQS:
+ sb.append("df--");
+ break;
+ case DOCS_AND_FREQS_AND_POSITIONS:
+ sb.append("dfp-");
+ break;
+ case DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS:
+ sb.append("dfpo");
+ break;
+ default:
+ sb.append("----");
+ }
+ }
+ // has norm?
+ if (f.hasNorms()) {
+ sb.append("N");
+ } else {
+ sb.append("-");
+ }
+ // has payloads?
+ if (f.hasPayloads()) {
+ sb.append("P");
+ } else {
+ sb.append("-");
+ }
+ // stored?
+ if (f.isStored()) {
+ sb.append("S");
+ } else {
+ sb.append("-");
+ }
+ // binary?
+ if (f.getBinaryValue() != null) {
+ sb.append("B");
+ } else {
+ sb.append("-");
+ }
+ // numeric?
+ if (f.getNumericValue() == null) {
+ sb.append("----");
+ } else {
+ sb.append("#");
+ // try faking it
+ Number numeric = f.getNumericValue();
+ if (numeric instanceof Integer) {
+ sb.append("i32");
+ } else if (numeric instanceof Long) {
+ sb.append("i64");
+ } else if (numeric instanceof Float) {
+ sb.append("f32");
+ } else if (numeric instanceof Double) {
+ sb.append("f64");
+ } else if (numeric instanceof Short) {
+ sb.append("i16");
+ } else if (numeric instanceof Byte) {
+ sb.append("i08");
+ } else if (numeric instanceof BigDecimal) {
+ sb.append("b^d");
+ } else if (numeric instanceof BigInteger) {
+ sb.append("b^i");
+ } else {
+ sb.append("???");
+ }
+ }
+ // has term vector?
+ if (f.hasTermVectors()) {
+ sb.append("V");
+ } else {
+ sb.append("-");
+ }
+ // doc values
+ if (f.getDvType() == null || f.getDvType() == DocValuesType.NONE) {
+ sb.append("-------");
+ } else {
+ sb.append("D");
+ switch (f.getDvType()) {
+ case NUMERIC:
+ sb.append("number");
+ break;
+ case BINARY:
+ sb.append("binary");
+ break;
+ case SORTED:
+ sb.append("sorted");
+ break;
+ case SORTED_NUMERIC:
+ sb.append("srtnum");
+ break;
+ case SORTED_SET:
+ sb.append("srtset");
+ break;
+ default:
+ sb.append("??????");
+ }
+ }
+ // point values
+ if (f.getPointDimensionCount() == 0) {
+ sb.append("----");
+ } else {
+ sb.append("T");
+ sb.append(f.getPointNumBytes());
+ sb.append("/");
+ sb.append(f.getPointDimensionCount());
+ }
+ return sb.toString();
+ }
+
+ @Override
+ protected Column[] columnInfos() {
+ return Column.values();
+ }
+ }
+
+}
+
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/DocumentsTabOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/DocumentsTabOperator.java
new file mode 100644
index 0000000..a0618da
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/DocumentsTabOperator.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components;
+
+/** Operator for the Documents tab */
+public interface DocumentsTabOperator extends ComponentOperatorRegistry.ComponentOperator {
+ void browseTerm(String field, String term);
+
+ void displayLatestDoc();
+
+ void displayDoc(int donid);
+
+ void seekNextTerm();
+
+ void showFirstTermDoc();
+}
\ No newline at end of file
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LogsPanelProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LogsPanelProvider.java
new file mode 100644
index 0000000..1d27cea
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LogsPanelProvider.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components;
+
+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;
+
+/** Provider of the Logs panel */
+public final class LogsPanelProvider {
+
+ private final JTextArea logTextArea;
+
+ public LogsPanelProvider(JTextArea logTextArea) {
+ this.logTextArea = logTextArea;
+ }
+
+ public JPanel get() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+
+ JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ header.setOpaque(false);
+ header.add(new JLabel(MessageUtils.getLocalizedMessage("logs.label.see_also")));
+
+ JLabel logPathLabel = new JLabel(LukeMain.LOG_FILE);
+ header.add(logPathLabel);
+
+ panel.add(header, BorderLayout.PAGE_START);
+
+ panel.add(new JScrollPane(logTextArea), BorderLayout.CENTER);
+ return panel;
+ }
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowOperator.java
new file mode 100644
index 0000000..ecc51c8
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowOperator.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components;
+
+import org.apache.lucene.luke.app.desktop.Preferences;
+
+/** Operator for the root window */
+public interface LukeWindowOperator extends ComponentOperatorRegistry.ComponentOperator {
+ void setColorTheme(Preferences.ColorTheme theme);
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowProvider.java
new file mode 100644
index 0000000..faf5c1c
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowProvider.java
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+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;
+import org.apache.lucene.luke.app.IndexObserver;
+import org.apache.lucene.luke.app.LukeState;
+import org.apache.lucene.luke.app.desktop.MessageBroker;
+import org.apache.lucene.luke.app.desktop.Preferences;
+import org.apache.lucene.luke.app.desktop.PreferencesFactory;
+import org.apache.lucene.luke.app.desktop.util.FontUtils;
+import org.apache.lucene.luke.app.desktop.util.ImageUtils;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+import org.apache.lucene.luke.app.desktop.util.TextAreaAppender;
+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 final Preferences prefs;
+
+ private final MessageBroker messageBroker;
+
+ private final TabSwitcherProxy tabSwitcher;
+
+ private final JMenuBar menuBar;
+
+ private final JTabbedPane tabbedPane;
+
+ private final JLabel messageLbl = new JLabel();
+
+ private final JLabel multiIcon = new JLabel();
+
+ private final JLabel readOnlyIcon = new JLabel();
+
+ private final JLabel noReaderIcon = new JLabel();
+
+ private JFrame frame = new JFrame();
+
+ public LukeWindowProvider() throws IOException {
+ // prepare log4j appender for Logs tab.
+ JTextArea logTextArea = new JTextArea();
+ logTextArea.setEditable(false);
+ TextAreaAppender.setTextArea(logTextArea);
+
+ this.prefs = PreferencesFactory.getInstance();
+ this.menuBar = new MenuBarProvider().get();
+ this.tabbedPane = new TabbedPaneProvider(logTextArea).get();
+ this.messageBroker = MessageBroker.getInstance();
+ this.tabSwitcher = TabSwitcherProxy.getInstance();
+
+ ComponentOperatorRegistry.getInstance().register(LukeWindowOperator.class, this);
+ Observer observer = new Observer();
+ DirectoryHandler.getInstance().addObserver(observer);
+ IndexHandler.getInstance().addObserver(observer);
+
+ messageBroker.registerReceiver(new MessageReceiverImpl());
+ }
+
+ public JFrame get() {
+ frame.setTitle(WINDOW_TITLE);
+ frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+
+ frame.setJMenuBar(menuBar);
+ frame.add(initMainPanel(), BorderLayout.CENTER);
+ frame.add(initMessagePanel(), BorderLayout.PAGE_END);
+
+ frame.setPreferredSize(new Dimension(950, 680));
+ frame.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor());
+
+ return frame;
+ }
+
+ private JPanel initMainPanel() {
+ JPanel panel = new JPanel(new GridLayout(1, 1));
+
+ tabbedPane.setEnabledAt(TabbedPaneProvider.Tab.OVERVIEW.index(), false);
+ tabbedPane.setEnabledAt(TabbedPaneProvider.Tab.DOCUMENTS.index(), false);
+ tabbedPane.setEnabledAt(TabbedPaneProvider.Tab.SEARCH.index(), false);
+ tabbedPane.setEnabledAt(TabbedPaneProvider.Tab.COMMITS.index(), false);
+
+ panel.add(tabbedPane);
+
+ panel.setOpaque(false);
+ return panel;
+ }
+
+ private JPanel initMessagePanel() {
+ JPanel panel = new JPanel(new GridLayout(1, 1));
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(0, 2, 2, 2));
+
+ JPanel innerPanel = new JPanel(new GridBagLayout());
+ innerPanel.setOpaque(false);
+ innerPanel.setBorder(BorderFactory.createLineBorder(Color.gray));
+ GridBagConstraints c = new GridBagConstraints();
+ c.fill = GridBagConstraints.HORIZONTAL;
+
+ JPanel msgPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ msgPanel.setOpaque(false);
+ msgPanel.add(messageLbl);
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0.8;
+ innerPanel.add(msgPanel, c);
+
+ JPanel iconPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+ iconPanel.setOpaque(false);
+
+ multiIcon.setText(FontUtils.elegantIconHtml(""));
+ multiIcon.setToolTipText(MessageUtils.getLocalizedMessage("tooltip.multi_reader"));
+ multiIcon.setVisible(false);
+ iconPanel.add(multiIcon);
+
+
+ readOnlyIcon.setText(FontUtils.elegantIconHtml(""));
+ readOnlyIcon.setToolTipText(MessageUtils.getLocalizedMessage("tooltip.read_only"));
+ readOnlyIcon.setVisible(false);
+ iconPanel.add(readOnlyIcon);
+
+ noReaderIcon.setText(FontUtils.elegantIconHtml(""));
+ noReaderIcon.setToolTipText(MessageUtils.getLocalizedMessage("tooltip.no_reader"));
+ noReaderIcon.setVisible(false);
+ iconPanel.add(noReaderIcon);
+
+ JLabel luceneIcon = new JLabel(ImageUtils.createImageIcon("lucene.gif", "lucene", 16, 16));
+ iconPanel.add(luceneIcon);
+
+ c.gridx = 1;
+ c.gridy = 0;
+ c.weightx = 0.2;
+ innerPanel.add(iconPanel);
+ panel.add(innerPanel);
+
+ return panel;
+ }
+
+ @Override
+ public void setColorTheme(Preferences.ColorTheme theme) {
+ frame.getContentPane().setBackground(theme.getBackgroundColor());
+ }
+
+ private class Observer implements IndexObserver, DirectoryObserver {
+
+ @Override
+ public void openDirectory(LukeState state) {
+ multiIcon.setVisible(false);
+ readOnlyIcon.setVisible(false);
+ noReaderIcon.setVisible(true);
+
+ tabSwitcher.switchTab(TabbedPaneProvider.Tab.COMMITS);
+
+ messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("message.directory_opened"));
+ }
+
+ @Override
+ public void closeDirectory() {
+ multiIcon.setVisible(false);
+ readOnlyIcon.setVisible(false);
+ noReaderIcon.setVisible(false);
+
+ messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("message.directory_closed"));
+ }
+
+ @Override
+ public void openIndex(LukeState state) {
+ multiIcon.setVisible(!state.hasDirectoryReader());
+ readOnlyIcon.setVisible(state.readOnly());
+ noReaderIcon.setVisible(false);
+
+ if (state.readOnly()) {
+ messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("message.index_opened_ro"));
+ } else if (!state.hasDirectoryReader()) {
+ messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("message.index_opened_multi"));
+ } else {
+ messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("message.index_opened"));
+ }
+ }
+
+ @Override
+ public void closeIndex() {
+ multiIcon.setVisible(false);
+ readOnlyIcon.setVisible(false);
+ noReaderIcon.setVisible(false);
+
+ messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("message.index_closed"));
+ }
+
+ }
+
+ private class MessageReceiverImpl implements MessageBroker.MessageReceiver {
+
+ @Override
+ public void showStatusMessage(String message) {
+ messageLbl.setText(message);
+ }
+
+ @Override
+ public void showUnknownErrorMessage() {
+ messageLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown"));
+ }
+
+ @Override
+ public void clearStatusMessage() {
+ messageLbl.setText("");
+ }
+
+ private MessageReceiverImpl() {
+ }
+
+ }
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/MenuBarProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/MenuBarProvider.java
new file mode 100644
index 0000000..2a5008f
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/MenuBarProvider.java
@@ -0,0 +1,303 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components;
+
+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;
+import org.apache.lucene.luke.app.IndexObserver;
+import org.apache.lucene.luke.app.LukeState;
+import org.apache.lucene.luke.app.desktop.Preferences;
+import org.apache.lucene.luke.app.desktop.PreferencesFactory;
+import org.apache.lucene.luke.app.desktop.components.dialog.menubar.AboutDialogFactory;
+import org.apache.lucene.luke.app.desktop.components.dialog.menubar.CheckIndexDialogFactory;
+import org.apache.lucene.luke.app.desktop.components.dialog.menubar.CreateIndexDialogFactory;
+import org.apache.lucene.luke.app.desktop.components.dialog.menubar.OpenIndexDialogFactory;
+import org.apache.lucene.luke.app.desktop.components.dialog.menubar.OptimizeIndexDialogFactory;
+import org.apache.lucene.luke.app.desktop.util.DialogOpener;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+import org.apache.lucene.luke.models.LukeException;
+import org.apache.lucene.util.Version;
+
+/** Provider of the MenuBar */
+public final class MenuBarProvider {
+
+ private final Preferences prefs;
+
+ private final ComponentOperatorRegistry operatorRegistry;
+
+ private final DirectoryHandler directoryHandler;
+
+ private final IndexHandler indexHandler;
+
+ private final OpenIndexDialogFactory openIndexDialogFactory;
+
+ private final CreateIndexDialogFactory createIndexDialogFactory;
+
+ private final OptimizeIndexDialogFactory optimizeIndexDialogFactory;
+
+ private final CheckIndexDialogFactory checkIndexDialogFactory;
+
+ private final AboutDialogFactory aboutDialogFactory;
+
+ private final JMenuItem openIndexMItem = new JMenuItem();
+
+ private final JMenuItem reopenIndexMItem = new JMenuItem();
+
+ private final JMenuItem createIndexMItem = new JMenuItem();
+
+ private final JMenuItem closeIndexMItem = new JMenuItem();
+
+ private final JMenuItem grayThemeMItem = new JMenuItem();
+
+ private final JMenuItem classicThemeMItem = new JMenuItem();
+
+ private final JMenuItem sandstoneThemeMItem = new JMenuItem();
+
+ private final JMenuItem navyThemeMItem = new JMenuItem();
+
+ private final JMenuItem exitMItem = new JMenuItem();
+
+ private final JMenuItem optimizeIndexMItem = new JMenuItem();
+
+ private final JMenuItem checkIndexMItem = new JMenuItem();
+
+ private final JMenuItem aboutMItem = new JMenuItem();
+
+ private final ListenerFunctions listeners = new ListenerFunctions();
+
+ public MenuBarProvider() throws IOException {
+ this.prefs = PreferencesFactory.getInstance();
+ this.directoryHandler = DirectoryHandler.getInstance();
+ this.indexHandler = IndexHandler.getInstance();
+ this.operatorRegistry = ComponentOperatorRegistry.getInstance();
+ this.openIndexDialogFactory = OpenIndexDialogFactory.getInstance();
+ this.createIndexDialogFactory = CreateIndexDialogFactory.getInstance();
+ this.optimizeIndexDialogFactory = OptimizeIndexDialogFactory.getInstance();
+ this.checkIndexDialogFactory = CheckIndexDialogFactory.getInstance();
+ this.aboutDialogFactory = AboutDialogFactory.getInstance();
+
+ Observer observer = new Observer();
+ directoryHandler.addObserver(observer);
+ indexHandler.addObserver(observer);
+ }
+
+ public JMenuBar get() {
+ JMenuBar menuBar = new JMenuBar();
+
+ menuBar.add(createFileMenu());
+ menuBar.add(createToolsMenu());
+ menuBar.add(createHelpMenu());
+
+ return menuBar;
+ }
+
+ private JMenu createFileMenu() {
+ JMenu fileMenu = new JMenu(MessageUtils.getLocalizedMessage("menu.file"));
+
+ openIndexMItem.setText(MessageUtils.getLocalizedMessage("menu.item.open_index"));
+ openIndexMItem.addActionListener(listeners::showOpenIndexDialog);
+ fileMenu.add(openIndexMItem);
+
+ reopenIndexMItem.setText(MessageUtils.getLocalizedMessage("menu.item.reopen_index"));
+ reopenIndexMItem.setEnabled(false);
+ reopenIndexMItem.addActionListener(listeners::reopenIndex);
+ fileMenu.add(reopenIndexMItem);
+
+ createIndexMItem.setText(MessageUtils.getLocalizedMessage("menu.item.create_index"));
+ createIndexMItem.addActionListener(listeners::showCreateIndexDialog);
+ fileMenu.add(createIndexMItem);
+
+
+ closeIndexMItem.setText(MessageUtils.getLocalizedMessage("menu.item.close_index"));
+ closeIndexMItem.setEnabled(false);
+ closeIndexMItem.addActionListener(listeners::closeIndex);
+ fileMenu.add(closeIndexMItem);
+
+ fileMenu.addSeparator();
+
+ JMenu settingsMenu = new JMenu(MessageUtils.getLocalizedMessage("menu.settings"));
+ JMenu themeMenu = new JMenu(MessageUtils.getLocalizedMessage("menu.color"));
+ grayThemeMItem.setText(MessageUtils.getLocalizedMessage("menu.item.theme_gray"));
+ grayThemeMItem.addActionListener(listeners::changeThemeToGray);
+ themeMenu.add(grayThemeMItem);
+ classicThemeMItem.setText(MessageUtils.getLocalizedMessage("menu.item.theme_classic"));
+ classicThemeMItem.addActionListener(listeners::changeThemeToClassic);
+ themeMenu.add(classicThemeMItem);
+ sandstoneThemeMItem.setText(MessageUtils.getLocalizedMessage("menu.item.theme_sandstone"));
+ sandstoneThemeMItem.addActionListener(listeners::changeThemeToSandstone);
+ themeMenu.add(sandstoneThemeMItem);
+ navyThemeMItem.setText(MessageUtils.getLocalizedMessage("menu.item.theme_navy"));
+ navyThemeMItem.addActionListener(listeners::changeThemeToNavy);
+ themeMenu.add(navyThemeMItem);
+ settingsMenu.add(themeMenu);
+ fileMenu.add(settingsMenu);
+
+ fileMenu.addSeparator();
+
+ exitMItem.setText(MessageUtils.getLocalizedMessage("menu.item.exit"));
+ exitMItem.addActionListener(listeners::exit);
+ fileMenu.add(exitMItem);
+
+ return fileMenu;
+ }
+
+ private JMenu createToolsMenu() {
+ JMenu toolsMenu = new JMenu(MessageUtils.getLocalizedMessage("menu.tools"));
+ optimizeIndexMItem.setText(MessageUtils.getLocalizedMessage("menu.item.optimize"));
+ optimizeIndexMItem.setEnabled(false);
+ optimizeIndexMItem.addActionListener(listeners::showOptimizeIndexDialog);
+ toolsMenu.add(optimizeIndexMItem);
+ checkIndexMItem.setText(MessageUtils.getLocalizedMessage("menu.item.check_index"));
+ checkIndexMItem.setEnabled(false);
+ checkIndexMItem.addActionListener(listeners::showCheckIndexDialog);
+ toolsMenu.add(checkIndexMItem);
+ return toolsMenu;
+ }
+
+ private JMenu createHelpMenu() {
+ JMenu helpMenu = new JMenu(MessageUtils.getLocalizedMessage("menu.help"));
+ aboutMItem.setText(MessageUtils.getLocalizedMessage("menu.item.about"));
+ aboutMItem.addActionListener(listeners::showAboutDialog);
+ helpMenu.add(aboutMItem);
+ return helpMenu;
+ }
+
+ private class ListenerFunctions {
+
+ void showOpenIndexDialog(ActionEvent e) {
+ 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) -> {});
+ }
+
+ void reopenIndex(ActionEvent e) {
+ indexHandler.reOpen();
+ }
+
+ void closeIndex(ActionEvent e) {
+ close();
+ }
+
+ void changeThemeToGray(ActionEvent e) {
+ changeTheme(Preferences.ColorTheme.GRAY);
+ }
+
+ void changeThemeToClassic(ActionEvent e) {
+ changeTheme(Preferences.ColorTheme.CLASSIC);
+ }
+
+ void changeThemeToSandstone(ActionEvent e) {
+ changeTheme(Preferences.ColorTheme.SANDSTONE);
+ }
+
+ void changeThemeToNavy(ActionEvent e) {
+ changeTheme(Preferences.ColorTheme.NAVY);
+ }
+
+ private void changeTheme(Preferences.ColorTheme theme) {
+ try {
+ prefs.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);
+ }
+ }
+
+ void exit(ActionEvent e) {
+ close();
+ System.exit(0);
+ }
+
+ private void close() {
+ directoryHandler.close();
+ indexHandler.close();
+ }
+
+ void showOptimizeIndexDialog(ActionEvent e) {
+ new DialogOpener<>(optimizeIndexDialogFactory).open("Optimize index", 600, 600,
+ factory -> {
+ });
+ }
+
+ void showCheckIndexDialog(ActionEvent e) {
+ 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 -> {
+ });
+ }
+
+ }
+
+ private class Observer implements IndexObserver, DirectoryObserver {
+
+ @Override
+ public void openDirectory(LukeState state) {
+ reopenIndexMItem.setEnabled(false);
+ closeIndexMItem.setEnabled(false);
+ optimizeIndexMItem.setEnabled(false);
+ checkIndexMItem.setEnabled(true);
+ }
+
+ @Override
+ public void closeDirectory() {
+ close();
+ }
+
+ @Override
+ public void openIndex(LukeState state) {
+ reopenIndexMItem.setEnabled(true);
+ closeIndexMItem.setEnabled(true);
+ if (!state.readOnly() && state.hasDirectoryReader()) {
+ optimizeIndexMItem.setEnabled(true);
+ }
+ if (state.hasDirectoryReader()) {
+ checkIndexMItem.setEnabled(true);
+ }
+ }
+
+ @Override
+ public void closeIndex() {
+ close();
+ }
+
+ private void close() {
+ reopenIndexMItem.setEnabled(false);
+ closeIndexMItem.setEnabled(false);
+ optimizeIndexMItem.setEnabled(false);
+ checkIndexMItem.setEnabled(false);
+ }
+
+ }
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/OverviewPanelProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/OverviewPanelProvider.java
new file mode 100644
index 0000000..c85e93b
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/OverviewPanelProvider.java
@@ -0,0 +1,644 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+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.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;
+import org.apache.lucene.luke.app.desktop.MessageBroker;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+import org.apache.lucene.luke.app.desktop.util.StyleConstants;
+import org.apache.lucene.luke.app.desktop.util.TableUtils;
+import org.apache.lucene.luke.models.overview.Overview;
+import org.apache.lucene.luke.models.overview.OverviewFactory;
+import org.apache.lucene.luke.models.overview.TermCountsOrder;
+import org.apache.lucene.luke.models.overview.TermStats;
+
+/** Provider of the Overview panel */
+public final class OverviewPanelProvider {
+
+ private static final int GRIDX_DESC = 0;
+ private static final int GRIDX_VAL = 1;
+ private static final double WEIGHTX_DESC = 0.1;
+ private static final double WEIGHTX_VAL = 0.9;
+
+ private final OverviewFactory overviewFactory = new OverviewFactory();
+
+ private final ComponentOperatorRegistry operatorRegistry;
+
+ private final TabSwitcherProxy tabSwitcher;
+
+ private final MessageBroker messageBroker;
+
+ private final JPanel panel = new JPanel();
+
+ private final JLabel indexPathLbl = new JLabel();
+
+ private final JLabel numFieldsLbl = new JLabel();
+
+ private final JLabel numDocsLbl = new JLabel();
+
+ private final JLabel numTermsLbl = new JLabel();
+
+ private final JLabel delOptLbl = new JLabel();
+
+ private final JLabel indexVerLbl = new JLabel();
+
+ private final JLabel indexFmtLbl = new JLabel();
+
+ private final JLabel dirImplLbl = new JLabel();
+
+ private final JLabel commitPointLbl = new JLabel();
+
+ private final JLabel commitUserDataLbl = new JLabel();
+
+ private final JTable termCountsTable = new JTable();
+
+ private final JTextField selectedField = new JTextField();
+
+ private final JButton showTopTermsBtn = new JButton();
+
+ private final JSpinner numTopTermsSpnr = new JSpinner();
+
+ private final JTable topTermsTable = new JTable();
+
+ private final JPopupMenu topTermsContextMenu = new JPopupMenu();
+
+ private final ListenerFunctions listeners = new ListenerFunctions();
+
+ private Overview overviewModel;
+
+ public OverviewPanelProvider() {
+ this.messageBroker = MessageBroker.getInstance();
+ this.operatorRegistry = ComponentOperatorRegistry.getInstance();
+ this.tabSwitcher = TabSwitcherProxy.getInstance();
+
+ IndexHandler.getInstance().addObserver(new Observer());
+ }
+
+ public JPanel get() {
+ panel.setOpaque(false);
+ panel.setLayout(new GridLayout(1, 1));
+ panel.setBorder(BorderFactory.createLineBorder(Color.gray));
+
+ JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel());
+ splitPane.setDividerLocation(0.4);
+ splitPane.setOpaque(false);
+ panel.add(splitPane);
+
+ setUpTopTermsContextMenu();
+
+ return panel;
+ }
+
+ private JPanel initUpperPanel() {
+ JPanel panel = new JPanel(new GridBagLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.insets = new Insets(2, 10, 2, 2);
+ c.gridy = 0;
+
+ c.gridx = GRIDX_DESC;
+ c.weightx = WEIGHTX_DESC;
+ panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.index_path"), JLabel.RIGHT), c);
+
+ c.gridx = GRIDX_VAL;
+ c.weightx = WEIGHTX_VAL;
+ indexPathLbl.setText("?");
+ panel.add(indexPathLbl, c);
+
+ c.gridx = GRIDX_DESC;
+ c.gridy += 1;
+ c.weightx = WEIGHTX_DESC;
+ panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.num_fields"), JLabel.RIGHT), c);
+
+ c.gridx = GRIDX_VAL;
+ c.weightx = WEIGHTX_VAL;
+ numFieldsLbl.setText("?");
+ panel.add(numFieldsLbl, c);
+
+ c.gridx = GRIDX_DESC;
+ c.gridy += 1;
+ c.weightx = WEIGHTX_DESC;
+ panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.num_docs"), JLabel.RIGHT), c);
+
+ c.gridx = GRIDX_VAL;
+ c.weightx = WEIGHTX_VAL;
+ numDocsLbl.setText("?");
+ panel.add(numDocsLbl, c);
+
+ c.gridx = GRIDX_DESC;
+ c.gridy += 1;
+ c.weightx = WEIGHTX_DESC;
+ panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.num_terms"), JLabel.RIGHT), c);
+
+ c.gridx = GRIDX_VAL;
+ c.weightx = WEIGHTX_VAL;
+ numTermsLbl.setText("?");
+ panel.add(numTermsLbl, c);
+
+ c.gridx = GRIDX_DESC;
+ c.gridy += 1;
+ c.weightx = WEIGHTX_DESC;
+ panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.del_opt"), JLabel.RIGHT), c);
+
+ c.gridx = GRIDX_VAL;
+ c.weightx = WEIGHTX_VAL;
+ delOptLbl.setText("?");
+ panel.add(delOptLbl, c);
+
+ c.gridx = GRIDX_DESC;
+ c.gridy += 1;
+ c.weightx = WEIGHTX_DESC;
+ panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.index_version"), JLabel.RIGHT), c);
+
+ c.gridx = GRIDX_VAL;
+ c.weightx = WEIGHTX_VAL;
+ indexVerLbl.setText("?");
+ panel.add(indexVerLbl, c);
+
+ c.gridx = GRIDX_DESC;
+ c.gridy += 1;
+ c.weightx = WEIGHTX_DESC;
+ panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.index_format"), JLabel.RIGHT), c);
+
+ c.gridx = GRIDX_VAL;
+ c.weightx = WEIGHTX_VAL;
+ indexFmtLbl.setText("?");
+ panel.add(indexFmtLbl, c);
+
+ c.gridx = GRIDX_DESC;
+ c.gridy += 1;
+ c.weightx = WEIGHTX_DESC;
+ panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.dir_impl"), JLabel.RIGHT), c);
+
+ c.gridx = GRIDX_VAL;
+ c.weightx = WEIGHTX_VAL;
+ dirImplLbl.setText("?");
+ panel.add(dirImplLbl, c);
+
+ c.gridx = GRIDX_DESC;
+ c.gridy += 1;
+ c.weightx = WEIGHTX_DESC;
+ panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.commit_point"), JLabel.RIGHT), c);
+
+ c.gridx = GRIDX_VAL;
+ c.weightx = WEIGHTX_VAL;
+ commitPointLbl.setText("?");
+ panel.add(commitPointLbl, c);
+
+ c.gridx = GRIDX_DESC;
+ c.gridy += 1;
+ c.weightx = WEIGHTX_DESC;
+ panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.commit_userdata"), JLabel.RIGHT), c);
+
+ c.gridx = GRIDX_VAL;
+ c.weightx = WEIGHTX_VAL;
+ commitUserDataLbl.setText("?");
+ panel.add(commitUserDataLbl, c);
+
+ return panel;
+ }
+
+ private JPanel initLowerPanel() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+
+ JLabel label = new JLabel(MessageUtils.getLocalizedMessage("overview.label.select_fields"));
+ label.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+ panel.add(label, BorderLayout.PAGE_START);
+
+ JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, initTermCountsPanel(), initTopTermsPanel());
+ splitPane.setOpaque(false);
+ splitPane.setDividerLocation(320);
+ splitPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+ panel.add(splitPane, BorderLayout.CENTER);
+
+ return panel;
+ }
+
+ private JPanel initTermCountsPanel() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+
+ JLabel label = new JLabel(MessageUtils.getLocalizedMessage("overview.label.available_fields"));
+ label.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0));
+ panel.add(label, BorderLayout.PAGE_START);
+
+ 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());
+ JScrollPane scrollPane = new JScrollPane(termCountsTable);
+ panel.add(scrollPane, BorderLayout.CENTER);
+
+ panel.setOpaque(false);
+ return panel;
+ }
+
+ private JPanel initTopTermsPanel() {
+ JPanel panel = new JPanel(new GridLayout(1, 1));
+ panel.setOpaque(false);
+
+ JPanel selectedPanel = new JPanel(new BorderLayout());
+ selectedPanel.setOpaque(false);
+ JPanel innerPanel = new JPanel();
+ innerPanel.setOpaque(false);
+ innerPanel.setLayout(new BoxLayout(innerPanel, BoxLayout.PAGE_AXIS));
+ innerPanel.setBorder(BorderFactory.createEmptyBorder(20, 0, 0, 0));
+ selectedPanel.add(innerPanel, BorderLayout.PAGE_START);
+
+ JPanel innerPanel1 = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ innerPanel1.setOpaque(false);
+ innerPanel1.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.selected_field")));
+ innerPanel.add(innerPanel1);
+
+ selectedField.setColumns(20);
+ selectedField.setPreferredSize(new Dimension(100, 30));
+ selectedField.setFont(StyleConstants.FONT_MONOSPACE_LARGE);
+ selectedField.setEditable(false);
+ selectedField.setBackground(Color.white);
+ JPanel innerPanel2 = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ innerPanel2.setOpaque(false);
+ innerPanel2.add(selectedField);
+ innerPanel.add(innerPanel2);
+
+ showTopTermsBtn.setText(MessageUtils.getLocalizedMessage("overview.button.show_terms"));
+ showTopTermsBtn.setPreferredSize(new Dimension(170, 40));
+ showTopTermsBtn.setFont(StyleConstants.FONT_BUTTON_LARGE);
+ showTopTermsBtn.addActionListener(listeners::showTopTerms);
+ showTopTermsBtn.setEnabled(false);
+ JPanel innerPanel3 = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ innerPanel3.setOpaque(false);
+ innerPanel3.add(showTopTermsBtn);
+ innerPanel.add(innerPanel3);
+
+ JPanel innerPanel4 = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ innerPanel4.setOpaque(false);
+ innerPanel4.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.num_top_terms")));
+ innerPanel.add(innerPanel4);
+
+ SpinnerNumberModel numberModel = new SpinnerNumberModel(50, 0, 1000, 1);
+ numTopTermsSpnr.setPreferredSize(new Dimension(80, 30));
+ numTopTermsSpnr.setModel(numberModel);
+ JPanel innerPanel5 = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ innerPanel5.setOpaque(false);
+ innerPanel5.add(numTopTermsSpnr);
+ innerPanel.add(innerPanel5);
+
+ JPanel termsPanel = new JPanel(new BorderLayout());
+ termsPanel.setOpaque(false);
+ JLabel label = new JLabel(MessageUtils.getLocalizedMessage("overview.label.top_terms"));
+ label.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0));
+ termsPanel.add(label, BorderLayout.PAGE_START);
+
+ 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());
+ JScrollPane scrollPane = new JScrollPane(topTermsTable);
+ termsPanel.add(scrollPane, BorderLayout.CENTER);
+
+ JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, selectedPanel, termsPanel);
+ splitPane.setOpaque(false);
+ splitPane.setDividerLocation(180);
+ splitPane.setBorder(BorderFactory.createEmptyBorder());
+ panel.add(splitPane);
+
+ return panel;
+ }
+
+ private void setUpTopTermsContextMenu() {
+ 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"));
+ item2.addActionListener(listeners::searchByTerm);
+ topTermsContextMenu.add(item2);
+ }
+
+ // control methods
+
+ private void selectField() {
+ String field = getSelectedField();
+ selectedField.setText(field);
+ showTopTermsBtn.setEnabled(true);
+ }
+
+ private void showTopTerms() {
+ String field = getSelectedField();
+ int numTerms = (int) numTopTermsSpnr.getModel().getValue();
+ List<TermStats> termStats = overviewModel.getTopTerms(field, numTerms);
+
+ // 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());
+ 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);
+ });
+ }
+
+ private void searchByTerm() {
+ String field = getSelectedField();
+ String term = getSelectedTerm();
+ operatorRegistry.get(SearchTabOperator.class).ifPresent(operator -> {
+ operator.searchByTerm(field, term);
+ tabSwitcher.switchTab(TabbedPaneProvider.Tab.SEARCH);
+ });
+ }
+
+ private String getSelectedField() {
+ int selected = termCountsTable.getSelectedRow();
+ // need to convert selected row index to underlying model index
+ // https://docs.oracle.com/javase/8/docs/api/javax/swing/table/TableRowSorter.html
+ int row = termCountsTable.convertRowIndexToModel(selected);
+ if (row < 0 || row >= termCountsTable.getRowCount()) {
+ throw new IllegalStateException("Field is not selected.");
+ }
+ return (String) termCountsTable.getModel().getValueAt(row, TermCountsTableModel.Column.NAME.getIndex());
+ }
+
+ private String getSelectedTerm() {
+ int rowTerm = topTermsTable.getSelectedRow();
+ if (rowTerm < 0 || rowTerm >= topTermsTable.getRowCount()) {
+ throw new IllegalStateException("Term is not selected.");
+ }
+ return (String) topTermsTable.getModel().getValueAt(rowTerm, TopTermsTableModel.Column.TEXT.getIndex());
+ }
+
+ private class ListenerFunctions {
+
+ void selectField(MouseEvent e) {
+ OverviewPanelProvider.this.selectField();
+ }
+
+ void showTopTerms(ActionEvent e) {
+ OverviewPanelProvider.this.showTopTerms();
+ }
+
+ void showTopTermsContextMenu(MouseEvent e) {
+ if (e.getClickCount() == 2 && !e.isConsumed()) {
+ int row = topTermsTable.rowAtPoint(e.getPoint());
+ if (row != topTermsTable.getSelectedRow()) {
+ topTermsTable.changeSelection(row, topTermsTable.getSelectedColumn(), false, false);
+ }
+ topTermsContextMenu.show(e.getComponent(), e.getX(), e.getY());
+ }
+ }
+
+ void browseByTerm(ActionEvent e) {
+ OverviewPanelProvider.this.browseByTerm();
+ }
+
+ void searchByTerm(ActionEvent e) {
+ OverviewPanelProvider.this.searchByTerm();
+ }
+
+ }
+
+ private class Observer implements IndexObserver {
+
+ @Override
+ public void openIndex(LukeState state) {
+ overviewModel = overviewFactory.newInstance(state.getIndexReader(), state.getIndexPath());
+
+ indexPathLbl.setText(overviewModel.getIndexPath());
+ indexPathLbl.setToolTipText(overviewModel.getIndexPath());
+ 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 opt = overviewModel.isOptimized().map(b -> b ? "Yes" : "No").orElse("?");
+ delOptLbl.setText(del + " / " + opt);
+ indexVerLbl.setText(overviewModel.getIndexVersion().map(v -> Long.toString(v)).orElse("?"));
+ indexFmtLbl.setText(overviewModel.getIndexFormat().orElse(""));
+ dirImplLbl.setText(overviewModel.getDirImpl().orElse(""));
+ commitPointLbl.setText(overviewModel.getCommitDescription().orElse("---"));
+ commitUserDataLbl.setText(overviewModel.getCommitUserData().orElse("---"));
+
+ // term counts table
+ Map<String, Long> termCounts = overviewModel.getSortedTermCounts(TermCountsOrder.COUNT_DESC);
+ 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());
+ DefaultTableCellRenderer rightRenderer = new DefaultTableCellRenderer();
+ rightRenderer.setHorizontalAlignment(JLabel.RIGHT);
+ 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().setColumnMargin(StyleConstants.TABLE_COLUMN_MARGIN_DEFAULT);
+ }
+
+ @Override
+ public void closeIndex() {
+ indexPathLbl.setText("");
+ numFieldsLbl.setText("");
+ numDocsLbl.setText("");
+ numTermsLbl.setText("");
+ delOptLbl.setText("");
+ indexVerLbl.setText("");
+ indexFmtLbl.setText("");
+ dirImplLbl.setText("");
+ commitPointLbl.setText("");
+ commitUserDataLbl.setText("");
+
+ selectedField.setText("");
+ showTopTermsBtn.setEnabled(false);
+
+ termCountsTable.setRowSorter(null);
+ 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);
+
+ private final String colName;
+ private final int index;
+ private final Class<?> type;
+ private final int width;
+
+ Column(String colName, int index, Class<?> type, int width) {
+ this.colName = colName;
+ this.index = index;
+ this.type = type;
+ this.width = width;
+ }
+
+ @Override
+ public String getColName() {
+ return colName;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public Class<?> getType() {
+ return type;
+ }
+
+ @Override
+ public int getColumnWidth() {
+ return width;
+ }
+ }
+
+ TermCountsTableModel() {
+ super();
+ }
+
+ TermCountsTableModel(double numTerms, Map<String, Long> termCounts) {
+ super(termCounts.size());
+ int i = 0;
+ 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)};
+ }
+ }
+
+ @Override
+ protected Column[] columnInfos() {
+ return Column.values();
+ }
+ }
+
+ static final class TopTermsTableModel extends TableModelBase<TopTermsTableModel.Column> {
+
+ enum Column implements TableColumnInfo {
+ RANK("Rank", 0, Integer.class, 50),
+ FREQ("Freq", 1, Integer.class, 80),
+ TEXT("Text", 2, String.class, Integer.MAX_VALUE);
+
+ private final String colName;
+ private final int index;
+ private final Class<?> type;
+ private final int width;
+
+ Column(String colName, int index, Class<?> type, int width) {
+ this.colName = colName;
+ this.index = index;
+ this.type = type;
+ this.width = width;
+ }
+
+ @Override
+ public String getColName() {
+ return colName;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public Class<?> getType() {
+ return type;
+ }
+
+ @Override
+ public int getColumnWidth() {
+ return width;
+ }
+ }
+
+ TopTermsTableModel() {
+ super();
+ }
+
+ TopTermsTableModel(List<TermStats> termStats, int numTerms) {
+ super(Math.min(numTerms, termStats.size()));
+ for (int i = 0; i < data.length; i++) {
+ int rank = i + 1;
+ int freq = termStats.get(i).getDocFreq();
+ String termText = termStats.get(i).getDecodedTermText();
+ data[i] = new Object[]{rank, freq, termText};
+ }
+ }
+
+ @Override
+ protected Column[] columnInfos() {
+ return Column.values();
+ }
+ }
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/SearchPanelProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/SearchPanelProvider.java
new file mode 100644
index 0000000..f94517a
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/SearchPanelProvider.java
@@ -0,0 +1,834 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+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.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.luke.app.IndexHandler;
+import org.apache.lucene.luke.app.IndexObserver;
+import org.apache.lucene.luke.app.LukeState;
+import org.apache.lucene.luke.app.desktop.MessageBroker;
+import org.apache.lucene.luke.app.desktop.components.dialog.ConfirmDialogFactory;
+import org.apache.lucene.luke.app.desktop.components.dialog.search.ExplainDialogFactory;
+import org.apache.lucene.luke.app.desktop.components.fragments.search.AnalyzerPaneProvider;
+import org.apache.lucene.luke.app.desktop.components.fragments.search.FieldValuesPaneProvider;
+import org.apache.lucene.luke.app.desktop.components.fragments.search.FieldValuesTabOperator;
+import org.apache.lucene.luke.app.desktop.components.fragments.search.MLTPaneProvider;
+import org.apache.lucene.luke.app.desktop.components.fragments.search.MLTTabOperator;
+import org.apache.lucene.luke.app.desktop.components.fragments.search.QueryParserPaneProvider;
+import org.apache.lucene.luke.app.desktop.components.fragments.search.QueryParserTabOperator;
+import org.apache.lucene.luke.app.desktop.components.fragments.search.SimilarityPaneProvider;
+import org.apache.lucene.luke.app.desktop.components.fragments.search.SimilarityTabOperator;
+import org.apache.lucene.luke.app.desktop.components.fragments.search.SortPaneProvider;
+import org.apache.lucene.luke.app.desktop.components.fragments.search.SortTabOperator;
+import org.apache.lucene.luke.app.desktop.util.DialogOpener;
+import org.apache.lucene.luke.app.desktop.util.FontUtils;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+import org.apache.lucene.luke.app.desktop.util.StringUtils;
+import org.apache.lucene.luke.app.desktop.util.StyleConstants;
+import org.apache.lucene.luke.app.desktop.util.TabUtils;
+import org.apache.lucene.luke.app.desktop.util.TableUtils;
+import org.apache.lucene.luke.models.LukeException;
+import org.apache.lucene.luke.models.search.MLTConfig;
+import org.apache.lucene.luke.models.search.QueryParserConfig;
+import org.apache.lucene.luke.models.search.Search;
+import org.apache.lucene.luke.models.search.SearchFactory;
+import org.apache.lucene.luke.models.search.SearchResults;
+import org.apache.lucene.luke.models.search.SimilarityConfig;
+import org.apache.lucene.luke.models.tools.IndexTools;
+import org.apache.lucene.luke.models.tools.IndexToolsFactory;
+import org.apache.lucene.search.Explanation;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TotalHits;
+
+/** Provider of the Search panel */
+public final class SearchPanelProvider implements SearchTabOperator {
+
+ private static final int DEFAULT_PAGE_SIZE = 10;
+
+ private final SearchFactory searchFactory;
+
+ private final IndexToolsFactory toolsFactory;
+
+ private final IndexHandler indexHandler;
+
+ private final MessageBroker messageBroker;
+
+ private final TabSwitcherProxy tabSwitcher;
+
+ private final ComponentOperatorRegistry operatorRegistry;
+
+ private final ConfirmDialogFactory confirmDialogFactory;
+
+ private final ExplainDialogFactory explainDialogProvider;
+
+ private final JTabbedPane tabbedPane = new JTabbedPane();
+
+ private final JScrollPane qparser;
+
+ private final JScrollPane analyzer;
+
+ private final JScrollPane similarity;
+
+ private final JScrollPane sort;
+
+ private final JScrollPane values;
+
+ private final JScrollPane mlt;
+
+ private final JCheckBox termQueryCB = new JCheckBox();
+
+ private final JTextArea queryStringTA = new JTextArea();
+
+ private final JTextArea parsedQueryTA = new JTextArea();
+
+ private final JButton parseBtn = new JButton();
+
+ private final JCheckBox rewriteCB = new JCheckBox();
+
+ private final JButton searchBtn = new JButton();
+
+ private JCheckBox exactHitsCntCB = new JCheckBox();
+
+ private final JButton mltBtn = new JButton();
+
+ private final JFormattedTextField mltDocFTF = new JFormattedTextField();
+
+ private final JLabel totalHitsLbl = new JLabel();
+
+ private final JLabel startLbl = new JLabel();
+
+ private final JLabel endLbl = new JLabel();
+
+ private final JButton prevBtn = new JButton();
+
+ private final JButton nextBtn = new JButton();
+
+ private final JButton delBtn = new JButton();
+
+ private final JTable resultsTable = new JTable();
+
+ private final ListenerFunctions listeners = new ListenerFunctions();
+
+ private Search searchModel;
+
+ private IndexTools toolsModel;
+
+ public SearchPanelProvider() throws IOException {
+ this.searchFactory = new SearchFactory();
+ this.toolsFactory = new IndexToolsFactory();
+ this.indexHandler = IndexHandler.getInstance();
+ this.messageBroker = MessageBroker.getInstance();
+ this.tabSwitcher = TabSwitcherProxy.getInstance();
+ this.operatorRegistry = ComponentOperatorRegistry.getInstance();
+ this.confirmDialogFactory = ConfirmDialogFactory.getInstance();
+ this.explainDialogProvider = ExplainDialogFactory.getInstance();
+ this.qparser = new QueryParserPaneProvider().get();
+ this.analyzer = new AnalyzerPaneProvider().get();
+ this.similarity = new SimilarityPaneProvider().get();
+ this.sort = new SortPaneProvider().get();
+ this.values = new FieldValuesPaneProvider().get();
+ this.mlt = new MLTPaneProvider().get();
+
+ indexHandler.addObserver(new Observer());
+ operatorRegistry.register(SearchTabOperator.class, this);
+ }
+
+ public JPanel get() {
+ JPanel panel = new JPanel(new GridLayout(1, 1));
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createLineBorder(Color.gray));
+
+ JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel());
+ splitPane.setOpaque(false);
+ splitPane.setDividerLocation(350);
+ panel.add(splitPane);
+
+ return panel;
+ }
+
+ private JSplitPane initUpperPanel() {
+ JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, initQuerySettingsPane(), initQueryPane());
+ splitPane.setOpaque(false);
+ splitPane.setDividerLocation(570);
+ return splitPane;
+ }
+
+ private JPanel initQuerySettingsPane() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+
+ JLabel label = new JLabel(MessageUtils.getLocalizedMessage("search.label.settings"));
+ panel.add(label, BorderLayout.PAGE_START);
+
+ tabbedPane.addTab("Query Parser", qparser);
+ tabbedPane.addTab("Analyzer", analyzer);
+ tabbedPane.addTab("Similarity", similarity);
+ tabbedPane.addTab("Sort", sort);
+ tabbedPane.addTab("Field Values", values);
+ tabbedPane.addTab("More Like This", mlt);
+
+ TabUtils.forceTransparent(tabbedPane);
+
+ panel.add(tabbedPane, BorderLayout.CENTER);
+
+ return panel;
+ }
+
+ private JPanel initQueryPane() {
+ JPanel panel = new JPanel(new GridBagLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+ GridBagConstraints c = new GridBagConstraints();
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.LINE_START;
+
+ JLabel labelQE = new JLabel(MessageUtils.getLocalizedMessage("search.label.expression"));
+ c.gridx = 0;
+ c.gridy = 0;
+ c.gridwidth = 2;
+ c.weightx = 0.5;
+ c.insets = new Insets(2, 0, 2, 2);
+ panel.add(labelQE, c);
+
+ termQueryCB.setText(MessageUtils.getLocalizedMessage("search.checkbox.term"));
+ termQueryCB.addActionListener(listeners::toggleTermQuery);
+ termQueryCB.setOpaque(false);
+ c.gridx = 2;
+ c.gridy = 0;
+ c.gridwidth = 1;
+ c.weightx = 0.2;
+ c.insets = new Insets(2, 0, 2, 2);
+ panel.add(termQueryCB, c);
+
+ queryStringTA.setRows(4);
+ queryStringTA.setLineWrap(true);
+ queryStringTA.setText("*:*");
+ c.gridx = 0;
+ c.gridy = 1;
+ 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);
+
+ JLabel labelPQ = new JLabel(MessageUtils.getLocalizedMessage("search.label.parsed"));
+ c.gridx = 0;
+ c.gridy = 2;
+ c.gridwidth = 3;
+ c.weightx = 0.0;
+ c.insets = new Insets(8, 0, 2, 2);
+ panel.add(labelPQ, c);
+
+ parsedQueryTA.setRows(4);
+ parsedQueryTA.setLineWrap(true);
+ parsedQueryTA.setEditable(false);
+ c.gridx = 0;
+ c.gridy = 3;
+ c.gridwidth = 3;
+ c.weightx = 0.0;
+ c.insets = new Insets(2, 0, 2, 2);
+ panel.add(new JScrollPane(parsedQueryTA), c);
+
+ 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);
+ c.gridx = 0;
+ c.gridy = 4;
+ c.gridwidth = 1;
+ c.weightx = 0.2;
+ c.insets = new Insets(5, 0, 0, 2);
+ panel.add(parseBtn, c);
+
+ rewriteCB.setText(MessageUtils.getLocalizedMessage("search.checkbox.rewrite"));
+ rewriteCB.setOpaque(false);
+ c.gridx = 1;
+ c.gridy = 4;
+ c.gridwidth = 2;
+ c.weightx = 0.2;
+ c.insets = new Insets(5, 0, 0, 2);
+ panel.add(rewriteCB, c);
+
+ 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);
+ c.gridx = 0;
+ c.gridy = 5;
+ c.gridwidth = 1;
+ c.weightx = 0.2;
+ c.insets = new Insets(5, 0, 5, 0);
+ panel.add(searchBtn, c);
+
+ exactHitsCntCB.setText(MessageUtils.getLocalizedMessage("search.checkbox.exact_hits_cnt"));
+ exactHitsCntCB.setOpaque(false);
+ c.gridx = 1;
+ c.gridy = 5;
+ c.gridwidth = 2;
+ c.weightx = 0.2;
+ c.insets = new Insets(5, 0, 0, 2);
+ panel.add(exactHitsCntCB, c);
+
+ 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);
+ c.gridx = 0;
+ c.gridy = 6;
+ c.gridwidth = 1;
+ c.weightx = 0.3;
+ c.insets = new Insets(10, 0, 2, 0);
+ panel.add(mltBtn, c);
+
+ JPanel docNo = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ docNo.setOpaque(false);
+ JLabel docNoLabel = new JLabel("with doc #");
+ docNo.add(docNoLabel);
+ mltDocFTF.setColumns(8);
+ mltDocFTF.setValue(0);
+ docNo.add(mltDocFTF);
+ c.gridx = 1;
+ c.gridy = 6;
+ c.gridwidth = 2;
+ c.weightx = 0.3;
+ c.insets = new Insets(8, 0, 0, 2);
+ panel.add(docNo, c);
+
+ return panel;
+ }
+
+ private JPanel initLowerPanel() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+
+ panel.add(initSearchResultsHeaderPane(), BorderLayout.PAGE_START);
+ panel.add(initSearchResultsTablePane(), BorderLayout.CENTER);
+
+ return panel;
+ }
+
+ private JPanel initSearchResultsHeaderPane() {
+ JPanel panel = new JPanel(new GridLayout(1, 2));
+ panel.setOpaque(false);
+
+ 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);
+
+ JPanel resultsInfo = new JPanel(new FlowLayout(FlowLayout.TRAILING));
+ resultsInfo.setOpaque(false);
+ resultsInfo.setOpaque(false);
+
+ JLabel totalLabel = new JLabel(MessageUtils.getLocalizedMessage("search.label.total"));
+ resultsInfo.add(totalLabel);
+
+ totalHitsLbl.setText("?");
+ resultsInfo.add(totalHitsLbl);
+
+ prevBtn.setText(FontUtils.elegantIconHtml("D"));
+ prevBtn.setMargin(new Insets(5, 0, 5, 0));
+ prevBtn.setPreferredSize(new Dimension(30, 20));
+ prevBtn.setEnabled(false);
+ prevBtn.addActionListener(listeners::prevPage);
+ resultsInfo.add(prevBtn);
+
+ startLbl.setText("0");
+ resultsInfo.add(startLbl);
+
+ resultsInfo.add(new JLabel(" ~ "));
+
+ endLbl.setText("0");
+ resultsInfo.add(endLbl);
+
+ nextBtn.setText(FontUtils.elegantIconHtml("E"));
+ nextBtn.setMargin(new Insets(3, 0, 3, 0));
+ nextBtn.setPreferredSize(new Dimension(30, 20));
+ nextBtn.setEnabled(false);
+ nextBtn.addActionListener(listeners::nextPage);
+ resultsInfo.add(nextBtn);
+
+ JSeparator sep = new JSeparator(JSeparator.VERTICAL);
+ sep.setPreferredSize(new Dimension(5, 1));
+ resultsInfo.add(sep);
+
+ 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);
+ resultsInfo.add(delBtn);
+
+ panel.add(resultsInfo, BorderLayout.CENTER);
+
+ return panel;
+ }
+
+ private JPanel initSearchResultsTablePane() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+
+ JPanel note = new JPanel(new FlowLayout(FlowLayout.LEADING, 5, 2));
+ note.setOpaque(false);
+ note.add(new JLabel(MessageUtils.getLocalizedMessage("search.label.results.note")));
+ panel.add(note, BorderLayout.PAGE_START);
+
+ TableUtils.setupTable(resultsTable, ListSelectionModel.SINGLE_SELECTION, new SearchResultsTableModel(),
+ new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ listeners.showContextMenuInResultsTable(e);
+ }
+ },
+ SearchResultsTableModel.Column.DOCID.getColumnWidth(),
+ SearchResultsTableModel.Column.SCORE.getColumnWidth());
+ JScrollPane scrollPane = new JScrollPane(resultsTable);
+ panel.add(scrollPane, BorderLayout.CENTER);
+
+ return panel;
+ }
+
+ // control methods
+
+ private void toggleTermQuery() {
+ if (termQueryCB.isSelected()) {
+ enableTermQuery();
+ } else {
+ disableTermQuery();
+ }
+ }
+
+ private void enableTermQuery() {
+ 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()) {
+ tabbedPane.setSelectedIndex(Tab.SORT.index());
+ }
+ parseBtn.setEnabled(false);
+ rewriteCB.setEnabled(false);
+ }
+
+ private void disableTermQuery() {
+ tabbedPane.setEnabledAt(Tab.QPARSER.index(), true);
+ tabbedPane.setEnabledAt(Tab.ANALYZER.index(), true);
+ tabbedPane.setEnabledAt(Tab.SIMILARITY.index(), true);
+ parseBtn.setEnabled(true);
+ rewriteCB.setEnabled(true);
+ }
+
+ private void execParse() {
+ Query query = parse(rewriteCB.isSelected());
+ parsedQueryTA.setText(query.toString());
+ messageBroker.clearStatusMessage();
+ }
+
+ private void doSearch() {
+ Query query;
+ if (termQueryCB.isSelected()) {
+ // term query
+ if (StringUtils.isNullOrEmpty(queryStringTA.getText())) {
+ throw new LukeException("Query is not set.");
+ }
+ String[] tmp = queryStringTA.getText().split(":");
+ if (tmp.length < 2) {
+ 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());
+
+ TableUtils.setupTable(resultsTable, ListSelectionModel.SINGLE_SELECTION, new SearchResultsTableModel(), null,
+ SearchResultsTableModel.Column.DOCID.getColumnWidth(),
+ SearchResultsTableModel.Column.SCORE.getColumnWidth());
+ populateResults(results);
+
+ messageBroker.clearStatusMessage();
+ }
+
+ private void nextPage() {
+ searchModel.nextPage().ifPresent(this::populateResults);
+ messageBroker.clearStatusMessage();
+ }
+
+ private void prevPage() {
+ searchModel.prevPage().ifPresent(this::populateResults);
+ messageBroker.clearStatusMessage();
+ }
+
+ private void doMLTSearch() {
+ if (Objects.isNull(mltDocFTF.getValue())) {
+ 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());
+ 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);
+
+ TableUtils.setupTable(resultsTable, ListSelectionModel.SINGLE_SELECTION, new SearchResultsTableModel(), null,
+ SearchResultsTableModel.Column.DOCID.getColumnWidth(),
+ SearchResultsTableModel.Column.SCORE.getColumnWidth());
+ populateResults(results);
+
+ messageBroker.clearStatusMessage();
+ }
+
+ 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());
+ return searchModel.parseQuery(expr, df, analyzer, config, rewrite);
+ }
+
+ private void populateResults(SearchResults res) {
+ totalHitsLbl.setText(String.valueOf(res.getTotalHits()));
+ if (res.getTotalHits().value > 0) {
+ startLbl.setText(String.valueOf(res.getOffset() + 1));
+ 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());
+
+ 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());
+ } else {
+ startLbl.setText("0");
+ endLbl.setText("0");
+ prevBtn.setEnabled(false);
+ nextBtn.setEnabled(false);
+ delBtn.setEnabled(false);
+ }
+ }
+
+ private void confirmDeletion() {
+ new DialogOpener<>(confirmDialogFactory).open("Confirm Deletion", 400, 200, (factory) -> {
+ factory.setMessage(MessageUtils.getLocalizedMessage("search.message.delete_confirm"));
+ factory.setCallback(this::deleteDocs);
+ });
+ }
+
+ private void deleteDocs() {
+ Query query = searchModel.getCurrentQuery();
+ if (query != null) {
+ toolsModel.deleteDocuments(query);
+ indexHandler.reOpen();
+ messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("search.message.delete_success", query.toString()));
+ }
+ delBtn.setEnabled(false);
+ }
+
+ private JPopupMenu setupResultsContextMenuPopup() {
+ 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);
+ });
+ });
+ 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);
+ });
+ popup.add(item2);
+
+ return popup;
+ }
+
+ @Override
+ public void searchByTerm(String field, String term) {
+ termQueryCB.setSelected(true);
+ enableTermQuery();
+ queryStringTA.setText(field + ":" + term);
+ doSearch();
+ }
+
+ @Override
+ public void mltSearch(int docNum) {
+ mltDocFTF.setValue(docNum);
+ doMLTSearch();
+ tabbedPane.setSelectedIndex(Tab.MLT.index());
+ }
+
+ @Override
+ public void enableExactHitsCB(boolean value) {
+ exactHitsCntCB.setEnabled(value);
+ }
+
+ @Override
+ public void setExactHits(boolean value) {
+ exactHitsCntCB.setSelected(value);
+ }
+
+ private class ListenerFunctions {
+
+ void toggleTermQuery(ActionEvent e) {
+ SearchPanelProvider.this.toggleTermQuery();
+ }
+
+ void execParse(ActionEvent e) {
+ SearchPanelProvider.this.execParse();
+ }
+
+ void execSearch(ActionEvent e) {
+ SearchPanelProvider.this.doSearch();
+ }
+
+ void nextPage(ActionEvent e) {
+ SearchPanelProvider.this.nextPage();
+ }
+
+ void prevPage(ActionEvent e) {
+ SearchPanelProvider.this.prevPage();
+ }
+
+ void execMLTSearch(ActionEvent e) {
+ SearchPanelProvider.this.doMLTSearch();
+ }
+
+ void confirmDeletion(ActionEvent e) {
+ SearchPanelProvider.this.confirmDeletion();
+ }
+
+ void showContextMenuInResultsTable(MouseEvent e) {
+ if (e.getClickCount() == 2 && !e.isConsumed()) {
+ SearchPanelProvider.this.setupResultsContextMenuPopup().show(e.getComponent(), e.getX(), e.getY());
+ setupResultsContextMenuPopup().show(e.getComponent(), e.getX(), e.getY());
+ }
+ }
+
+ }
+
+ private class Observer implements IndexObserver {
+
+ @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());
+ });
+
+ queryStringTA.setText("*:*");
+ parsedQueryTA.setText("");
+ parseBtn.setEnabled(true);
+ searchBtn.setEnabled(true);
+ mltBtn.setEnabled(true);
+ }
+
+ @Override
+ public void closeIndex() {
+ searchModel = null;
+ toolsModel = null;
+
+ queryStringTA.setText("");
+ parsedQueryTA.setText("");
+ parseBtn.setEnabled(false);
+ searchBtn.setEnabled(false);
+ mltBtn.setEnabled(false);
+ totalHitsLbl.setText("0");
+ startLbl.setText("0");
+ endLbl.setText("0");
+ nextBtn.setEnabled(false);
+ prevBtn.setEnabled(false);
+ delBtn.setEnabled(false);
+ 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);
+
+ private int tabIdx;
+
+ Tab(int tabIdx) {
+ this.tabIdx = tabIdx;
+ }
+
+ int index() {
+ return tabIdx;
+ }
+ }
+
+ static final class SearchResultsTableModel extends TableModelBase<SearchResultsTableModel.Column> {
+
+ enum Column implements TableColumnInfo {
+ DOCID("Doc ID", 0, Integer.class, 50),
+ SCORE("Score", 1, Float.class, 100),
+ VALUE("Field Values", 2, String.class, 800);
+
+ private final String colName;
+ private final int index;
+ private final Class<?> type;
+ private final int width;
+
+ Column(String colName, int index, Class<?> type, int width) {
+ this.colName = colName;
+ this.index = index;
+ this.type = type;
+ this.width = width;
+ }
+
+ @Override
+ public String getColName() {
+ return colName;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public Class<?> getType() {
+ return type;
+ }
+
+ @Override
+ public int getColumnWidth() {
+ return width;
+ }
+ }
+
+ SearchResultsTableModel() {
+ super();
+ }
+
+ SearchResultsTableModel(SearchResults results) {
+ super(results.size());
+ for (int i = 0; i < results.size(); i++) {
+ SearchResults.Doc doc = results.getHits().get(i);
+ data[i][Column.DOCID.getIndex()] = doc.getDocId();
+ if (!Float.isNaN(doc.getScore())) {
+ data[i][Column.SCORE.getIndex()] = doc.getScore();
+ } 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());
+ data[i][Column.VALUE.getIndex()] = String.join(" ", concatValues);
+ }
+ }
+
+ @Override
+ protected Column[] columnInfos() {
+ return Column.values();
+ }
+ }
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/SearchTabOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/SearchTabOperator.java
new file mode 100644
index 0000000..05e7002
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/SearchTabOperator.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components;
+
+/** Operator for the Search tab */
+public interface SearchTabOperator extends ComponentOperatorRegistry.ComponentOperator {
+ void searchByTerm(String field, String term);
+
+ void mltSearch(int docNum);
+
+ void enableExactHitsCB(boolean value);
+
+ void setExactHits(boolean value);
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabSwitcherProxy.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabSwitcherProxy.java
new file mode 100644
index 0000000..42f2194
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabSwitcherProxy.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components;
+
+/** An utility class for switching tabs. */
+public class TabSwitcherProxy {
+
+ private static final TabSwitcherProxy instance = new TabSwitcherProxy();
+
+ private TabSwitcher switcher;
+
+ public static TabSwitcherProxy getInstance() {
+ return instance;
+ }
+
+ public void set(TabSwitcher switcher) {
+ if (this.switcher == null) {
+ this.switcher = switcher;
+ }
+ }
+
+ public void switchTab(TabbedPaneProvider.Tab tab) {
+ if (switcher == null) {
+ throw new IllegalStateException();
+ }
+ switcher.switchTab(tab);
+ }
+
+ /** tab switcher */
+ public interface TabSwitcher {
+ void switchTab(TabbedPaneProvider.Tab tab);
+ }
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabbedPaneProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabbedPaneProvider.java
new file mode 100644
index 0000000..c5fd73a
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabbedPaneProvider.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components;
+
+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;
+import org.apache.lucene.luke.app.IndexObserver;
+import org.apache.lucene.luke.app.LukeState;
+import org.apache.lucene.luke.app.desktop.MessageBroker;
+import org.apache.lucene.luke.app.desktop.util.FontUtils;
+import org.apache.lucene.luke.app.desktop.util.TabUtils;
+
+/** Provider of the Tabbed pane */
+public final class TabbedPaneProvider implements TabSwitcherProxy.TabSwitcher {
+
+ private final MessageBroker messageBroker;
+
+ private final JTabbedPane tabbedPane = new JTabbedPane();
+
+ private final JPanel overviewPanel;
+
+ private final JPanel documentsPanel;
+
+ private final JPanel searchPanel;
+
+ private final JPanel analysisPanel;
+
+ private final JPanel commitsPanel;
+
+ private final JPanel logsPanel;
+
+ public TabbedPaneProvider(JTextArea logTextArea) throws IOException {
+ this.overviewPanel = new OverviewPanelProvider().get();
+ this.documentsPanel = new DocumentsPanelProvider().get();
+ this.searchPanel = new SearchPanelProvider().get();
+ this.analysisPanel = new AnalysisPanelProvider().get();
+ this.commitsPanel = new CommitsPanelProvider().get();
+ this.logsPanel = new LogsPanelProvider(logTextArea).get();
+
+ this.messageBroker = MessageBroker.getInstance();
+
+ TabSwitcherProxy.getInstance().set(this);
+
+ Observer observer = new Observer();
+ IndexHandler.getInstance().addObserver(observer);
+ DirectoryHandler.getInstance().addObserver(observer);
+ }
+
+ public JTabbedPane get() {
+ tabbedPane.addTab(FontUtils.elegantIconHtml("", "Overview"), overviewPanel);
+ tabbedPane.addTab(FontUtils.elegantIconHtml("i", "Documents"), documentsPanel);
+ tabbedPane.addTab(FontUtils.elegantIconHtml("", "Search"), searchPanel);
+ tabbedPane.addTab(FontUtils.elegantIconHtml("", "Analysis"), analysisPanel);
+ tabbedPane.addTab(FontUtils.elegantIconHtml("", "Commits"), commitsPanel);
+ tabbedPane.addTab(FontUtils.elegantIconHtml("", "Logs"), logsPanel);
+
+ TabUtils.forceTransparent(tabbedPane);
+
+ return tabbedPane;
+ }
+
+ public void switchTab(Tab tab) {
+ tabbedPane.setSelectedIndex(tab.index());
+ tabbedPane.setVisible(false);
+ tabbedPane.setVisible(true);
+ messageBroker.clearStatusMessage();
+ }
+
+ private class Observer implements IndexObserver, DirectoryObserver {
+
+ @Override
+ public void openDirectory(LukeState state) {
+ tabbedPane.setEnabledAt(Tab.COMMITS.index(), true);
+ }
+
+ @Override
+ public void closeDirectory() {
+ tabbedPane.setEnabledAt(Tab.OVERVIEW.index(), false);
+ tabbedPane.setEnabledAt(Tab.DOCUMENTS.index(), false);
+ tabbedPane.setEnabledAt(Tab.SEARCH.index(), false);
+ tabbedPane.setEnabledAt(Tab.COMMITS.index(), false);
+ }
+
+ @Override
+ public void openIndex(LukeState state) {
+ tabbedPane.setEnabledAt(Tab.OVERVIEW.index(), true);
+ tabbedPane.setEnabledAt(Tab.DOCUMENTS.index(), true);
+ tabbedPane.setEnabledAt(Tab.SEARCH.index(), true);
+ tabbedPane.setEnabledAt(Tab.COMMITS.index(), true);
+ }
+
+ @Override
+ public void closeIndex() {
+ tabbedPane.setEnabledAt(Tab.OVERVIEW.index(), false);
+ tabbedPane.setEnabledAt(Tab.DOCUMENTS.index(), false);
+ tabbedPane.setEnabledAt(Tab.SEARCH.index(), false);
+ tabbedPane.setEnabledAt(Tab.COMMITS.index(), false);
+ }
+ }
+
+ /** tabs in the main frame */
+ public enum Tab {
+ OVERVIEW(0), DOCUMENTS(1), SEARCH(2), ANALYZER(3), COMMITS(4);
+
+ private int tabIdx;
+
+ Tab(int tabIdx) {
+ this.tabIdx = tabIdx;
+ }
+
+ int index() {
+ return tabIdx;
+ }
+ }
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TableColumnInfo.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TableColumnInfo.java
new file mode 100644
index 0000000..63cdbb1
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TableColumnInfo.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components;
+
+/** Holder of table column attributes */
+public interface TableColumnInfo {
+
+ String getColName();
+
+ int getIndex();
+
+ Class<?> getType();
+
+ default int getColumnWidth() {
+ return 0;
+ }
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TableModelBase.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TableModelBase.java
new file mode 100644
index 0000000..f8ef21a
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TableModelBase.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components;
+
+import javax.swing.table.AbstractTableModel;
+import java.util.Map;
+
+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. */
+public abstract class TableModelBase<T extends TableColumnInfo> extends AbstractTableModel {
+
+ private final Map<Integer, T> columnMap = TableUtils.columnMap(columnInfos());
+
+ private final String[] colNames = TableUtils.columnNames(columnInfos());
+
+ protected final Object[][] data;
+
+ protected TableModelBase() {
+ this.data = new Object[0][colNames.length];
+ }
+
+ protected TableModelBase(int rows) {
+ this.data = new Object[rows][colNames.length];
+ }
+
+ protected abstract T[] columnInfos();
+
+ @Override
+ public int getRowCount() {
+ return data.length;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return colNames.length;
+ }
+
+ @Override
+ public String getColumnName(int colIndex) {
+ if (columnMap.containsKey(colIndex)) {
+ return columnMap.get(colIndex).getColName();
+ }
+ return "";
+ }
+
+ @Override
+ public Class<?> getColumnClass(int colIndex) {
+ if (columnMap.containsKey(colIndex)) {
+ return columnMap.get(colIndex).getType();
+ }
+ return Object.class;
+ }
+
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ return data[rowIndex][columnIndex];
+ }
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/ConfirmDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/ConfirmDialogFactory.java
new file mode 100644
index 0000000..d546598
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/ConfirmDialogFactory.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.Window;
+import java.io.IOException;
+
+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;
+import org.apache.lucene.luke.app.desktop.util.FontUtils;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+import org.apache.lucene.luke.app.desktop.util.lang.Callable;
+
+/** Factory of confirm dialog */
+public final class ConfirmDialogFactory implements DialogOpener.DialogFactory {
+
+ private static ConfirmDialogFactory instance;
+
+ private final Preferences prefs;
+
+ private JDialog dialog;
+
+ private String message;
+
+ private Callable callback;
+
+ public synchronized static ConfirmDialogFactory getInstance() throws IOException {
+ if (instance == null) {
+ instance = new ConfirmDialogFactory();
+ }
+ return instance;
+ }
+
+ private ConfirmDialogFactory() throws IOException {
+ this.prefs = PreferencesFactory.getInstance();
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public void setCallback(Callable callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public JDialog create(Window owner, String title, int width, int height) {
+ dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL);
+ dialog.add(content());
+ dialog.setSize(new Dimension(width, height));
+ dialog.setLocationRelativeTo(owner);
+ dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor());
+ return dialog;
+ }
+
+ private JPanel content() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
+
+ JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ header.setOpaque(false);
+ JLabel alertIconLbl = new JLabel(FontUtils.elegantIconHtml("q"));
+ alertIconLbl.setHorizontalAlignment(JLabel.CENTER);
+ alertIconLbl.setFont(new Font(alertIconLbl.getFont().getFontName(), Font.PLAIN, 25));
+ header.add(alertIconLbl);
+ panel.add(header, BorderLayout.PAGE_START);
+
+ JPanel center = new JPanel(new GridLayout(1, 1));
+ center.setOpaque(false);
+ center.setBorder(BorderFactory.createLineBorder(Color.gray, 3));
+ center.add(new JLabel(message, JLabel.CENTER));
+ panel.add(center, BorderLayout.CENTER);
+
+ 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();
+ });
+ footer.add(okBtn);
+ JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close"));
+ closeBtn.addActionListener(e -> dialog.dispose());
+ footer.add(closeBtn);
+ panel.add(footer, BorderLayout.PAGE_END);
+
+ return panel;
+ }
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/HelpDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/HelpDialogFactory.java
new file mode 100644
index 0000000..b9bcf9d
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/HelpDialogFactory.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.Window;
+import java.io.IOException;
+
+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;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+
+/** Factory of help dialog */
+public final class HelpDialogFactory implements DialogOpener.DialogFactory {
+
+ private static HelpDialogFactory instance;
+
+ private final Preferences prefs;
+
+ private JDialog dialog;
+
+ private String desc;
+
+ private JComponent helpContent;
+
+ public synchronized static HelpDialogFactory getInstance() throws IOException {
+ if (instance == null) {
+ instance = new HelpDialogFactory();
+ }
+ return instance;
+ }
+
+ private HelpDialogFactory() throws IOException {
+ this.prefs = PreferencesFactory.getInstance();
+ }
+
+ public void setDesc(String desc) {
+ this.desc = desc;
+ }
+
+ public void setContent(JComponent helpContent) {
+ this.helpContent = helpContent;
+ }
+
+ @Override
+ public JDialog create(Window owner, String title, int width, int height) {
+ dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL);
+ dialog.add(content());
+ dialog.setSize(new Dimension(width, height));
+ dialog.setLocationRelativeTo(owner);
+ dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor());
+ return dialog;
+ }
+
+ private JPanel content() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
+
+ JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ header.setOpaque(false);
+ header.add(new JLabel(desc));
+ panel.add(header, BorderLayout.PAGE_START);
+
+ JPanel center = new JPanel(new GridLayout(1, 1));
+ center.setOpaque(false);
+ center.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ center.add(helpContent);
+ panel.add(center, BorderLayout.CENTER);
+
+ JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING));
+ footer.setOpaque(false);
+ JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close"));
+ closeBtn.addActionListener(e -> dialog.dispose());
+ footer.add(closeBtn);
+ panel.add(footer, BorderLayout.PAGE_END);
+
+ return panel;
+ }
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/AnalysisChainDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/AnalysisChainDialogFactory.java
new file mode 100644
index 0000000..31fce6d
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/AnalysisChainDialogFactory.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Window;
+import java.io.IOException;
+
+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;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+
+/** Factory of analysis chain dialog */
+public class AnalysisChainDialogFactory implements DialogOpener.DialogFactory {
+
+ private static AnalysisChainDialogFactory instance;
+
+ private final Preferences prefs;
+
+ private JDialog dialog;
+
+ private CustomAnalyzer analyzer;
+
+ public synchronized static AnalysisChainDialogFactory getInstance() throws IOException {
+ if (instance == null) {
+ instance = new AnalysisChainDialogFactory();
+ }
+ return instance;
+ }
+
+ private AnalysisChainDialogFactory() throws IOException {
+ this.prefs = PreferencesFactory.getInstance();
+ }
+
+ public void setAnalyzer(CustomAnalyzer analyzer) {
+ this.analyzer = analyzer;
+ }
+
+ @Override
+ public JDialog create(Window owner, String title, int width, int height) {
+ dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL);
+ dialog.add(content());
+ dialog.setSize(new Dimension(width, height));
+ dialog.setLocationRelativeTo(owner);
+ dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor());
+ return dialog;
+ }
+
+ private JPanel content() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+
+ panel.add(analysisChain(), BorderLayout.PAGE_START);
+
+ JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING, 10, 5));
+ footer.setOpaque(false);
+ JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close"));
+ closeBtn.addActionListener(e -> dialog.dispose());
+ footer.add(closeBtn);
+ panel.add(footer, BorderLayout.PAGE_END);
+
+ return panel;
+ }
+
+ private JPanel analysisChain() {
+ JPanel panel = new JPanel(new GridBagLayout());
+ panel.setOpaque(false);
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.insets = new Insets(5, 5, 5, 5);
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0.1;
+ c.weighty = 0.5;
+ panel.add(new JLabel(MessageUtils.getLocalizedMessage("analysis.dialog.chain.label.charfilters")), c);
+
+ String[] charFilters = analyzer.getCharFilterFactories().stream().map(f -> f.getClass().getName()).toArray(String[]::new);
+ JList<String> charFilterList = new JList<>(charFilters);
+ charFilterList.setVisibleRowCount(charFilters.length == 0 ? 1 : Math.min(charFilters.length, 5));
+ c.gridx = 1;
+ c.gridy = 0;
+ c.weightx = 0.5;
+ c.weighty = 0.5;
+ panel.add(new JScrollPane(charFilterList), c);
+
+ c.gridx = 0;
+ c.gridy = 1;
+ c.weightx = 0.1;
+ c.weighty = 0.1;
+ panel.add(new JLabel(MessageUtils.getLocalizedMessage("analysis.dialog.chain.label.tokenizer")), c);
+
+ String tokenizer = analyzer.getTokenizerFactory().getClass().getName();
+ JTextField tokenizerTF = new JTextField(tokenizer);
+ tokenizerTF.setColumns(30);
+ tokenizerTF.setEditable(false);
+ tokenizerTF.setPreferredSize(new Dimension(300, 25));
+ tokenizerTF.setBorder(BorderFactory.createLineBorder(Color.gray));
+ c.gridx = 1;
+ c.gridy = 1;
+ c.weightx = 0.5;
+ c.weighty = 0.1;
+ panel.add(tokenizerTF, c);
+
+ c.gridx = 0;
+ c.gridy = 2;
+ c.weightx = 0.1;
+ c.weighty = 0.5;
+ panel.add(new JLabel(MessageUtils.getLocalizedMessage("analysis.dialog.chain.label.tokenfilters")), c);
+
+ String[] tokenFilters = analyzer.getTokenFilterFactories().stream().map(f -> f.getClass().getName()).toArray(String[]::new);
+ JList<String> tokenFilterList = new JList<>(tokenFilters);
+ tokenFilterList.setVisibleRowCount(tokenFilters.length == 0 ? 1 : Math.min(tokenFilters.length, 5));
+ tokenFilterList.setMinimumSize(new Dimension(300, 25));
+ c.gridx = 1;
+ c.gridy = 2;
+ c.weightx = 0.5;
+ c.weighty = 0.5;
+ panel.add(new JScrollPane(tokenFilterList), c);
+
+ return panel;
+ }
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersDialogFactory.java
new file mode 100644
index 0000000..5a964d6
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersDialogFactory.java
@@ -0,0 +1,303 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Window;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+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;
+import org.apache.lucene.luke.app.desktop.components.TableColumnInfo;
+import org.apache.lucene.luke.app.desktop.components.TableModelBase;
+import org.apache.lucene.luke.app.desktop.components.fragments.analysis.CustomAnalyzerPanelOperator;
+import org.apache.lucene.luke.app.desktop.util.DialogOpener;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+import org.apache.lucene.luke.app.desktop.util.TableUtils;
+import org.apache.lucene.luke.app.desktop.util.lang.Callable;
+
+/** Factory of edit filters dialog */
+public final class EditFiltersDialogFactory implements DialogOpener.DialogFactory {
+
+ private static EditFiltersDialogFactory instance;
+
+ private final Preferences prefs;
+
+ private final ComponentOperatorRegistry operatorRegistry;
+
+ private final EditParamsDialogFactory editParamsDialogFactory;
+
+ private final JLabel targetLbl = new JLabel();
+
+ private final JTable filtersTable = new JTable();
+
+ private final ListenerFunctions listeners = new ListenerFunctions();
+
+ private final FiltersTableMouseListener tableListener = new FiltersTableMouseListener();
+
+ private JDialog dialog;
+
+ private List<String> selectedFilters;
+
+ private Callable callback;
+
+ private EditFiltersMode mode;
+
+ public synchronized static EditFiltersDialogFactory getInstance() throws IOException {
+ if (instance == null) {
+ instance = new EditFiltersDialogFactory();
+ }
+ return instance;
+ }
+
+ private EditFiltersDialogFactory() throws IOException {
+ this.prefs = PreferencesFactory.getInstance();
+ this.operatorRegistry = ComponentOperatorRegistry.getInstance();
+ this.editParamsDialogFactory = EditParamsDialogFactory.getInstance();
+ }
+
+ public void setSelectedFilters(List<String> selectedFilters) {
+ this.selectedFilters = selectedFilters;
+ }
+
+ public void setCallback(Callable callback) {
+ this.callback = callback;
+ }
+
+ public void setMode(EditFiltersMode mode) {
+ this.mode = mode;
+ }
+
+ @Override
+ public JDialog create(Window owner, String title, int width, int height) {
+ dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL);
+ dialog.add(content());
+ dialog.setSize(new Dimension(width, height));
+ dialog.setLocationRelativeTo(owner);
+ dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor());
+ return dialog;
+ }
+
+ private JPanel content() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+
+ JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 10));
+ header.setOpaque(false);
+ header.add(new JLabel(MessageUtils.getLocalizedMessage("analysis.dialog.hint.edit_param")));
+ header.add(targetLbl);
+ panel.add(header, BorderLayout.PAGE_START);
+
+ 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());
+ 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();
+ });
+ footer.add(okBtn);
+ JButton cancelBtn = new JButton(MessageUtils.getLocalizedMessage("button.cancel"));
+ cancelBtn.addActionListener(e -> dialog.dispose());
+ footer.add(cancelBtn);
+ panel.add(footer, BorderLayout.PAGE_END);
+
+ return panel;
+ }
+
+ private class ListenerFunctions {
+
+ void showEditParamsDialog(MouseEvent e) {
+ if (e.getClickCount() != 2 || e.isConsumed()) {
+ return;
+ }
+ int selectedIndex = filtersTable.rowAtPoint(e.getPoint());
+ if (selectedIndex < 0 || selectedIndex >= selectedFilters.size()) {
+ return;
+ }
+
+ switch (mode) {
+ case CHARFILTER:
+ showEditParamsCharFilterDialog(selectedIndex);
+ break;
+ case TOKENFILTER:
+ showEditParamsTokenFilterDialog(selectedIndex);
+ break;
+ default:
+ }
+ }
+
+ 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);
+ });
+ }
+
+ 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);
+ });
+ }
+ }
+
+ private class FiltersTableMouseListener extends MouseAdapter {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ listeners.showEditParamsDialog(e);
+ }
+ }
+
+ static final class FiltersTableModel extends TableModelBase<FiltersTableModel.Column> {
+
+ enum Column implements TableColumnInfo {
+ DELETE("Delete", 0, Boolean.class, 50),
+ ORDER("Order", 1, Integer.class, 50),
+ TYPE("Factory class", 2, String.class, Integer.MAX_VALUE);
+
+ private final String colName;
+ private final int index;
+ private final Class<?> type;
+ private final int width;
+
+ Column(String colName, int index, Class<?> type, int width) {
+ this.colName = colName;
+ this.index = index;
+ this.type = type;
+ this.width = width;
+ }
+
+ @Override
+ public String getColName() {
+ return colName;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public Class<?> getType() {
+ return type;
+ }
+
+ @Override
+ public int getColumnWidth() {
+ return width;
+ }
+ }
+
+ FiltersTableModel() {
+ super();
+ }
+
+ FiltersTableModel(List<String> selectedFilters) {
+ super(selectedFilters.size());
+ for (int i = 0; i < selectedFilters.size(); i++) {
+ data[i][Column.DELETE.getIndex()] = false;
+ data[i][Column.ORDER.getIndex()] = i + 1;
+ data[i][Column.TYPE.getIndex()] = selectedFilters.get(i);
+ }
+ }
+
+ @Override
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ return columnIndex == Column.DELETE.getIndex();
+ }
+
+ @Override
+ public void setValueAt(Object value, int rowIndex, int columnIndex) {
+ data[rowIndex][columnIndex] = value;
+ }
+
+ @Override
+ protected Column[] columnInfos() {
+ return Column.values();
+ }
+ }
+
+ static final class TypeCellRenderer implements TableCellRenderer {
+
+ @Override
+ 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);
+ }
+
+ }
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersMode.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersMode.java
new file mode 100644
index 0000000..d5edd8b
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersMode.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components.dialog.analysis;
+
+/** Edit filters mode */
+public enum EditFiltersMode {
+ CHARFILTER, TOKENFILTER;
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditParamsDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditParamsDialogFactory.java
new file mode 100644
index 0000000..f9a30da
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditParamsDialogFactory.java
@@ -0,0 +1,254 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+import java.awt.FlowLayout;
+import java.awt.Window;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+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;
+import org.apache.lucene.luke.app.desktop.components.TableColumnInfo;
+import org.apache.lucene.luke.app.desktop.components.TableModelBase;
+import org.apache.lucene.luke.app.desktop.components.fragments.analysis.CustomAnalyzerPanelOperator;
+import org.apache.lucene.luke.app.desktop.util.DialogOpener;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+import org.apache.lucene.luke.app.desktop.util.TableUtils;
+import org.apache.lucene.luke.app.desktop.util.lang.Callable;
+
+/** Factory of edit parameters dialog */
+public final class EditParamsDialogFactory implements DialogOpener.DialogFactory {
+
+ private static EditParamsDialogFactory instance;
+
+ private final Preferences prefs;
+
+ private final ComponentOperatorRegistry operatorRegistry;
+
+ private final JTable paramsTable = new JTable();
+
+ private JDialog dialog;
+
+ private EditParamsMode mode;
+
+ private String target;
+
+ private int targetIndex;
+
+ private Map<String, String> params = new HashMap<>();
+
+ private Callable callback;
+
+ public synchronized static EditParamsDialogFactory getInstance() throws IOException {
+ if (instance == null) {
+ instance = new EditParamsDialogFactory();
+ }
+ return instance;
+ }
+
+ private EditParamsDialogFactory() throws IOException {
+ this.prefs = PreferencesFactory.getInstance();
+ this.operatorRegistry = ComponentOperatorRegistry.getInstance();
+ }
+
+ public void setMode(EditParamsMode mode) {
+ this.mode = mode;
+ }
+
+ public void setTarget(String target) {
+ this.target = target;
+ }
+
+ public void setTargetIndex(int targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ public void setParams(Map<String, String> params) {
+ this.params.putAll(params);
+ }
+
+ public void setCallback(Callable callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public JDialog create(Window owner, String title, int width, int height) {
+ dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL);
+ dialog.add(content());
+ dialog.setSize(new Dimension(width, height));
+ dialog.setLocationRelativeTo(owner);
+ dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor());
+ return dialog;
+ }
+
+ private JPanel content() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+
+ JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 10));
+ header.setOpaque(false);
+ header.add(new JLabel("Parameters for:"));
+ String[] tmp = target.split("\\.");
+ JLabel targetLbl = new JLabel(tmp[tmp.length - 1]);
+ header.add(targetLbl);
+ panel.add(header, BorderLayout.PAGE_START);
+
+ TableUtils.setupTable(paramsTable, ListSelectionModel.SINGLE_SELECTION, new ParamsTableModel(params), null,
+ ParamsTableModel.Column.DELETE.getColumnWidth(),
+ ParamsTableModel.Column.NAME.getColumnWidth());
+ paramsTable.setShowGrid(true);
+ panel.add(new JScrollPane(paramsTable), 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 -> {
+ 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();
+ });
+ footer.add(cancelBtn);
+ panel.add(footer, BorderLayout.PAGE_END);
+
+ return panel;
+ }
+
+ 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;
+ }
+ });
+ }
+
+ static final class ParamsTableModel extends TableModelBase<ParamsTableModel.Column> {
+
+ enum Column implements TableColumnInfo {
+ DELETE("Delete", 0, Boolean.class, 50),
+ NAME("Name", 1, String.class, 150),
+ VALUE("Value", 2, String.class, Integer.MAX_VALUE);
+
+ private final String colName;
+ private final int index;
+ private final Class<?> type;
+ private final int width;
+
+ Column(String colName, int index, Class<?> type, int width) {
+ this.colName = colName;
+ this.index = index;
+ this.type = type;
+ this.width = width;
+ }
+
+ @Override
+ public String getColName() {
+ return colName;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public Class<?> getType() {
+ return type;
+ }
+
+ @Override
+ public int getColumnWidth() {
+ return width;
+ }
+
+ }
+
+ private static final int PARAM_SIZE = 20;
+
+ ParamsTableModel(Map<String, String> params) {
+ super(PARAM_SIZE);
+ List<String> keys = new ArrayList<>(params.keySet());
+ for (int i = 0; i < keys.size(); i++) {
+ data[i][Column.NAME.getIndex()] = keys.get(i);
+ data[i][Column.VALUE.getIndex()] = params.get(keys.get(i));
+ }
+ for (int i = 0; i < data.length; i++) {
+ data[i][Column.DELETE.getIndex()] = false;
+ }
+ }
+
+ @Override
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ return true;
+ }
+
+ @Override
+ public void setValueAt(Object value, int rowIndex, int columnIndex) {
+ data[rowIndex][columnIndex] = value;
+ }
+
+ @Override
+ protected Column[] columnInfos() {
+ return Column.values();
+ }
+ }
+
+}
+
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditParamsMode.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditParamsMode.java
new file mode 100644
index 0000000..8e76879
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditParamsMode.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components.dialog.analysis;
+
+/** Edit parameters mode */
+public enum EditParamsMode {
+ CHARFILTER, TOKENIZER, TOKENFILTER;
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/TokenAttributeDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/TokenAttributeDialogFactory.java
new file mode 100644
index 0000000..4112699
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/TokenAttributeDialogFactory.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+import java.awt.FlowLayout;
+import java.awt.Window;
+import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+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;
+import org.apache.lucene.luke.app.desktop.components.TableModelBase;
+import org.apache.lucene.luke.app.desktop.util.DialogOpener;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+import org.apache.lucene.luke.app.desktop.util.TableUtils;
+import org.apache.lucene.luke.models.analysis.Analysis;
+
+/** Factory of token attribute dialog */
+public final class TokenAttributeDialogFactory implements DialogOpener.DialogFactory {
+
+ private static TokenAttributeDialogFactory instance;
+
+ private final Preferences prefs;
+
+ private final JTable attributesTable = new JTable();
+
+ private JDialog dialog;
+
+ private String term;
+
+ private List<Analysis.TokenAttribute> attributes;
+
+ public synchronized static TokenAttributeDialogFactory getInstance() throws IOException {
+ if (instance == null) {
+ instance = new TokenAttributeDialogFactory();
+ }
+ return instance;
+ }
+
+ private TokenAttributeDialogFactory() throws IOException {
+ this.prefs = PreferencesFactory.getInstance();
+ }
+
+ public void setTerm(String term) {
+ this.term = term;
+ }
+
+ public void setAttributes(List<Analysis.TokenAttribute> attributes) {
+ this.attributes = attributes;
+ }
+
+ @Override
+ public JDialog create(Window owner, String title, int width, int height) {
+ dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL);
+ dialog.add(content());
+ dialog.setSize(new Dimension(width, height));
+ dialog.setLocationRelativeTo(owner);
+ dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor());
+ return dialog;
+ }
+
+ private JPanel content() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
+
+ JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ header.setOpaque(false);
+ header.add(new JLabel("All token attributes for:"));
+ 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);
+ panel.add(new JScrollPane(attributesTable), BorderLayout.CENTER);
+
+ JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING));
+ footer.setOpaque(false);
+ JButton okBtn = new JButton(MessageUtils.getLocalizedMessage("button.ok"));
+ okBtn.addActionListener(e -> dialog.dispose());
+ footer.add(okBtn);
+ panel.add(footer, BorderLayout.PAGE_END);
+
+ return panel;
+ }
+
+ 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);
+
+ private final String colName;
+ private final int index;
+ private final Class<?> type;
+
+ Column(String colName, int index, Class<?> type) {
+ this.colName = colName;
+ this.index = index;
+ this.type = type;
+ }
+
+ @Override
+ public String getColName() {
+ return colName;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public Class<?> getType() {
+ return type;
+ }
+ }
+
+ AttributeTableModel(List<TokenAttValue> attrValues) {
+ super(attrValues.size());
+ for (int i = 0; i < attrValues.size(); i++) {
+ TokenAttValue attrValue = attrValues.get(i);
+ data[i][Column.ATTR.getIndex()] = attrValue.getAttClass();
+ data[i][Column.NAME.getIndex()] = attrValue.getName();
+ data[i][Column.VALUE.getIndex()] = attrValue.getValue();
+ }
+ }
+
+ @Override
+ protected Column[] columnInfos() {
+ return Column.values();
+ }
+ }
+
+ static final class TokenAttValue {
+ private String attClass;
+ private String name;
+ private String value;
+
+ public static TokenAttValue of(String attClass, String name, String value) {
+ TokenAttValue attValue = new TokenAttValue();
+ attValue.attClass = attClass;
+ attValue.name = name;
+ attValue.value = value;
+ return attValue;
+ }
+
+ private TokenAttValue() {
+ }
+
+ String getAttClass() {
+ return attClass;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ String getValue() {
+ return value;
+ }
+ }
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/package-info.java
new file mode 100644
index 0000000..bd3419b
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** Dialogs used in the Analysis tab */
+package org.apache.lucene.luke.app.desktop.components.dialog.analysis;
\ No newline at end of file
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogFactory.java
new file mode 100644
index 0000000..0bbeb3e
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogFactory.java
@@ -0,0 +1,593 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+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.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Constructor;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.DoublePoint;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FloatPoint;
+import org.apache.lucene.document.IntPoint;
+import org.apache.lucene.document.LongPoint;
+import org.apache.lucene.document.NumericDocValuesField;
+import org.apache.lucene.document.SortedDocValuesField;
+import org.apache.lucene.document.SortedNumericDocValuesField;
+import org.apache.lucene.document.SortedSetDocValuesField;
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.document.TextField;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.index.IndexableFieldType;
+import org.apache.lucene.luke.app.IndexHandler;
+import org.apache.lucene.luke.app.IndexObserver;
+import org.apache.lucene.luke.app.LukeState;
+import org.apache.lucene.luke.app.desktop.Preferences;
+import org.apache.lucene.luke.app.desktop.PreferencesFactory;
+import org.apache.lucene.luke.app.desktop.components.AnalysisTabOperator;
+import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry;
+import org.apache.lucene.luke.app.desktop.components.DocumentsTabOperator;
+import org.apache.lucene.luke.app.desktop.components.TabSwitcherProxy;
+import org.apache.lucene.luke.app.desktop.components.TabbedPaneProvider;
+import org.apache.lucene.luke.app.desktop.components.TableColumnInfo;
+import org.apache.lucene.luke.app.desktop.components.TableModelBase;
+import org.apache.lucene.luke.app.desktop.components.dialog.HelpDialogFactory;
+import org.apache.lucene.luke.app.desktop.dto.documents.NewField;
+import org.apache.lucene.luke.app.desktop.util.DialogOpener;
+import org.apache.lucene.luke.app.desktop.util.FontUtils;
+import org.apache.lucene.luke.app.desktop.util.HelpHeaderRenderer;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+import org.apache.lucene.luke.app.desktop.util.NumericUtils;
+import org.apache.lucene.luke.app.desktop.util.StringUtils;
+import org.apache.lucene.luke.app.desktop.util.TableUtils;
+import org.apache.lucene.luke.models.LukeException;
+import org.apache.lucene.luke.models.tools.IndexTools;
+import org.apache.lucene.luke.models.tools.IndexToolsFactory;
+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 {
+
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private static AddDocumentDialogFactory instance;
+
+ private final static int ROW_COUNT = 50;
+
+ private final Preferences prefs;
+
+ private final IndexHandler indexHandler;
+
+ private final IndexToolsFactory toolsFactory = new IndexToolsFactory();
+
+ private final TabSwitcherProxy tabSwitcher;
+
+ private final ComponentOperatorRegistry operatorRegistry;
+
+ private final IndexOptionsDialogFactory indexOptionsDialogFactory;
+
+ private final HelpDialogFactory helpDialogFactory;
+
+ private final ListenerFunctions listeners = new ListenerFunctions();
+
+ private final JLabel analyzerNameLbl = new JLabel(StandardAnalyzer.class.getName());
+
+ private final List<NewField> newFieldList;
+
+ private final JButton addBtn = new JButton();
+
+ private final JButton closeBtn = new JButton();
+
+ private final JTextArea infoTA = new JTextArea();
+
+ private IndexTools toolsModel;
+
+ private JDialog dialog;
+
+ public synchronized static AddDocumentDialogFactory getInstance() throws IOException {
+ if (instance == null) {
+ instance = new AddDocumentDialogFactory();
+ }
+ return instance;
+ }
+
+ private AddDocumentDialogFactory() throws IOException {
+ this.prefs = PreferencesFactory.getInstance();
+ this.indexHandler = IndexHandler.getInstance();
+ this.tabSwitcher = TabSwitcherProxy.getInstance();
+ 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());
+
+ operatorRegistry.register(AddDocumentDialogOperator.class, this);
+ indexHandler.addObserver(new Observer());
+
+ initialize();
+ }
+
+ private void initialize() {
+ addBtn.setText(MessageUtils.getLocalizedMessage("add_document.button.add"));
+ addBtn.setMargin(new Insets(3, 3, 3, 3));
+ addBtn.setEnabled(true);
+ addBtn.addActionListener(listeners::addDocument);
+
+ closeBtn.setText(MessageUtils.getLocalizedMessage("button.cancel"));
+ closeBtn.setMargin(new Insets(3, 3, 3, 3));
+ closeBtn.addActionListener(e -> dialog.dispose());
+
+ infoTA.setRows(3);
+ infoTA.setLineWrap(true);
+ infoTA.setEditable(false);
+ infoTA.setText(MessageUtils.getLocalizedMessage("add_document.info"));
+ infoTA.setForeground(Color.gray);
+ }
+
+ @Override
+ public JDialog create(Window owner, String title, int width, int height) {
+ dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL);
+ dialog.add(content());
+ dialog.setSize(new Dimension(width, height));
+ dialog.setLocationRelativeTo(owner);
+ dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor());
+ return dialog;
+ }
+
+ private JPanel content() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
+ panel.add(header(), BorderLayout.PAGE_START);
+ panel.add(center(), BorderLayout.CENTER);
+ panel.add(footer(), BorderLayout.PAGE_END);
+ return panel;
+ }
+
+ private JPanel header() {
+ JPanel panel = new JPanel();
+ panel.setOpaque(false);
+ panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
+
+ JPanel analyzerHeader = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 10));
+ 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);
+ }
+ });
+ analyzerHeader.add(FontUtils.toLinkText(changeLbl));
+ panel.add(analyzerHeader);
+
+ return panel;
+ }
+
+ private JPanel center() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+
+ JPanel tableHeader = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 5));
+ tableHeader.setOpaque(false);
+ tableHeader.add(new JLabel(MessageUtils.getLocalizedMessage("add_document.label.fields")));
+ panel.add(tableHeader, BorderLayout.PAGE_START);
+
+ JScrollPane scrollPane = new JScrollPane(fieldsTable());
+ scrollPane.setOpaque(false);
+ scrollPane.getViewport().setOpaque(false);
+ panel.add(scrollPane, BorderLayout.CENTER);
+
+ JPanel tableFooter = new JPanel(new FlowLayout(FlowLayout.TRAILING, 10, 5));
+ tableFooter.setOpaque(false);
+ addBtn.setEnabled(true);
+ tableFooter.add(addBtn);
+ tableFooter.add(closeBtn);
+ panel.add(tableFooter, BorderLayout.PAGE_END);
+
+ return panel;
+ }
+
+ private JTable fieldsTable() {
+ JTable fieldsTable = new JTable();
+ 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));
+ for (int i = 0; i < fieldsTable.getModel().getRowCount(); i++) {
+ 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));
+ return fieldsTable;
+ }
+
+ private JComponent createTypeHelpDialog() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+
+ JTextArea descTA = new JTextArea();
+
+ 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"
+ };
+ 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));
+ });
+ wrapper1.add(typeCombo);
+ header.add(wrapper1);
+ JPanel wrapper2 = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ wrapper2.setOpaque(false);
+ wrapper2.add(new JLabel("Brief description and Examples"));
+ header.add(wrapper2);
+ panel.add(header, BorderLayout.PAGE_START);
+
+ descTA.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ descTA.setEditable(false);
+ descTA.setLineWrap(true);
+ descTA.setRows(10);
+ descTA.setText(MessageUtils.getLocalizedMessage("help.fieldtype." + typeList[0]));
+ JScrollPane scrollPane = new JScrollPane(descTA);
+ panel.add(scrollPane, BorderLayout.CENTER);
+
+ return panel;
+ }
+
+ private JPanel footer() {
+ JPanel panel = new JPanel(new GridLayout(1, 1));
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+
+ JScrollPane scrollPane = new JScrollPane(infoTA);
+ scrollPane.setOpaque(false);
+ scrollPane.getViewport().setOpaque(false);
+ panel.add(scrollPane);
+ return panel;
+ }
+
+ @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
+ };
+
+ @Override
+ public void setAnalyzer(Analyzer analyzer) {
+ analyzerNameLbl.setText(analyzer.getClass().getName());
+ }
+
+ 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());
+ if (validFields.isEmpty()) {
+ infoTA.setText("Please add one or more fields. Name and Value are both required.");
+ return;
+ }
+
+ Document doc = new Document();
+ try {
+ for (NewField nf : validFields) {
+ doc.add(toIndexableField(nf));
+ }
+ } catch (NumberFormatException ex) {
+ log.error(ex.getMessage(), e);
+ throw new LukeException("Invalid value: " + ex.getMessage(), ex);
+ } catch (Exception ex) {
+ log.error(ex.getMessage(), e);
+ throw new LukeException(ex.getMessage(), ex);
+ }
+
+ addDocument(doc);
+ log.info("Added document: {}", doc.toString());
+ }
+
+ @SuppressWarnings("unchecked")
+ private IndexableField toIndexableField(NewField nf) throws Exception {
+ final Constructor<? extends IndexableField> constr;
+ if (nf.getType().equals(TextField.class) || nf.getType().equals(StringField.class)) {
+ Field.Store store = nf.isStored() ? Field.Store.YES : Field.Store.NO;
+ constr = nf.getType().getConstructor(String.class, String.class, Field.Store.class);
+ return constr.newInstance(nf.getName(), nf.getValue(), store);
+ } else if (nf.getType().equals(IntPoint.class)) {
+ constr = nf.getType().getConstructor(String.class, int[].class);
+ int[] values = NumericUtils.convertToIntArray(nf.getValue(), false);
+ return constr.newInstance(nf.getName(), values);
+ } else if (nf.getType().equals(LongPoint.class)) {
+ constr = nf.getType().getConstructor(String.class, long[].class);
+ long[] values = NumericUtils.convertToLongArray(nf.getValue(), false);
+ return constr.newInstance(nf.getName(), values);
+ } else if (nf.getType().equals(FloatPoint.class)) {
+ constr = nf.getType().getConstructor(String.class, float[].class);
+ float[] values = NumericUtils.convertToFloatArray(nf.getValue(), false);
+ return constr.newInstance(nf.getName(), values);
+ } else if (nf.getType().equals(DoublePoint.class)) {
+ 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)) {
+ 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)) {
+ constr = nf.getType().getConstructor(String.class, long.class);
+ long value = NumericUtils.tryConvertToLongValue(nf.getValue());
+ return constr.newInstance(nf.getName(), value);
+ } else if (nf.getType().equals(StoredField.class)) {
+ constr = nf.getType().getConstructor(String.class, String.class);
+ return constr.newInstance(nf.getName(), nf.getValue());
+ } else if (nf.getType().equals(Field.class)) {
+ constr = nf.getType().getConstructor(String.class, String.class, IndexableFieldType.class);
+ return constr.newInstance(nf.getName(), nf.getValue(), nf.getFieldType());
+ } else {
+ // TODO: unknown field
+ return new StringField(nf.getName(), nf.getValue(), Field.Store.YES);
+ }
+ }
+
+ private void addDocument(Document doc) {
+ try {
+ 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);
+ tabSwitcher.switchTab(TabbedPaneProvider.Tab.DOCUMENTS);
+ infoTA.setText(MessageUtils.getLocalizedMessage("add_document.message.success"));
+ addBtn.setEnabled(false);
+ closeBtn.setText(MessageUtils.getLocalizedMessage("button.close"));
+ } catch (LukeException e) {
+ infoTA.setText(MessageUtils.getLocalizedMessage("add_document.message.fail"));
+ throw e;
+ } catch (Exception e) {
+ infoTA.setText(MessageUtils.getLocalizedMessage("add_document.message.fail"));
+ 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());
+ }
+
+ @Override
+ public void closeIndex() {
+ toolsModel = null;
+ }
+ }
+
+ static final class FieldsTableModel extends TableModelBase<FieldsTableModel.Column> {
+
+ enum Column implements TableColumnInfo {
+ DEL("Del", 0, Boolean.class),
+ NAME("Name", 1, String.class),
+ TYPE("Type", 2, Class.class),
+ OPTIONS("Options", 3, String.class),
+ VALUE("Value", 4, String.class);
+
+ private String colName;
+ private int index;
+ private Class<?> type;
+
+ Column(String colName, int index, Class<?> type) {
+ this.colName = colName;
+ this.index = index;
+ this.type = type;
+ }
+
+ @Override
+ public String getColName() {
+ return colName;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public Class<?> getType() {
+ return type;
+ }
+
+ }
+
+ private final List<NewField> newFieldList;
+
+ FieldsTableModel(List<NewField> newFieldList) {
+ super(newFieldList.size());
+ this.newFieldList = newFieldList;
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ if (columnIndex == Column.OPTIONS.getIndex()) {
+ return "";
+ }
+ return data[rowIndex][columnIndex];
+ }
+
+ @Override
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ return columnIndex != Column.OPTIONS.getIndex();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void setValueAt(Object value, int rowIndex, int columnIndex) {
+ data[rowIndex][columnIndex] = value;
+ fireTableCellUpdated(rowIndex, columnIndex);
+ NewField selectedField = newFieldList.get(rowIndex);
+ if (columnIndex == Column.DEL.getIndex()) {
+ selectedField.setDeleted((Boolean) value);
+ } else if (columnIndex == Column.NAME.getIndex()) {
+ selectedField.setName((String) value);
+ } else if (columnIndex == Column.TYPE.getIndex()) {
+ selectedField.setType((Class<? extends IndexableField>) value);
+ selectedField.resetFieldType((Class<? extends IndexableField>) value);
+ selectedField.setStored(selectedField.getFieldType().stored());
+ } else if (columnIndex == Column.VALUE.getIndex()) {
+ selectedField.setValue((String) value);
+ }
+ }
+
+ @Override
+ protected Column[] columnInfos() {
+ return Column.values();
+ }
+ }
+
+ static final class TypeCellRenderer implements TableCellRenderer {
+
+ @SuppressWarnings("unchecked")
+ @Override
+ 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);
+ }
+ }
+
+ static final class OptionsCellRenderer implements TableCellRenderer {
+
+ private JDialog dialog;
+
+ private final IndexOptionsDialogFactory indexOptionsDialogFactory;
+
+ private final List<NewField> newFieldList;
+
+ private final JPanel panel = new JPanel();
+
+ private JTable table;
+
+ public OptionsCellRenderer(JDialog dialog, IndexOptionsDialogFactory indexOptionsDialogFactory, List<NewField> newFieldList) {
+ this.dialog = dialog;
+ this.indexOptionsDialogFactory = indexOptionsDialogFactory;
+ this.newFieldList = newFieldList;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ 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();
+ if (header != null) {
+ panel.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
+ panel.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
+ 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));
+ });
+ }
+ }
+ });
+ panel.add(FontUtils.toLinkText(optionsLbl));
+ }
+ }
+ return panel;
+ }
+
+ }
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogOperator.java
new file mode 100644
index 0000000..2c29d6f
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogOperator.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components.dialog.documents;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry;
+
+/** Operator of add dodument dialog */
+public interface AddDocumentDialogOperator extends ComponentOperatorRegistry.ComponentOperator {
+ void setAnalyzer(Analyzer analyzer);
+}
+
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/DocValuesDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/DocValuesDialogFactory.java
new file mode 100644
index 0000000..7bea476
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/DocValuesDialogFactory.java
@@ -0,0 +1,296 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+import java.awt.FlowLayout;
+import java.awt.Insets;
+import java.awt.Toolkit;
+import java.awt.Window;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.awt.event.ActionEvent;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+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;
+import org.apache.lucene.luke.app.desktop.util.FontUtils;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+import org.apache.lucene.luke.models.documents.DocValues;
+import org.apache.lucene.luke.util.BytesRefUtils;
+import org.apache.lucene.util.NumericUtils;
+
+/** Factory of doc values dialog */
+public final class DocValuesDialogFactory implements DialogOpener.DialogFactory {
+
+ private static DocValuesDialogFactory instance;
+
+ private final Preferences prefs;
+
+ private final JComboBox<String> decodersCombo = new JComboBox<>();
+
+ private final JList<String> valueList = new JList<>();
+
+ private final ListenerFunctions listeners = new ListenerFunctions();
+
+ private JDialog dialog;
+
+ private String field;
+
+ private DocValues docValues;
+
+ public synchronized static DocValuesDialogFactory getInstance() throws IOException {
+ if (instance == null) {
+ instance = new DocValuesDialogFactory();
+ }
+ return instance;
+ }
+
+ private DocValuesDialogFactory() throws IOException {
+ this.prefs = PreferencesFactory.getInstance();
+ }
+
+ public void setValue(String field, DocValues docValues) {
+ this.field = field;
+ this.docValues = docValues;
+
+ DefaultListModel<String> values = new DefaultListModel<>();
+ if (docValues.getValues().size() > 0) {
+ decodersCombo.setEnabled(false);
+ 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);
+ }
+
+ valueList.setModel(values);
+ }
+
+ @Override
+ public JDialog create(Window owner, String title, int width, int height) {
+ if (Objects.isNull(field) || Objects.isNull(docValues)) {
+ throw new IllegalStateException("field name and/or doc values is not set.");
+ }
+
+ dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL);
+ dialog.add(content());
+ dialog.setSize(new Dimension(width, height));
+ dialog.setLocationRelativeTo(owner);
+ dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor());
+ return dialog;
+ }
+
+ private JPanel content() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
+ panel.add(headerPanel(), BorderLayout.PAGE_START);
+ JScrollPane scrollPane = new JScrollPane(valueList());
+ scrollPane.setOpaque(false);
+ scrollPane.getViewport().setOpaque(false);
+ panel.add(scrollPane, BorderLayout.CENTER);
+ panel.add(footerPanel(), BorderLayout.PAGE_END);
+ return panel;
+ }
+
+ private JPanel headerPanel() {
+ JPanel header = new JPanel();
+ header.setOpaque(false);
+ header.setLayout(new BoxLayout(header, BoxLayout.PAGE_AXIS));
+
+ 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(field));
+ header.add(fieldHeader);
+
+ JPanel typeHeader = new JPanel(new FlowLayout(FlowLayout.LEADING, 3, 3));
+ typeHeader.setOpaque(false);
+ typeHeader.add(new JLabel(MessageUtils.getLocalizedMessage("documents.docvalues.label.type")));
+ typeHeader.add(new JLabel(docValues.getDvType().toString()));
+ header.add(typeHeader);
+
+ 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);
+ decodersCombo.setModel(new DefaultComboBoxModel<>(decoders));
+ decodersCombo.setSelectedItem(Decoder.LONG.toString());
+ decodersCombo.addActionListener(listeners::selectDecoder);
+ decodeHeader.add(decodersCombo);
+ if (docValues.getValues().size() > 0) {
+ decodeHeader.setEnabled(false);
+ }
+ header.add(decodeHeader);
+
+ return header;
+ }
+
+ private JList<String> valueList() {
+ valueList.setVisibleRowCount(5);
+ valueList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+ valueList.setLayoutOrientation(JList.VERTICAL);
+
+ DefaultListModel<String> values = new DefaultListModel<>();
+ if (docValues.getValues().size() > 0) {
+ docValues.getValues().stream()
+ .map(BytesRefUtils::decode)
+ .forEach(values::addElement);
+ } else {
+ docValues.getNumericValues().stream()
+ .map(String::valueOf)
+ .forEach(values::addElement);
+ }
+ valueList.setModel(values);
+
+ return valueList;
+ }
+
+ private JPanel footerPanel() {
+ JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING, 5, 5));
+ footer.setOpaque(false);
+
+ 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);
+
+ JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close"));
+ closeBtn.setMargin(new Insets(3, 0, 3, 0));
+ closeBtn.addActionListener(e -> dialog.dispose());
+ footer.add(closeBtn);
+
+ return footer;
+ }
+
+ // control methods
+
+ private void selectDecoder() {
+ String decoderLabel = (String) decodersCombo.getSelectedItem();
+ Decoder decoder = Decoder.fromLabel(decoderLabel);
+
+ if (docValues.getNumericValues().isEmpty()) {
+ return;
+ }
+
+ DefaultListModel<String> values = new DefaultListModel<>();
+ switch (decoder) {
+ case LONG:
+ docValues.getNumericValues().stream()
+ .map(String::valueOf)
+ .forEach(values::addElement);
+ break;
+ case FLOAT:
+ docValues.getNumericValues().stream()
+ .mapToInt(Long::intValue)
+ .mapToObj(NumericUtils::sortableIntToFloat)
+ .map(String::valueOf)
+ .forEach(values::addElement);
+ break;
+ case DOUBLE:
+ docValues.getNumericValues().stream()
+ .map(NumericUtils::sortableLongToDouble)
+ .map(String::valueOf)
+ .forEach(values::addElement);
+ break;
+ }
+
+ valueList.setModel(values);
+ }
+
+ private void copyValues() {
+ List<String> values = valueList.getSelectedValuesList();
+ if (values.isEmpty()) {
+ values = getAllVlues();
+ }
+
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ StringSelection selection = new StringSelection(String.join("\n", values));
+ clipboard.setContents(selection, null);
+ }
+
+ private List<String> getAllVlues() {
+ List<String> values = new ArrayList<>();
+ for (int i = 0; i < valueList.getModel().getSize(); i++) {
+ values.add(valueList.getModel().getElementAt(i));
+ }
+ return values;
+ }
+
+ private class ListenerFunctions {
+
+ void selectDecoder(ActionEvent e) {
+ DocValuesDialogFactory.this.selectDecoder();
+ }
+
+ void copyValues(ActionEvent e) {
+ DocValuesDialogFactory.this.copyValues();
+ }
+ }
+
+
+ /** doc value decoders */
+ public enum Decoder {
+
+ LONG("long"), FLOAT("float"), DOUBLE("double");
+
+ private final String label;
+
+ Decoder(String label) {
+ this.label = label;
+ }
+
+ @Override
+ public String toString() {
+ return label;
+ }
+
+ public static Decoder fromLabel(String label) {
+ for (Decoder d : values()) {
+ if (d.label.equalsIgnoreCase(label)) {
+ return d;
+ }
+ }
+ throw new IllegalArgumentException("No such decoder: " + label);
+ }
+ }
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/IndexOptionsDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/IndexOptionsDialogFactory.java
new file mode 100644
index 0000000..a0bda9c
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/IndexOptionsDialogFactory.java
@@ -0,0 +1,308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.luke.app.desktop.components.dialog.documents;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+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;
+import org.apache.lucene.index.DocValuesType;
+import org.apache.lucene.index.IndexOptions;
+import org.apache.lucene.index.IndexableFieldType;
+import org.apache.lucene.luke.app.desktop.Preferences;
+import org.apache.lucene.luke.app.desktop.PreferencesFactory;
+import org.apache.lucene.luke.app.desktop.dto.documents.NewField;
+import org.apache.lucene.luke.app.desktop.util.DialogOpener;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+
+/** Factory of index options dialog */
+public final class IndexOptionsDialogFactory implements DialogOpener.DialogFactory {
+
+ private static IndexOptionsDialogFactory instance;
+
+ private final Preferences prefs;
+
+ private final JCheckBox storedCB = new JCheckBox();
+
+ private final JCheckBox tokenizedCB = new JCheckBox();
+
+ private final JCheckBox omitNormsCB = new JCheckBox();
+
+ private final JComboBox<String> idxOptCombo = new JComboBox<>(availableIndexOptions());
+
+ private final JCheckBox storeTVCB = new JCheckBox();
+
+ private final JCheckBox storeTVPosCB = new JCheckBox();
+
+ private final JCheckBox storeTVOffCB = new JCheckBox();
+
+ private final JCheckBox storeTVPayCB = new JCheckBox();
+
+ private final JComboBox<String> dvTypeCombo = new JComboBox<>(availableDocValuesType());
+
+ private final JTextField dimCountTF = new JTextField();
+
+ private final JTextField dimNumBytesTF = new JTextField();
+
+ private JDialog dialog;
+
+ private NewField nf;
+
+ public synchronized static IndexOptionsDialogFactory getInstance() throws IOException {
+ if (instance == null) {
+ instance = new IndexOptionsDialogFactory();
+ }
+ return instance;
+ }
+
+ private IndexOptionsDialogFactory() throws IOException {
+ this.prefs = PreferencesFactory.getInstance();
+ initialize();
+ }
+
+ private void initialize() {
+ storedCB.setText(MessageUtils.getLocalizedMessage("idx_options.checkbox.stored"));
+ storedCB.setOpaque(false);
+ tokenizedCB.setText(MessageUtils.getLocalizedMessage("idx_options.checkbox.tokenized"));
+ tokenizedCB.setOpaque(false);
+ omitNormsCB.setText(MessageUtils.getLocalizedMessage("idx_options.checkbox.omit_norm"));
+ omitNormsCB.setOpaque(false);
+ idxOptCombo.setPreferredSize(new Dimension(300, idxOptCombo.getPreferredSize().height));
+ storeTVCB.setText(MessageUtils.getLocalizedMessage("idx_options.checkbox.store_tv"));
+ storeTVCB.setOpaque(false);
+ storeTVPosCB.setText(MessageUtils.getLocalizedMessage("idx_options.checkbox.store_tv_pos"));
+ storeTVPosCB.setOpaque(false);
+ storeTVOffCB.setText(MessageUtils.getLocalizedMessage("idx_options.checkbox.store_tv_off"));
+ storeTVOffCB.setOpaque(false);
+ storeTVPayCB.setText(MessageUtils.getLocalizedMessage("idx_options.checkbox.store_tv_pay"));
+ storeTVPayCB.setOpaque(false);
+ dimCountTF.setColumns(4);
+ dimNumBytesTF.setColumns(4);
+ }
+
+ @Override
+ public JDialog create(Window owner, String title, int width, int height) {
+ dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL);
+ dialog.add(content());
+ dialog.setSize(new Dimension(width, height));
+ dialog.setLocationRelativeTo(owner);
+ dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor());
+ return dialog;
+ }
+
+ private JPanel content() {
+ JPanel panel = new JPanel();
+ panel.setOpaque(false);
+ panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
+ panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
+
+ panel.add(indexOptions());
+ panel.add(new JSeparator(JSeparator.HORIZONTAL));
+ panel.add(tvOptions());
+ panel.add(new JSeparator(JSeparator.HORIZONTAL));
+ panel.add(dvOptions());
+ panel.add(new JSeparator(JSeparator.HORIZONTAL));
+ panel.add(pvOptions());
+ panel.add(new JSeparator(JSeparator.HORIZONTAL));
+ panel.add(footer());
+ return panel;
+ }
+
+ private JPanel indexOptions() {
+ JPanel panel = new JPanel();
+ panel.setOpaque(false);
+ panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
+
+ JPanel inner1 = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 5));
+ inner1.setOpaque(false);
+ inner1.add(storedCB);
+
+ inner1.add(tokenizedCB);
+ inner1.add(omitNormsCB);
+ panel.add(inner1);
+
+ JPanel inner2 = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 1));
+ inner2.setOpaque(false);
+ JLabel idxOptLbl = new JLabel(MessageUtils.getLocalizedMessage("idx_options.label.index_options"));
+ inner2.add(idxOptLbl);
+ inner2.add(idxOptCombo);
+ panel.add(inner2);
+
+ return panel;
+ }
+
+ private JPanel tvOptions() {
+ JPanel panel = new JPanel();
+ panel.setOpaque(false);
+ panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
+
+ JPanel inner1 = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 2));
+ inner1.setOpaque(false);
+ inner1.add(storeTVCB);
+ panel.add(inner1);
+
+ JPanel inner2 = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 2));
+ inner2.setOpaque(false);
+ inner2.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
+ inner2.add(storeTVPosCB);
+ inner2.add(storeTVOffCB);
+ inner2.add(storeTVPayCB);
+ panel.add(inner2);
+
+ return panel;
+ }
+
+ private JPanel dvOptions() {
+ JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 2));
+ panel.setOpaque(false);
+ JLabel dvTypeLbl = new JLabel(MessageUtils.getLocalizedMessage("idx_options.label.dv_type"));
+ panel.add(dvTypeLbl);
+ panel.add(dvTypeCombo);
+ return panel;
+ }
+
+ private JPanel pvOptions() {
+ JPanel panel = new JPanel();
+ panel.setOpaque(false);
+ panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
+
+ JPanel inner1 = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 2));
+ inner1.setOpaque(false);
+ inner1.add(new JLabel(MessageUtils.getLocalizedMessage("idx_options.label.point_dims")));
+ panel.add(inner1);
+
+ JPanel inner2 = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 2));
+ inner2.setOpaque(false);
+ inner2.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
+ inner2.add(new JLabel(MessageUtils.getLocalizedMessage("idx_options.label.point_dc")));
+ inner2.add(dimCountTF);
+ inner2.add(new JLabel(MessageUtils.getLocalizedMessage("idx_options.label.point_nb")));
+ inner2.add(dimNumBytesTF);
+ panel.add(inner2);
+
+ return panel;
+ }
+
+ private JPanel footer() {
+ JPanel panel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
+ panel.setOpaque(false);
+ JButton okBtn = new JButton(MessageUtils.getLocalizedMessage("button.ok"));
+ okBtn.setMargin(new Insets(3, 3, 3, 3));
+ okBtn.addActionListener(e -> saveOptions());
+ panel.add(okBtn);
+ JButton cancelBtn = new JButton(MessageUtils.getLocalizedMessage("button.cancel"));
+ cancelBtn.setMargin(new Insets(3, 3, 3, 3));
+ cancelBtn.addActionListener(e -> dialog.dispose());
+ panel.add(cancelBtn);
+
+ return panel;
+ }
+
+ // control methods
+
+ public void setNewField(NewField nf) {
+ this.nf = nf;
+
+ storedCB.setSelected(nf.isStored());
+
+ IndexableFieldType fieldType = nf.getFieldType();
+ tokenizedCB.setSelected(fieldType.tokenized());
+ omitNormsCB.setSelected(fieldType.omitNorms());
+ idxOptCombo.setSelectedItem(fieldType.indexOptions().name());
+ storeTVCB.setSelected(fieldType.storeTermVectors());
+ storeTVPosCB.setSelected(fieldType.storeTermVectorPositions());
+ storeTVOffCB.setSelected(fieldType.storeTermVectorOffsets());
+ storeTVPayCB.setSelected(fieldType.storeTermVectorPayloads());
+ dvTypeCombo.setSelectedItem(fieldType.docValuesType().name());
+ dimCountTF.setText(String.valueOf(fieldType.pointDataDimensionCount()));
+ 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)) {
+ storedCB.setEnabled(true);
+ } else {
+ storedCB.setEnabled(false);
+ }
+
+ if (nf.getType().equals(Field.class)) {
+ tokenizedCB.setEnabled(true);
+ omitNormsCB.setEnabled(true);
+ idxOptCombo.setEnabled(true);
+ storeTVCB.setEnabled(true);
+ storeTVPosCB.setEnabled(true);
+ storeTVOffCB.setEnabled(true);
+ storeTVPosCB.setEnabled(true);
+ } else {
+ tokenizedCB.setEnabled(false);
+ omitNormsCB.setEnabled(false);
+ idxOptCombo.setEnabled(false);
+ storeTVCB.setEnabled(false);
+ storeTVPosCB.setEnabled(false);
+ storeTVOffCB.setEnabled(false);
+ storeTVPayCB.setEnabled(false);
+ }
+
+ // TODO
+ dvTypeCombo.setEnabled(false);
+ dimCountTF.setEnabled(false);
+ dimNumBytesTF.setEnabled(false);
+ }
+
+ private void saveOptions() {
+ nf.setStored(storedCB.isSelected());
+ if (nf.getType().equals(Field.class)) {
+ FieldType ftype = (FieldType) nf.getFieldType();
+ ftype.setStored(storedCB.isSelected());
+ ftype.setTokenized(tokenizedCB.isSelected());
+ ftype.setOmitNorms(omitNormsCB.isSelected());
+ ftype.setIndexOptions(IndexOptions.valueOf((String) idxOptCombo.getSelectedItem()));
+ ftype.setStoreTermVectors(storeTVCB.isSelected());
+ ftype.setStoreTermVectorPositions(storeTVPosCB.isSelected());
+ ftype.setStoreTermVectorOffsets(storeTVOffCB.isSelected());
+ ftype.setStoreTermVectorPayloads(storeTVPayCB.isSelected());
+ }
+ dialog.dispose();
+ }
+
+ private static String[] availableIndexOptions() {
+ return Arrays.stream(IndexOptions.values()).map(IndexOptions::name).toArray(String[]::new);
+ }
+
+ private static String[] availableDocValuesType() {
+ return Arrays.stream(DocValuesType.values()).map(DocValuesType::name).toArray(String[]::new);
+ }
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/StoredValueDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/StoredValueDialogFactory.java
new file mode 100644
index 0000000..bd179f7
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/StoredValueDialogFactory.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Insets;
+import java.awt.Toolkit;
+import java.awt.Window;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.io.IOException;
+import java.util.Objects;
+
+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;
+import org.apache.lucene.luke.app.desktop.util.FontUtils;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+
+/** Factory of stored values dialog */
+public final class StoredValueDialogFactory implements DialogOpener.DialogFactory {
+
+ private static StoredValueDialogFactory instance;
+
+ private final Preferences prefs;
+
+ private JDialog dialog;
+
+ private String field;
+
+ private String value;
+
+ public synchronized static StoredValueDialogFactory getInstance() throws IOException {
+ if (instance == null) {
+ instance = new StoredValueDialogFactory();
+ }
+ return instance;
+ }
+
+ public void setField(String field) {
+ this.field = field;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ private StoredValueDialogFactory() throws IOException {
+ this.prefs = PreferencesFactory.getInstance();
+ }
+
+ @Override
+ public JDialog create(Window owner, String title, int width, int height) {
+ if (Objects.isNull(field) || Objects.isNull(value)) {
+ throw new IllegalStateException("field name and/or stored value is not set.");
+ }
+
+ dialog = new JDialog(owner, "Term Vector", Dialog.ModalityType.APPLICATION_MODAL);
+ dialog.add(content());
+ dialog.setSize(new Dimension(width, height));
+ dialog.setLocationRelativeTo(owner);
+ dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor());
+ return dialog;
+ }
+
+ private JPanel content() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
+
+ JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING, 5, 5));
+ header.setOpaque(false);
+ header.add(new JLabel(MessageUtils.getLocalizedMessage("documents.stored.label.stored_value")));
+ header.add(new JLabel(field));
+ panel.add(header, BorderLayout.PAGE_START);
+
+ JTextArea valueTA = new JTextArea(value);
+ valueTA.setLineWrap(true);
+ valueTA.setEditable(false);
+ valueTA.setBackground(Color.white);
+ JScrollPane scrollPane = new JScrollPane(valueTA);
+ panel.add(scrollPane, BorderLayout.CENTER);
+
+ JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING, 5, 5));
+ footer.setOpaque(false);
+
+ 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);
+ });
+ footer.add(copyBtn);
+
+ JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close"));
+ closeBtn.setMargin(new Insets(3, 3, 3, 3));
+ closeBtn.addActionListener(e -> dialog.dispose());
+ footer.add(closeBtn);
+ panel.add(footer, BorderLayout.PAGE_END);
+
+ return panel;
+ }
+
+
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/TermVectorDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/TermVectorDialogFactory.java
new file mode 100644
index 0000000..2e7da58
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/TermVectorDialogFactory.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+import java.awt.FlowLayout;
+import java.awt.Insets;
+import java.awt.Window;
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+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;
+import org.apache.lucene.luke.app.desktop.components.TableModelBase;
+import org.apache.lucene.luke.app.desktop.util.DialogOpener;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+import org.apache.lucene.luke.app.desktop.util.TableUtils;
+import org.apache.lucene.luke.models.documents.TermVectorEntry;
+
+/** Factory of term vector dialog */
+public final class TermVectorDialogFactory implements DialogOpener.DialogFactory {
+
+ private static TermVectorDialogFactory instance;
+
+ private final Preferences prefs;
+
+ private JDialog dialog;
+
+ private String field;
+
+ private List<TermVectorEntry> tvEntries;
+
+ public synchronized static TermVectorDialogFactory getInstance() throws IOException {
+ if (instance == null) {
+ instance = new TermVectorDialogFactory();
+ }
+ return instance;
+ }
+
+ private TermVectorDialogFactory() throws IOException {
+ this.prefs = PreferencesFactory.getInstance();
+ }
+
+ public void setField(String field) {
+ this.field = field;
+ }
+
+ public void setTvEntries(List<TermVectorEntry> tvEntries) {
+ this.tvEntries = tvEntries;
+ }
+
+ @Override
+ public JDialog create(Window owner, String title, int width, int height) {
+ if (Objects.isNull(field) || Objects.isNull(tvEntries)) {
+ throw new IllegalStateException("field name and/or term vector is not set.");
+ }
+
+ dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL);
+ dialog.add(content());
+ dialog.setSize(new Dimension(width, height));
+ dialog.setLocationRelativeTo(owner);
+ dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor());
+ return dialog;
+ }
+
+ private JPanel content() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
+
+ 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(field));
+ panel.add(header, BorderLayout.PAGE_START);
+
+ JTable tvTable = new JTable();
+ TableUtils.setupTable(tvTable, ListSelectionModel.SINGLE_SELECTION, new TermVectorTableModel(tvEntries), null, 100, 50, 100);
+ JScrollPane scrollPane = new JScrollPane(tvTable);
+ panel.add(scrollPane, BorderLayout.CENTER);
+
+ JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING, 0, 10));
+ footer.setOpaque(false);
+ JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close"));
+ closeBtn.setMargin(new Insets(3, 3, 3, 3));
+ closeBtn.addActionListener(e -> dialog.dispose());
+ footer.add(closeBtn);
+ panel.add(footer, BorderLayout.PAGE_END);
+
+ return panel;
+ }
+
+ 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),
+ OFFSETS("Offsets", 3, String.class);
+
+ private String colName;
+ private int index;
+ private Class<?> type;
+
+ Column(String colName, int index, Class<?> type) {
+ this.colName = colName;
+ this.index = index;
+ this.type = type;
+ }
+
+ @Override
+ public String getColName() {
+ return colName;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public Class<?> getType() {
+ return type;
+ }
+ }
+
+ TermVectorTableModel() {
+ super();
+ }
+
+ TermVectorTableModel(List<TermVectorEntry> tvEntries) {
+ super(tvEntries.size());
+
+ for (int i = 0; i < tvEntries.size(); i++) {
+ TermVectorEntry entry = tvEntries.get(i);
+
+ 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())
+ );
+
+ data[i] = new Object[]{termText, freq, positions, offsets};
+ }
+
+ }
+
+ @Override
+ protected Column[] columnInfos() {
+ return Column.values();
+ }
+ }
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/package-info.java
new file mode 100644
index 0000000..9c641f9
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** Dialogs used in the Documents tab */
+package org.apache.lucene.luke.app.desktop.components.dialog.documents;
\ No newline at end of file
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/AboutDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/AboutDialogFactory.java
new file mode 100644
index 0000000..e9d9c97
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/AboutDialogFactory.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+import java.awt.Dialog;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Window;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Objects;
+
+import org.apache.lucene.LucenePackage;
+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;
+import org.apache.lucene.luke.app.desktop.util.FontUtils;
+import org.apache.lucene.luke.app.desktop.util.ImageUtils;
+import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+import org.apache.lucene.luke.app.desktop.util.URLLabel;
+import org.apache.lucene.luke.models.LukeException;
+
+/** Factory of about dialog */
+public final class AboutDialogFactory implements DialogOpener.DialogFactory {
+
+ private static AboutDialogFactory instance;
+
+ private final Preferences prefs;
+
+ private JDialog dialog;
+
+ public synchronized static AboutDialogFactory getInstance() throws IOException {
+ if (instance == null) {
+ instance = new AboutDialogFactory();
+ }
+ return instance;
+ }
+
+ private AboutDialogFactory() throws IOException {
+ this.prefs = PreferencesFactory.getInstance();
+ }
+
+ @Override
+ public JDialog create(Window owner, String title, int width, int height) {
+ dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL);
+ dialog.add(content());
+ dialog.setSize(new Dimension(width, height));
+ dialog.setLocationRelativeTo(owner);
+ dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor());
+ return dialog;
+ }
+
+ private JPanel content() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
+
+ panel.add(header(), BorderLayout.PAGE_START);
+ panel.add(center(), BorderLayout.CENTER);
+ panel.add(footer(), BorderLayout.PAGE_END);
+
+ return panel;
+ }
+
+ private JPanel header() {
+ JPanel panel = new JPanel(new GridLayout(3, 1));
+ panel.setOpaque(false);
+
+ JPanel logo = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ logo.setOpaque(false);
+ logo.add(new JLabel(ImageUtils.createImageIcon("luke-logo.gif", 200, 40)));
+ panel.add(logo);
+
+ JPanel project = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ project.setOpaque(false);
+ JLabel projectLbl = new JLabel("Lucene Toolbox Project");
+ projectLbl.setFont(new Font(projectLbl.getFont().getFontName(), Font.BOLD, 32));
+ projectLbl.setForeground(Color.decode("#5aaa88"));
+ project.add(projectLbl);
+ panel.add(project);
+
+ JPanel desc = new JPanel();
+ desc.setOpaque(false);
+ desc.setLayout(new BoxLayout(desc, BoxLayout.PAGE_AXIS));
+
+ JPanel subTitle = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 5));
+ subTitle.setOpaque(false);
+ JLabel subTitleLbl = new JLabel("GUI client of the best Java search library Apache Lucene");
+ subTitleLbl.setFont(new Font(subTitleLbl.getFont().getFontName(), Font.PLAIN, 20));
+ subTitle.add(subTitleLbl);
+ subTitle.add(new JLabel(ImageUtils.createImageIcon("lucene-logo.gif", 100, 15)));
+ desc.add(subTitle);
+
+ JPanel link = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 5));
+ link.setOpaque(false);
+ JLabel linkLbl = FontUtils.toLinkText(new URLLabel("https://lucene.apache.org/"));
+ link.add(linkLbl);
+ desc.add(link);
+
+ panel.add(desc);
+
+ return panel;
+ }
+
+ private JScrollPane center() {
+ JEditorPane editorPane = new JEditorPane();
+ editorPane.setOpaque(false);
+ editorPane.setMargin(new Insets(0, 5, 2, 5));
+ editorPane.setContentType("text/html");
+ editorPane.setText(LICENSE_NOTICE);
+ editorPane.setEditable(false);
+ editorPane.addHyperlinkListener(hyperlinkListener);
+ 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);
+ });
+ return scrollPane;
+ }
+
+ private JPanel footer() {
+ JPanel panel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
+ panel.setOpaque(false);
+ JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close"));
+ closeBtn.setMargin(new Insets(5, 5, 5, 5));
+ if (closeBtn.getActionListeners().length == 0) {
+ closeBtn.addActionListener(e -> dialog.dispose());
+ }
+ panel.add(closeBtn);
+ return panel;
+ }
+
+ 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) " +
... 15087 lines suppressed ...